summaryrefslogtreecommitdiff
path: root/venv/lib/python3.11/site-packages/sqlalchemy/orm
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.11/site-packages/sqlalchemy/orm')
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__init__.py170
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/__init__.cpython-311.pycbin8557 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/_orm_constructors.cpython-311.pycbin100035 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/_typing.cpython-311.pycbin7834 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/attributes.cpython-311.pycbin104505 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/base.cpython-311.pycbin32806 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/bulk_persistence.cpython-311.pycbin70614 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/clsregistry.cpython-311.pycbin26896 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/collections.cpython-311.pycbin68350 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/context.cpython-311.pycbin103753 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/decl_api.cpython-311.pycbin70976 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/decl_base.cpython-311.pycbin76416 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/dependency.cpython-311.pycbin44509 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/descriptor_props.cpython-311.pycbin53451 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/dynamic.cpython-311.pycbin14108 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/evaluator.cpython-311.pycbin17661 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/events.cpython-311.pycbin140389 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/exc.cpython-311.pycbin11103 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/identity.cpython-311.pycbin13935 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/instrumentation.cpython-311.pycbin33762 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/interfaces.cpython-311.pycbin56553 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/loading.cpython-311.pycbin51952 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/mapped_collection.cpython-311.pycbin23740 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/mapper.cpython-311.pycbin175532 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/path_registry.cpython-311.pycbin34743 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/persistence.cpython-311.pycbin50760 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/properties.cpython-311.pycbin34341 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/query.cpython-311.pycbin132189 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/relationships.cpython-311.pycbin135454 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/scoping.cpython-311.pycbin84345 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/session.cpython-311.pycbin205815 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/state.cpython-311.pycbin47732 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/state_changes.cpython-311.pycbin7462 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/strategies.cpython-311.pycbin109480 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/strategy_options.cpython-311.pycbin90575 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/sync.cpython-311.pycbin6977 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/unitofwork.cpython-311.pycbin37079 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/util.cpython-311.pycbin92891 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/writeonly.cpython-311.pycbin29722 -> 0 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/_orm_constructors.py2471
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/_typing.py179
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/attributes.py2835
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/base.py971
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/bulk_persistence.py2048
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/clsregistry.py570
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/collections.py1618
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/context.py3243
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/decl_api.py1875
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py2152
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/dependency.py1304
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/descriptor_props.py1074
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/dynamic.py298
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/evaluator.py368
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/events.py3259
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/exc.py228
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/identity.py302
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/instrumentation.py754
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/interfaces.py1469
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/loading.py1665
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/mapped_collection.py560
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py4420
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/path_registry.py808
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/persistence.py1782
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/properties.py886
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/query.py3394
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py3500
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/scoping.py2165
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/session.py5238
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/state.py1136
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/state_changes.py198
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/strategies.py3344
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/strategy_options.py2555
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/sync.py164
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/unitofwork.py796
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/util.py2416
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/writeonly.py678
76 files changed, 0 insertions, 62893 deletions
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__init__.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__init__.py
deleted file mode 100644
index 70a1129..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__init__.py
+++ /dev/null
@@ -1,170 +0,0 @@
-# orm/__init__.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
-
-"""
-Functional constructs for ORM configuration.
-
-See the SQLAlchemy object relational tutorial and mapper configuration
-documentation for an overview of how this module is used.
-
-"""
-
-from __future__ import annotations
-
-from typing import Any
-
-from . import exc as exc
-from . import mapper as mapperlib
-from . import strategy_options as strategy_options
-from ._orm_constructors import _mapper_fn as mapper
-from ._orm_constructors import aliased as aliased
-from ._orm_constructors import backref as backref
-from ._orm_constructors import clear_mappers as clear_mappers
-from ._orm_constructors import column_property as column_property
-from ._orm_constructors import composite as composite
-from ._orm_constructors import contains_alias as contains_alias
-from ._orm_constructors import create_session as create_session
-from ._orm_constructors import deferred as deferred
-from ._orm_constructors import dynamic_loader as dynamic_loader
-from ._orm_constructors import join as join
-from ._orm_constructors import mapped_column as mapped_column
-from ._orm_constructors import orm_insert_sentinel as orm_insert_sentinel
-from ._orm_constructors import outerjoin as outerjoin
-from ._orm_constructors import query_expression as query_expression
-from ._orm_constructors import relationship as relationship
-from ._orm_constructors import synonym as synonym
-from ._orm_constructors import with_loader_criteria as with_loader_criteria
-from ._orm_constructors import with_polymorphic as with_polymorphic
-from .attributes import AttributeEventToken as AttributeEventToken
-from .attributes import InstrumentedAttribute as InstrumentedAttribute
-from .attributes import QueryableAttribute as QueryableAttribute
-from .base import class_mapper as class_mapper
-from .base import DynamicMapped as DynamicMapped
-from .base import InspectionAttrExtensionType as InspectionAttrExtensionType
-from .base import LoaderCallableStatus as LoaderCallableStatus
-from .base import Mapped as Mapped
-from .base import NotExtension as NotExtension
-from .base import ORMDescriptor as ORMDescriptor
-from .base import PassiveFlag as PassiveFlag
-from .base import SQLORMExpression as SQLORMExpression
-from .base import WriteOnlyMapped as WriteOnlyMapped
-from .context import FromStatement as FromStatement
-from .context import QueryContext as QueryContext
-from .decl_api import add_mapped_attribute as add_mapped_attribute
-from .decl_api import as_declarative as as_declarative
-from .decl_api import declarative_base as declarative_base
-from .decl_api import declarative_mixin as declarative_mixin
-from .decl_api import DeclarativeBase as DeclarativeBase
-from .decl_api import DeclarativeBaseNoMeta as DeclarativeBaseNoMeta
-from .decl_api import DeclarativeMeta as DeclarativeMeta
-from .decl_api import declared_attr as declared_attr
-from .decl_api import has_inherited_table as has_inherited_table
-from .decl_api import MappedAsDataclass as MappedAsDataclass
-from .decl_api import registry as registry
-from .decl_api import synonym_for as synonym_for
-from .decl_base import MappedClassProtocol as MappedClassProtocol
-from .descriptor_props import Composite as Composite
-from .descriptor_props import CompositeProperty as CompositeProperty
-from .descriptor_props import Synonym as Synonym
-from .descriptor_props import SynonymProperty as SynonymProperty
-from .dynamic import AppenderQuery as AppenderQuery
-from .events import AttributeEvents as AttributeEvents
-from .events import InstanceEvents as InstanceEvents
-from .events import InstrumentationEvents as InstrumentationEvents
-from .events import MapperEvents as MapperEvents
-from .events import QueryEvents as QueryEvents
-from .events import SessionEvents as SessionEvents
-from .identity import IdentityMap as IdentityMap
-from .instrumentation import ClassManager as ClassManager
-from .interfaces import EXT_CONTINUE as EXT_CONTINUE
-from .interfaces import EXT_SKIP as EXT_SKIP
-from .interfaces import EXT_STOP as EXT_STOP
-from .interfaces import InspectionAttr as InspectionAttr
-from .interfaces import InspectionAttrInfo as InspectionAttrInfo
-from .interfaces import MANYTOMANY as MANYTOMANY
-from .interfaces import MANYTOONE as MANYTOONE
-from .interfaces import MapperProperty as MapperProperty
-from .interfaces import NO_KEY as NO_KEY
-from .interfaces import NO_VALUE as NO_VALUE
-from .interfaces import ONETOMANY as ONETOMANY
-from .interfaces import PropComparator as PropComparator
-from .interfaces import RelationshipDirection as RelationshipDirection
-from .interfaces import UserDefinedOption as UserDefinedOption
-from .loading import merge_frozen_result as merge_frozen_result
-from .loading import merge_result as merge_result
-from .mapped_collection import attribute_keyed_dict as attribute_keyed_dict
-from .mapped_collection import (
- attribute_mapped_collection as attribute_mapped_collection,
-)
-from .mapped_collection import column_keyed_dict as column_keyed_dict
-from .mapped_collection import (
- column_mapped_collection as column_mapped_collection,
-)
-from .mapped_collection import keyfunc_mapping as keyfunc_mapping
-from .mapped_collection import KeyFuncDict as KeyFuncDict
-from .mapped_collection import mapped_collection as mapped_collection
-from .mapped_collection import MappedCollection as MappedCollection
-from .mapper import configure_mappers as configure_mappers
-from .mapper import Mapper as Mapper
-from .mapper import reconstructor as reconstructor
-from .mapper import validates as validates
-from .properties import ColumnProperty as ColumnProperty
-from .properties import MappedColumn as MappedColumn
-from .properties import MappedSQLExpression as MappedSQLExpression
-from .query import AliasOption as AliasOption
-from .query import Query as Query
-from .relationships import foreign as foreign
-from .relationships import Relationship as Relationship
-from .relationships import RelationshipProperty as RelationshipProperty
-from .relationships import remote as remote
-from .scoping import QueryPropertyDescriptor as QueryPropertyDescriptor
-from .scoping import scoped_session as scoped_session
-from .session import close_all_sessions as close_all_sessions
-from .session import make_transient as make_transient
-from .session import make_transient_to_detached as make_transient_to_detached
-from .session import object_session as object_session
-from .session import ORMExecuteState as ORMExecuteState
-from .session import Session as Session
-from .session import sessionmaker as sessionmaker
-from .session import SessionTransaction as SessionTransaction
-from .session import SessionTransactionOrigin as SessionTransactionOrigin
-from .state import AttributeState as AttributeState
-from .state import InstanceState as InstanceState
-from .strategy_options import contains_eager as contains_eager
-from .strategy_options import defaultload as defaultload
-from .strategy_options import defer as defer
-from .strategy_options import immediateload as immediateload
-from .strategy_options import joinedload as joinedload
-from .strategy_options import lazyload as lazyload
-from .strategy_options import Load as Load
-from .strategy_options import load_only as load_only
-from .strategy_options import noload as noload
-from .strategy_options import raiseload as raiseload
-from .strategy_options import selectin_polymorphic as selectin_polymorphic
-from .strategy_options import selectinload as selectinload
-from .strategy_options import subqueryload as subqueryload
-from .strategy_options import undefer as undefer
-from .strategy_options import undefer_group as undefer_group
-from .strategy_options import with_expression as with_expression
-from .unitofwork import UOWTransaction as UOWTransaction
-from .util import Bundle as Bundle
-from .util import CascadeOptions as CascadeOptions
-from .util import LoaderCriteriaOption as LoaderCriteriaOption
-from .util import object_mapper as object_mapper
-from .util import polymorphic_union as polymorphic_union
-from .util import was_deleted as was_deleted
-from .util import with_parent as with_parent
-from .writeonly import WriteOnlyCollection as WriteOnlyCollection
-from .. import util as _sa_util
-
-
-def __go(lcls: Any) -> None:
- _sa_util.preloaded.import_prefix("sqlalchemy.orm")
- _sa_util.preloaded.import_prefix("sqlalchemy.ext")
-
-
-__go(locals())
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/__init__.cpython-311.pyc
deleted file mode 100644
index 8a0b109..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/__init__.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/_orm_constructors.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/_orm_constructors.cpython-311.pyc
deleted file mode 100644
index 2fa0118..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/_orm_constructors.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/_typing.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/_typing.cpython-311.pyc
deleted file mode 100644
index 90d7d47..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/_typing.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/attributes.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/attributes.cpython-311.pyc
deleted file mode 100644
index d534677..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/attributes.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/base.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/base.cpython-311.pyc
deleted file mode 100644
index 3333b81..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/base.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/bulk_persistence.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/bulk_persistence.cpython-311.pyc
deleted file mode 100644
index 858b764..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/bulk_persistence.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/clsregistry.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/clsregistry.cpython-311.pyc
deleted file mode 100644
index 929ab36..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/clsregistry.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/collections.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/collections.cpython-311.pyc
deleted file mode 100644
index 64bba3c..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/collections.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/context.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/context.cpython-311.pyc
deleted file mode 100644
index a9888f5..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/context.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/decl_api.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/decl_api.cpython-311.pyc
deleted file mode 100644
index 6aba2cb..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/decl_api.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/decl_base.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/decl_base.cpython-311.pyc
deleted file mode 100644
index 7fbe0f4..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/decl_base.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/dependency.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/dependency.cpython-311.pyc
deleted file mode 100644
index de66fd6..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/dependency.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/descriptor_props.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/descriptor_props.cpython-311.pyc
deleted file mode 100644
index 2d3c764..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/descriptor_props.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/dynamic.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/dynamic.cpython-311.pyc
deleted file mode 100644
index af2c1c3..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/dynamic.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/evaluator.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/evaluator.cpython-311.pyc
deleted file mode 100644
index dd10afb..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/evaluator.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/events.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/events.cpython-311.pyc
deleted file mode 100644
index 383eaf6..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/events.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/exc.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/exc.cpython-311.pyc
deleted file mode 100644
index a9f7995..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/exc.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/identity.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/identity.cpython-311.pyc
deleted file mode 100644
index 799b3f1..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/identity.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/instrumentation.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/instrumentation.cpython-311.pyc
deleted file mode 100644
index e2986cd..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/instrumentation.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/interfaces.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/interfaces.cpython-311.pyc
deleted file mode 100644
index 15154d7..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/interfaces.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/loading.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/loading.cpython-311.pyc
deleted file mode 100644
index c5396e8..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/loading.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/mapped_collection.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/mapped_collection.cpython-311.pyc
deleted file mode 100644
index 44aea8f..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/mapped_collection.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/mapper.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/mapper.cpython-311.pyc
deleted file mode 100644
index 58c19aa..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/mapper.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/path_registry.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/path_registry.cpython-311.pyc
deleted file mode 100644
index 8d8ba5f..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/path_registry.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/persistence.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/persistence.cpython-311.pyc
deleted file mode 100644
index 566049c..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/persistence.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/properties.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/properties.cpython-311.pyc
deleted file mode 100644
index 3754ca1..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/properties.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/query.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/query.cpython-311.pyc
deleted file mode 100644
index d2df40d..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/query.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/relationships.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/relationships.cpython-311.pyc
deleted file mode 100644
index c66e5a4..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/relationships.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/scoping.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/scoping.cpython-311.pyc
deleted file mode 100644
index e815007..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/scoping.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/session.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/session.cpython-311.pyc
deleted file mode 100644
index f051751..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/session.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/state.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/state.cpython-311.pyc
deleted file mode 100644
index a70802e..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/state.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/state_changes.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/state_changes.cpython-311.pyc
deleted file mode 100644
index bb62b49..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/state_changes.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/strategies.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/strategies.cpython-311.pyc
deleted file mode 100644
index 57424ef..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/strategies.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/strategy_options.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/strategy_options.cpython-311.pyc
deleted file mode 100644
index e1fd9c9..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/strategy_options.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/sync.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/sync.cpython-311.pyc
deleted file mode 100644
index b1d21cc..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/sync.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/unitofwork.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/unitofwork.cpython-311.pyc
deleted file mode 100644
index 433eae1..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/unitofwork.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/util.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/util.cpython-311.pyc
deleted file mode 100644
index 17dd961..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/util.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/writeonly.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/writeonly.cpython-311.pyc
deleted file mode 100644
index c3a0b5b..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/__pycache__/writeonly.cpython-311.pyc
+++ /dev/null
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/_orm_constructors.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/_orm_constructors.py
deleted file mode 100644
index 7cb536b..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/_orm_constructors.py
+++ /dev/null
@@ -1,2471 +0,0 @@
-# orm/_orm_constructors.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
-
-from __future__ import annotations
-
-import typing
-from typing import Any
-from typing import Callable
-from typing import Collection
-from typing import Iterable
-from typing import NoReturn
-from typing import Optional
-from typing import overload
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import Union
-
-from . import mapperlib as mapperlib
-from ._typing import _O
-from .descriptor_props import Composite
-from .descriptor_props import Synonym
-from .interfaces import _AttributeOptions
-from .properties import MappedColumn
-from .properties import MappedSQLExpression
-from .query import AliasOption
-from .relationships import _RelationshipArgumentType
-from .relationships import _RelationshipDeclared
-from .relationships import _RelationshipSecondaryArgument
-from .relationships import RelationshipProperty
-from .session import Session
-from .util import _ORMJoin
-from .util import AliasedClass
-from .util import AliasedInsp
-from .util import LoaderCriteriaOption
-from .. import sql
-from .. import util
-from ..exc import InvalidRequestError
-from ..sql._typing import _no_kw
-from ..sql.base import _NoArg
-from ..sql.base import SchemaEventTarget
-from ..sql.schema import _InsertSentinelColumnDefault
-from ..sql.schema import SchemaConst
-from ..sql.selectable import FromClause
-from ..util.typing import Annotated
-from ..util.typing import Literal
-
-if TYPE_CHECKING:
- from ._typing import _EntityType
- from ._typing import _ORMColumnExprArgument
- from .descriptor_props import _CC
- from .descriptor_props import _CompositeAttrType
- from .interfaces import PropComparator
- from .mapper import Mapper
- from .query import Query
- from .relationships import _LazyLoadArgumentType
- from .relationships import _ORMColCollectionArgument
- from .relationships import _ORMOrderByArgument
- from .relationships import _RelationshipJoinConditionArgument
- from .relationships import ORMBackrefArgument
- from .session import _SessionBind
- from ..sql._typing import _AutoIncrementType
- from ..sql._typing import _ColumnExpressionArgument
- from ..sql._typing import _FromClauseArgument
- from ..sql._typing import _InfoType
- from ..sql._typing import _OnClauseArgument
- from ..sql._typing import _TypeEngineArgument
- from ..sql.elements import ColumnElement
- from ..sql.schema import _ServerDefaultArgument
- from ..sql.schema import FetchedValue
- from ..sql.selectable import Alias
- from ..sql.selectable import Subquery
-
-
-_T = typing.TypeVar("_T")
-
-
-@util.deprecated(
- "1.4",
- "The :class:`.AliasOption` object is not necessary "
- "for entities to be matched up to a query that is established "
- "via :meth:`.Query.from_statement` and now does nothing.",
- enable_warnings=False, # AliasOption itself warns
-)
-def contains_alias(alias: Union[Alias, Subquery]) -> AliasOption:
- r"""Return a :class:`.MapperOption` that will indicate to the
- :class:`_query.Query`
- that the main table has been aliased.
-
- """
- return AliasOption(alias)
-
-
-def mapped_column(
- __name_pos: Optional[
- Union[str, _TypeEngineArgument[Any], SchemaEventTarget]
- ] = None,
- __type_pos: Optional[
- Union[_TypeEngineArgument[Any], SchemaEventTarget]
- ] = None,
- *args: SchemaEventTarget,
- init: Union[_NoArg, bool] = _NoArg.NO_ARG,
- repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
- default: Optional[Any] = _NoArg.NO_ARG,
- default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
- compare: Union[_NoArg, bool] = _NoArg.NO_ARG,
- kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
- nullable: Optional[
- Union[bool, Literal[SchemaConst.NULL_UNSPECIFIED]]
- ] = SchemaConst.NULL_UNSPECIFIED,
- primary_key: Optional[bool] = False,
- deferred: Union[_NoArg, bool] = _NoArg.NO_ARG,
- deferred_group: Optional[str] = None,
- deferred_raiseload: Optional[bool] = None,
- use_existing_column: bool = False,
- name: Optional[str] = None,
- type_: Optional[_TypeEngineArgument[Any]] = None,
- autoincrement: _AutoIncrementType = "auto",
- doc: Optional[str] = None,
- key: Optional[str] = None,
- index: Optional[bool] = None,
- unique: Optional[bool] = None,
- info: Optional[_InfoType] = None,
- onupdate: Optional[Any] = None,
- insert_default: Optional[Any] = _NoArg.NO_ARG,
- server_default: Optional[_ServerDefaultArgument] = None,
- server_onupdate: Optional[FetchedValue] = None,
- active_history: bool = False,
- quote: Optional[bool] = None,
- system: bool = False,
- comment: Optional[str] = None,
- sort_order: Union[_NoArg, int] = _NoArg.NO_ARG,
- **kw: Any,
-) -> MappedColumn[Any]:
- r"""declare a new ORM-mapped :class:`_schema.Column` construct
- for use within :ref:`Declarative Table <orm_declarative_table>`
- configuration.
-
- The :func:`_orm.mapped_column` function provides an ORM-aware and
- Python-typing-compatible construct which is used with
- :ref:`declarative <orm_declarative_mapping>` mappings to indicate an
- attribute that's mapped to a Core :class:`_schema.Column` object. It
- provides the equivalent feature as mapping an attribute to a
- :class:`_schema.Column` object directly when using Declarative,
- specifically when using :ref:`Declarative Table <orm_declarative_table>`
- configuration.
-
- .. versionadded:: 2.0
-
- :func:`_orm.mapped_column` is normally used with explicit typing along with
- the :class:`_orm.Mapped` annotation type, where it can derive the SQL
- type and nullability for the column based on what's present within the
- :class:`_orm.Mapped` annotation. It also may be used without annotations
- as a drop-in replacement for how :class:`_schema.Column` is used in
- Declarative mappings in SQLAlchemy 1.x style.
-
- For usage examples of :func:`_orm.mapped_column`, see the documentation
- at :ref:`orm_declarative_table`.
-
- .. seealso::
-
- :ref:`orm_declarative_table` - complete documentation
-
- :ref:`whatsnew_20_orm_declarative_typing` - migration notes for
- Declarative mappings using 1.x style mappings
-
- :param __name: String name to give to the :class:`_schema.Column`. This
- is an optional, positional only argument that if present must be the
- first positional argument passed. If omitted, the attribute name to
- which the :func:`_orm.mapped_column` is mapped will be used as the SQL
- column name.
- :param __type: :class:`_types.TypeEngine` type or instance which will
- indicate the datatype to be associated with the :class:`_schema.Column`.
- This is an optional, positional-only argument that if present must
- immediately follow the ``__name`` parameter if present also, or otherwise
- be the first positional parameter. If omitted, the ultimate type for
- the column may be derived either from the annotated type, or if a
- :class:`_schema.ForeignKey` is present, from the datatype of the
- referenced column.
- :param \*args: Additional positional arguments include constructs such
- as :class:`_schema.ForeignKey`, :class:`_schema.CheckConstraint`,
- and :class:`_schema.Identity`, which are passed through to the constructed
- :class:`_schema.Column`.
- :param nullable: Optional bool, whether the column should be "NULL" or
- "NOT NULL". If omitted, the nullability is derived from the type
- annotation based on whether or not ``typing.Optional`` is present.
- ``nullable`` defaults to ``True`` otherwise for non-primary key columns,
- and ``False`` for primary key columns.
- :param primary_key: optional bool, indicates the :class:`_schema.Column`
- would be part of the table's primary key or not.
- :param deferred: Optional bool - this keyword argument is consumed by the
- ORM declarative process, and is not part of the :class:`_schema.Column`
- itself; instead, it indicates that this column should be "deferred" for
- loading as though mapped by :func:`_orm.deferred`.
-
- .. seealso::
-
- :ref:`orm_queryguide_deferred_declarative`
-
- :param deferred_group: Implies :paramref:`_orm.mapped_column.deferred`
- to ``True``, and set the :paramref:`_orm.deferred.group` parameter.
-
- .. seealso::
-
- :ref:`orm_queryguide_deferred_group`
-
- :param deferred_raiseload: Implies :paramref:`_orm.mapped_column.deferred`
- to ``True``, and set the :paramref:`_orm.deferred.raiseload` parameter.
-
- .. seealso::
-
- :ref:`orm_queryguide_deferred_raiseload`
-
- :param use_existing_column: if True, will attempt to locate the given
- column name on an inherited superclass (typically single inheriting
- superclass), and if present, will not produce a new column, mapping
- to the superclass column as though it were omitted from this class.
- This is used for mixins that add new columns to an inherited superclass.
-
- .. seealso::
-
- :ref:`orm_inheritance_column_conflicts`
-
- .. versionadded:: 2.0.0b4
-
- :param default: Passed directly to the
- :paramref:`_schema.Column.default` parameter if the
- :paramref:`_orm.mapped_column.insert_default` parameter is not present.
- Additionally, when used with :ref:`orm_declarative_native_dataclasses`,
- indicates a default Python value that should be applied to the keyword
- constructor within the generated ``__init__()`` method.
-
- Note that in the case of dataclass generation when
- :paramref:`_orm.mapped_column.insert_default` is not present, this means
- the :paramref:`_orm.mapped_column.default` value is used in **two**
- places, both the ``__init__()`` method as well as the
- :paramref:`_schema.Column.default` parameter. While this behavior may
- change in a future release, for the moment this tends to "work out"; a
- default of ``None`` will mean that the :class:`_schema.Column` gets no
- default generator, whereas a default that refers to a non-``None`` Python
- or SQL expression value will be assigned up front on the object when
- ``__init__()`` is called, which is the same value that the Core
- :class:`_sql.Insert` construct would use in any case, leading to the same
- end result.
-
- .. note:: When using Core level column defaults that are callables to
- be interpreted by the underlying :class:`_schema.Column` in conjunction
- with :ref:`ORM-mapped dataclasses
- <orm_declarative_native_dataclasses>`, especially those that are
- :ref:`context-aware default functions <context_default_functions>`,
- **the** :paramref:`_orm.mapped_column.insert_default` **parameter must
- be used instead**. This is necessary to disambiguate the callable from
- being interpreted as a dataclass level default.
-
- :param insert_default: Passed directly to the
- :paramref:`_schema.Column.default` parameter; will supersede the value
- of :paramref:`_orm.mapped_column.default` when present, however
- :paramref:`_orm.mapped_column.default` will always apply to the
- constructor default for a dataclasses mapping.
-
- :param sort_order: An integer that indicates how this mapped column
- should be sorted compared to the others when the ORM is creating a
- :class:`_schema.Table`. Among mapped columns that have the same
- value the default ordering is used, placing first the mapped columns
- defined in the main class, then the ones in the super classes.
- Defaults to 0. The sort is ascending.
-
- .. versionadded:: 2.0.4
-
- :param active_history=False:
-
- When ``True``, indicates that the "previous" value for a
- scalar attribute should be loaded when replaced, if not
- already loaded. Normally, history tracking logic for
- simple non-primary-key scalar values only needs to be
- aware of the "new" value in order to perform a flush. This
- flag is available for applications that make use of
- :func:`.attributes.get_history` or :meth:`.Session.is_modified`
- which also need to know the "previous" value of the attribute.
-
- .. versionadded:: 2.0.10
-
-
- :param init: Specific to :ref:`orm_declarative_native_dataclasses`,
- specifies if the mapped attribute should be part of the ``__init__()``
- method as generated by the dataclass process.
- :param repr: Specific to :ref:`orm_declarative_native_dataclasses`,
- specifies if the mapped attribute should be part of the ``__repr__()``
- method as generated by the dataclass process.
- :param default_factory: Specific to
- :ref:`orm_declarative_native_dataclasses`,
- specifies a default-value generation function that will take place
- as part of the ``__init__()``
- method as generated by the dataclass process.
- :param compare: Specific to
- :ref:`orm_declarative_native_dataclasses`, indicates if this field
- should be included in comparison operations when generating the
- ``__eq__()`` and ``__ne__()`` methods for the mapped class.
-
- .. versionadded:: 2.0.0b4
-
- :param kw_only: Specific to
- :ref:`orm_declarative_native_dataclasses`, indicates if this field
- should be marked as keyword-only when generating the ``__init__()``.
-
- :param \**kw: All remaining keyword arguments are passed through to the
- constructor for the :class:`_schema.Column`.
-
- """
-
- return MappedColumn(
- __name_pos,
- __type_pos,
- *args,
- name=name,
- type_=type_,
- autoincrement=autoincrement,
- insert_default=insert_default,
- attribute_options=_AttributeOptions(
- init, repr, default, default_factory, compare, kw_only
- ),
- doc=doc,
- key=key,
- index=index,
- unique=unique,
- info=info,
- active_history=active_history,
- nullable=nullable,
- onupdate=onupdate,
- primary_key=primary_key,
- server_default=server_default,
- server_onupdate=server_onupdate,
- use_existing_column=use_existing_column,
- quote=quote,
- comment=comment,
- system=system,
- deferred=deferred,
- deferred_group=deferred_group,
- deferred_raiseload=deferred_raiseload,
- sort_order=sort_order,
- **kw,
- )
-
-
-def orm_insert_sentinel(
- name: Optional[str] = None,
- type_: Optional[_TypeEngineArgument[Any]] = None,
- *,
- default: Optional[Any] = None,
- omit_from_statements: bool = True,
-) -> MappedColumn[Any]:
- """Provides a surrogate :func:`_orm.mapped_column` that generates
- a so-called :term:`sentinel` column, allowing efficient bulk
- inserts with deterministic RETURNING sorting for tables that don't
- otherwise have qualifying primary key configurations.
-
- Use of :func:`_orm.orm_insert_sentinel` is analogous to the use of the
- :func:`_schema.insert_sentinel` construct within a Core
- :class:`_schema.Table` construct.
-
- Guidelines for adding this construct to a Declarative mapped class
- are the same as that of the :func:`_schema.insert_sentinel` construct;
- the database table itself also needs to have a column with this name
- present.
-
- For background on how this object is used, see the section
- :ref:`engine_insertmanyvalues_sentinel_columns` as part of the
- section :ref:`engine_insertmanyvalues`.
-
- .. seealso::
-
- :func:`_schema.insert_sentinel`
-
- :ref:`engine_insertmanyvalues`
-
- :ref:`engine_insertmanyvalues_sentinel_columns`
-
-
- .. versionadded:: 2.0.10
-
- """
-
- return mapped_column(
- name=name,
- default=(
- default if default is not None else _InsertSentinelColumnDefault()
- ),
- _omit_from_statements=omit_from_statements,
- insert_sentinel=True,
- use_existing_column=True,
- nullable=True,
- )
-
-
-@util.deprecated_params(
- **{
- arg: (
- "2.0",
- f"The :paramref:`_orm.column_property.{arg}` parameter is "
- "deprecated for :func:`_orm.column_property`. This parameter "
- "applies to a writeable-attribute in a Declarative Dataclasses "
- "configuration only, and :func:`_orm.column_property` is treated "
- "as a read-only attribute in this context.",
- )
- for arg in ("init", "kw_only", "default", "default_factory")
- }
-)
-def column_property(
- column: _ORMColumnExprArgument[_T],
- *additional_columns: _ORMColumnExprArgument[Any],
- group: Optional[str] = None,
- deferred: bool = False,
- raiseload: bool = False,
- comparator_factory: Optional[Type[PropComparator[_T]]] = None,
- init: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
- repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
- default: Optional[Any] = _NoArg.NO_ARG,
- default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
- compare: Union[_NoArg, bool] = _NoArg.NO_ARG,
- kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
- active_history: bool = False,
- expire_on_flush: bool = True,
- info: Optional[_InfoType] = None,
- doc: Optional[str] = None,
-) -> MappedSQLExpression[_T]:
- r"""Provide a column-level property for use with a mapping.
-
- With Declarative mappings, :func:`_orm.column_property` is used to
- map read-only SQL expressions to a mapped class.
-
- When using Imperative mappings, :func:`_orm.column_property` also
- takes on the role of mapping table columns with additional features.
- When using fully Declarative mappings, the :func:`_orm.mapped_column`
- construct should be used for this purpose.
-
- With Declarative Dataclass mappings, :func:`_orm.column_property`
- is considered to be **read only**, and will not be included in the
- Dataclass ``__init__()`` constructor.
-
- The :func:`_orm.column_property` function returns an instance of
- :class:`.ColumnProperty`.
-
- .. seealso::
-
- :ref:`mapper_column_property_sql_expressions` - general use of
- :func:`_orm.column_property` to map SQL expressions
-
- :ref:`orm_imperative_table_column_options` - usage of
- :func:`_orm.column_property` with Imperative Table mappings to apply
- additional options to a plain :class:`_schema.Column` object
-
- :param \*cols:
- list of Column objects to be mapped.
-
- :param active_history=False:
-
- Used only for Imperative Table mappings, or legacy-style Declarative
- mappings (i.e. which have not been upgraded to
- :func:`_orm.mapped_column`), for column-based attributes that are
- expected to be writeable; use :func:`_orm.mapped_column` with
- :paramref:`_orm.mapped_column.active_history` for Declarative mappings.
- See that parameter for functional details.
-
- :param comparator_factory: a class which extends
- :class:`.ColumnProperty.Comparator` which provides custom SQL
- clause generation for comparison operations.
-
- :param group:
- a group name for this property when marked as deferred.
-
- :param deferred:
- when True, the column property is "deferred", meaning that
- it does not load immediately, and is instead loaded when the
- attribute is first accessed on an instance. See also
- :func:`~sqlalchemy.orm.deferred`.
-
- :param doc:
- optional string that will be applied as the doc on the
- class-bound descriptor.
-
- :param expire_on_flush=True:
- Disable expiry on flush. A column_property() which refers
- to a SQL expression (and not a single table-bound column)
- is considered to be a "read only" property; populating it
- has no effect on the state of data, and it can only return
- database state. For this reason a column_property()'s value
- is expired whenever the parent object is involved in a
- flush, that is, has any kind of "dirty" state within a flush.
- Setting this parameter to ``False`` will have the effect of
- leaving any existing value present after the flush proceeds.
- Note that the :class:`.Session` with default expiration
- settings still expires
- all attributes after a :meth:`.Session.commit` call, however.
-
- :param info: Optional data dictionary which will be populated into the
- :attr:`.MapperProperty.info` attribute of this object.
-
- :param raiseload: if True, indicates the column should raise an error
- when undeferred, rather than loading the value. This can be
- altered at query time by using the :func:`.deferred` option with
- raiseload=False.
-
- .. versionadded:: 1.4
-
- .. seealso::
-
- :ref:`orm_queryguide_deferred_raiseload`
-
- :param init:
-
- :param default:
-
- :param default_factory:
-
- :param kw_only:
-
- """
- return MappedSQLExpression(
- column,
- *additional_columns,
- attribute_options=_AttributeOptions(
- False if init is _NoArg.NO_ARG else init,
- repr,
- default,
- default_factory,
- compare,
- kw_only,
- ),
- group=group,
- deferred=deferred,
- raiseload=raiseload,
- comparator_factory=comparator_factory,
- active_history=active_history,
- expire_on_flush=expire_on_flush,
- info=info,
- doc=doc,
- _assume_readonly_dc_attributes=True,
- )
-
-
-@overload
-def composite(
- _class_or_attr: _CompositeAttrType[Any],
- *attrs: _CompositeAttrType[Any],
- group: Optional[str] = None,
- deferred: bool = False,
- raiseload: bool = False,
- comparator_factory: Optional[Type[Composite.Comparator[_T]]] = None,
- active_history: bool = False,
- init: Union[_NoArg, bool] = _NoArg.NO_ARG,
- repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
- default: Optional[Any] = _NoArg.NO_ARG,
- default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
- compare: Union[_NoArg, bool] = _NoArg.NO_ARG,
- kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
- info: Optional[_InfoType] = None,
- doc: Optional[str] = None,
- **__kw: Any,
-) -> Composite[Any]: ...
-
-
-@overload
-def composite(
- _class_or_attr: Type[_CC],
- *attrs: _CompositeAttrType[Any],
- group: Optional[str] = None,
- deferred: bool = False,
- raiseload: bool = False,
- comparator_factory: Optional[Type[Composite.Comparator[_T]]] = None,
- active_history: bool = False,
- init: Union[_NoArg, bool] = _NoArg.NO_ARG,
- repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
- default: Optional[Any] = _NoArg.NO_ARG,
- default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
- compare: Union[_NoArg, bool] = _NoArg.NO_ARG,
- kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
- info: Optional[_InfoType] = None,
- doc: Optional[str] = None,
- **__kw: Any,
-) -> Composite[_CC]: ...
-
-
-@overload
-def composite(
- _class_or_attr: Callable[..., _CC],
- *attrs: _CompositeAttrType[Any],
- group: Optional[str] = None,
- deferred: bool = False,
- raiseload: bool = False,
- comparator_factory: Optional[Type[Composite.Comparator[_T]]] = None,
- active_history: bool = False,
- init: Union[_NoArg, bool] = _NoArg.NO_ARG,
- repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
- default: Optional[Any] = _NoArg.NO_ARG,
- default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
- compare: Union[_NoArg, bool] = _NoArg.NO_ARG,
- kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
- info: Optional[_InfoType] = None,
- doc: Optional[str] = None,
- **__kw: Any,
-) -> Composite[_CC]: ...
-
-
-def composite(
- _class_or_attr: Union[
- None, Type[_CC], Callable[..., _CC], _CompositeAttrType[Any]
- ] = None,
- *attrs: _CompositeAttrType[Any],
- group: Optional[str] = None,
- deferred: bool = False,
- raiseload: bool = False,
- comparator_factory: Optional[Type[Composite.Comparator[_T]]] = None,
- active_history: bool = False,
- init: Union[_NoArg, bool] = _NoArg.NO_ARG,
- repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
- default: Optional[Any] = _NoArg.NO_ARG,
- default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
- compare: Union[_NoArg, bool] = _NoArg.NO_ARG,
- kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
- info: Optional[_InfoType] = None,
- doc: Optional[str] = None,
- **__kw: Any,
-) -> Composite[Any]:
- r"""Return a composite column-based property for use with a Mapper.
-
- See the mapping documentation section :ref:`mapper_composite` for a
- full usage example.
-
- The :class:`.MapperProperty` returned by :func:`.composite`
- is the :class:`.Composite`.
-
- :param class\_:
- The "composite type" class, or any classmethod or callable which
- will produce a new instance of the composite object given the
- column values in order.
-
- :param \*attrs:
- List of elements to be mapped, which may include:
-
- * :class:`_schema.Column` objects
- * :func:`_orm.mapped_column` constructs
- * string names of other attributes on the mapped class, which may be
- any other SQL or object-mapped attribute. This can for
- example allow a composite that refers to a many-to-one relationship
-
- :param active_history=False:
- When ``True``, indicates that the "previous" value for a
- scalar attribute should be loaded when replaced, if not
- already loaded. See the same flag on :func:`.column_property`.
-
- :param group:
- A group name for this property when marked as deferred.
-
- :param deferred:
- When True, the column property is "deferred", meaning that it does
- not load immediately, and is instead loaded when the attribute is
- first accessed on an instance. See also
- :func:`~sqlalchemy.orm.deferred`.
-
- :param comparator_factory: a class which extends
- :class:`.Composite.Comparator` which provides custom SQL
- clause generation for comparison operations.
-
- :param doc:
- optional string that will be applied as the doc on the
- class-bound descriptor.
-
- :param info: Optional data dictionary which will be populated into the
- :attr:`.MapperProperty.info` attribute of this object.
-
- :param init: Specific to :ref:`orm_declarative_native_dataclasses`,
- specifies if the mapped attribute should be part of the ``__init__()``
- method as generated by the dataclass process.
- :param repr: Specific to :ref:`orm_declarative_native_dataclasses`,
- specifies if the mapped attribute should be part of the ``__repr__()``
- method as generated by the dataclass process.
- :param default_factory: Specific to
- :ref:`orm_declarative_native_dataclasses`,
- specifies a default-value generation function that will take place
- as part of the ``__init__()``
- method as generated by the dataclass process.
-
- :param compare: Specific to
- :ref:`orm_declarative_native_dataclasses`, indicates if this field
- should be included in comparison operations when generating the
- ``__eq__()`` and ``__ne__()`` methods for the mapped class.
-
- .. versionadded:: 2.0.0b4
-
- :param kw_only: Specific to
- :ref:`orm_declarative_native_dataclasses`, indicates if this field
- should be marked as keyword-only when generating the ``__init__()``.
-
- """
- if __kw:
- raise _no_kw()
-
- return Composite(
- _class_or_attr,
- *attrs,
- attribute_options=_AttributeOptions(
- init, repr, default, default_factory, compare, kw_only
- ),
- group=group,
- deferred=deferred,
- raiseload=raiseload,
- comparator_factory=comparator_factory,
- active_history=active_history,
- info=info,
- doc=doc,
- )
-
-
-def with_loader_criteria(
- entity_or_base: _EntityType[Any],
- where_criteria: Union[
- _ColumnExpressionArgument[bool],
- Callable[[Any], _ColumnExpressionArgument[bool]],
- ],
- loader_only: bool = False,
- include_aliases: bool = False,
- propagate_to_loaders: bool = True,
- track_closure_variables: bool = True,
-) -> LoaderCriteriaOption:
- """Add additional WHERE criteria to the load for all occurrences of
- a particular entity.
-
- .. versionadded:: 1.4
-
- The :func:`_orm.with_loader_criteria` option is intended to add
- limiting criteria to a particular kind of entity in a query,
- **globally**, meaning it will apply to the entity as it appears
- in the SELECT query as well as within any subqueries, join
- conditions, and relationship loads, including both eager and lazy
- loaders, without the need for it to be specified in any particular
- part of the query. The rendering logic uses the same system used by
- single table inheritance to ensure a certain discriminator is applied
- to a table.
-
- E.g., using :term:`2.0-style` queries, we can limit the way the
- ``User.addresses`` collection is loaded, regardless of the kind
- of loading used::
-
- from sqlalchemy.orm import with_loader_criteria
-
- stmt = select(User).options(
- selectinload(User.addresses),
- with_loader_criteria(Address, Address.email_address != 'foo'))
- )
-
- Above, the "selectinload" for ``User.addresses`` will apply the
- given filtering criteria to the WHERE clause.
-
- Another example, where the filtering will be applied to the
- ON clause of the join, in this example using :term:`1.x style`
- queries::
-
- q = session.query(User).outerjoin(User.addresses).options(
- with_loader_criteria(Address, Address.email_address != 'foo'))
- )
-
- The primary purpose of :func:`_orm.with_loader_criteria` is to use
- it in the :meth:`_orm.SessionEvents.do_orm_execute` event handler
- to ensure that all occurrences of a particular entity are filtered
- in a certain way, such as filtering for access control roles. It
- also can be used to apply criteria to relationship loads. In the
- example below, we can apply a certain set of rules to all queries
- emitted by a particular :class:`_orm.Session`::
-
- session = Session(bind=engine)
-
- @event.listens_for("do_orm_execute", session)
- def _add_filtering_criteria(execute_state):
-
- if (
- execute_state.is_select
- and not execute_state.is_column_load
- and not execute_state.is_relationship_load
- ):
- execute_state.statement = execute_state.statement.options(
- with_loader_criteria(
- SecurityRole,
- lambda cls: cls.role.in_(['some_role']),
- include_aliases=True
- )
- )
-
- In the above example, the :meth:`_orm.SessionEvents.do_orm_execute`
- event will intercept all queries emitted using the
- :class:`_orm.Session`. For those queries which are SELECT statements
- and are not attribute or relationship loads a custom
- :func:`_orm.with_loader_criteria` option is added to the query. The
- :func:`_orm.with_loader_criteria` option will be used in the given
- statement and will also be automatically propagated to all relationship
- loads that descend from this query.
-
- The criteria argument given is a ``lambda`` that accepts a ``cls``
- argument. The given class will expand to include all mapped subclass
- and need not itself be a mapped class.
-
- .. tip::
-
- When using :func:`_orm.with_loader_criteria` option in
- conjunction with the :func:`_orm.contains_eager` loader option,
- it's important to note that :func:`_orm.with_loader_criteria` only
- affects the part of the query that determines what SQL is rendered
- in terms of the WHERE and FROM clauses. The
- :func:`_orm.contains_eager` option does not affect the rendering of
- the SELECT statement outside of the columns clause, so does not have
- any interaction with the :func:`_orm.with_loader_criteria` option.
- However, the way things "work" is that :func:`_orm.contains_eager`
- is meant to be used with a query that is already selecting from the
- additional entities in some way, where
- :func:`_orm.with_loader_criteria` can apply it's additional
- criteria.
-
- In the example below, assuming a mapping relationship as
- ``A -> A.bs -> B``, the given :func:`_orm.with_loader_criteria`
- option will affect the way in which the JOIN is rendered::
-
- stmt = select(A).join(A.bs).options(
- contains_eager(A.bs),
- with_loader_criteria(B, B.flag == 1)
- )
-
- Above, the given :func:`_orm.with_loader_criteria` option will
- affect the ON clause of the JOIN that is specified by
- ``.join(A.bs)``, so is applied as expected. The
- :func:`_orm.contains_eager` option has the effect that columns from
- ``B`` are added to the columns clause::
-
- SELECT
- b.id, b.a_id, b.data, b.flag,
- a.id AS id_1,
- a.data AS data_1
- FROM a JOIN b ON a.id = b.a_id AND b.flag = :flag_1
-
-
- The use of the :func:`_orm.contains_eager` option within the above
- statement has no effect on the behavior of the
- :func:`_orm.with_loader_criteria` option. If the
- :func:`_orm.contains_eager` option were omitted, the SQL would be
- the same as regards the FROM and WHERE clauses, where
- :func:`_orm.with_loader_criteria` continues to add its criteria to
- the ON clause of the JOIN. The addition of
- :func:`_orm.contains_eager` only affects the columns clause, in that
- additional columns against ``b`` are added which are then consumed
- by the ORM to produce ``B`` instances.
-
- .. warning:: The use of a lambda inside of the call to
- :func:`_orm.with_loader_criteria` is only invoked **once per unique
- class**. Custom functions should not be invoked within this lambda.
- See :ref:`engine_lambda_caching` for an overview of the "lambda SQL"
- feature, which is for advanced use only.
-
- :param entity_or_base: a mapped class, or a class that is a super
- class of a particular set of mapped classes, to which the rule
- will apply.
-
- :param where_criteria: a Core SQL expression that applies limiting
- criteria. This may also be a "lambda:" or Python function that
- accepts a target class as an argument, when the given class is
- a base with many different mapped subclasses.
-
- .. note:: To support pickling, use a module-level Python function to
- produce the SQL expression instead of a lambda or a fixed SQL
- expression, which tend to not be picklable.
-
- :param include_aliases: if True, apply the rule to :func:`_orm.aliased`
- constructs as well.
-
- :param propagate_to_loaders: defaults to True, apply to relationship
- loaders such as lazy loaders. This indicates that the
- option object itself including SQL expression is carried along with
- each loaded instance. Set to ``False`` to prevent the object from
- being assigned to individual instances.
-
-
- .. seealso::
-
- :ref:`examples_session_orm_events` - includes examples of using
- :func:`_orm.with_loader_criteria`.
-
- :ref:`do_orm_execute_global_criteria` - basic example on how to
- combine :func:`_orm.with_loader_criteria` with the
- :meth:`_orm.SessionEvents.do_orm_execute` event.
-
- :param track_closure_variables: when False, closure variables inside
- of a lambda expression will not be used as part of
- any cache key. This allows more complex expressions to be used
- inside of a lambda expression but requires that the lambda ensures
- it returns the identical SQL every time given a particular class.
-
- .. versionadded:: 1.4.0b2
-
- """
- return LoaderCriteriaOption(
- entity_or_base,
- where_criteria,
- loader_only,
- include_aliases,
- propagate_to_loaders,
- track_closure_variables,
- )
-
-
-def relationship(
- argument: Optional[_RelationshipArgumentType[Any]] = None,
- secondary: Optional[_RelationshipSecondaryArgument] = None,
- *,
- uselist: Optional[bool] = None,
- collection_class: Optional[
- Union[Type[Collection[Any]], Callable[[], Collection[Any]]]
- ] = None,
- primaryjoin: Optional[_RelationshipJoinConditionArgument] = None,
- secondaryjoin: Optional[_RelationshipJoinConditionArgument] = None,
- back_populates: Optional[str] = None,
- order_by: _ORMOrderByArgument = False,
- backref: Optional[ORMBackrefArgument] = None,
- overlaps: Optional[str] = None,
- post_update: bool = False,
- cascade: str = "save-update, merge",
- viewonly: bool = False,
- init: Union[_NoArg, bool] = _NoArg.NO_ARG,
- repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
- default: Union[_NoArg, _T] = _NoArg.NO_ARG,
- default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
- compare: Union[_NoArg, bool] = _NoArg.NO_ARG,
- kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
- lazy: _LazyLoadArgumentType = "select",
- passive_deletes: Union[Literal["all"], bool] = False,
- passive_updates: bool = True,
- active_history: bool = False,
- enable_typechecks: bool = True,
- foreign_keys: Optional[_ORMColCollectionArgument] = None,
- remote_side: Optional[_ORMColCollectionArgument] = None,
- join_depth: Optional[int] = None,
- comparator_factory: Optional[
- Type[RelationshipProperty.Comparator[Any]]
- ] = None,
- single_parent: bool = False,
- innerjoin: bool = False,
- distinct_target_key: Optional[bool] = None,
- load_on_pending: bool = False,
- query_class: Optional[Type[Query[Any]]] = None,
- info: Optional[_InfoType] = None,
- omit_join: Literal[None, False] = None,
- sync_backref: Optional[bool] = None,
- **kw: Any,
-) -> _RelationshipDeclared[Any]:
- """Provide a relationship between two mapped classes.
-
- This corresponds to a parent-child or associative table relationship.
- The constructed class is an instance of :class:`.Relationship`.
-
- .. seealso::
-
- :ref:`tutorial_orm_related_objects` - tutorial introduction
- to :func:`_orm.relationship` in the :ref:`unified_tutorial`
-
- :ref:`relationship_config_toplevel` - narrative documentation
-
- :param argument:
- This parameter refers to the class that is to be related. It
- accepts several forms, including a direct reference to the target
- class itself, the :class:`_orm.Mapper` instance for the target class,
- a Python callable / lambda that will return a reference to the
- class or :class:`_orm.Mapper` when called, and finally a string
- name for the class, which will be resolved from the
- :class:`_orm.registry` in use in order to locate the class, e.g.::
-
- class SomeClass(Base):
- # ...
-
- related = relationship("RelatedClass")
-
- The :paramref:`_orm.relationship.argument` may also be omitted from the
- :func:`_orm.relationship` construct entirely, and instead placed inside
- a :class:`_orm.Mapped` annotation on the left side, which should
- include a Python collection type if the relationship is expected
- to be a collection, such as::
-
- class SomeClass(Base):
- # ...
-
- related_items: Mapped[List["RelatedItem"]] = relationship()
-
- Or for a many-to-one or one-to-one relationship::
-
- class SomeClass(Base):
- # ...
-
- related_item: Mapped["RelatedItem"] = relationship()
-
- .. seealso::
-
- :ref:`orm_declarative_properties` - further detail
- on relationship configuration when using Declarative.
-
- :param secondary:
- For a many-to-many relationship, specifies the intermediary
- table, and is typically an instance of :class:`_schema.Table`.
- In less common circumstances, the argument may also be specified
- as an :class:`_expression.Alias` construct, or even a
- :class:`_expression.Join` construct.
-
- :paramref:`_orm.relationship.secondary` may
- also be passed as a callable function which is evaluated at
- mapper initialization time. When using Declarative, it may also
- be a string argument noting the name of a :class:`_schema.Table`
- that is
- present in the :class:`_schema.MetaData`
- collection associated with the
- parent-mapped :class:`_schema.Table`.
-
- .. warning:: When passed as a Python-evaluable string, the
- argument is interpreted using Python's ``eval()`` function.
- **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
- See :ref:`declarative_relationship_eval` for details on
- declarative evaluation of :func:`_orm.relationship` arguments.
-
- The :paramref:`_orm.relationship.secondary` keyword argument is
- typically applied in the case where the intermediary
- :class:`_schema.Table`
- is not otherwise expressed in any direct class mapping. If the
- "secondary" table is also explicitly mapped elsewhere (e.g. as in
- :ref:`association_pattern`), one should consider applying the
- :paramref:`_orm.relationship.viewonly` flag so that this
- :func:`_orm.relationship`
- is not used for persistence operations which
- may conflict with those of the association object pattern.
-
- .. seealso::
-
- :ref:`relationships_many_to_many` - Reference example of "many
- to many".
-
- :ref:`self_referential_many_to_many` - Specifics on using
- many-to-many in a self-referential case.
-
- :ref:`declarative_many_to_many` - Additional options when using
- Declarative.
-
- :ref:`association_pattern` - an alternative to
- :paramref:`_orm.relationship.secondary`
- when composing association
- table relationships, allowing additional attributes to be
- specified on the association table.
-
- :ref:`composite_secondary_join` - a lesser-used pattern which
- in some cases can enable complex :func:`_orm.relationship` SQL
- conditions to be used.
-
- :param active_history=False:
- When ``True``, indicates that the "previous" value for a
- many-to-one reference should be loaded when replaced, if
- not already loaded. Normally, history tracking logic for
- simple many-to-ones only needs to be aware of the "new"
- value in order to perform a flush. This flag is available
- for applications that make use of
- :func:`.attributes.get_history` which also need to know
- the "previous" value of the attribute.
-
- :param backref:
- A reference to a string relationship name, or a :func:`_orm.backref`
- construct, which will be used to automatically generate a new
- :func:`_orm.relationship` on the related class, which then refers to this
- one using a bi-directional :paramref:`_orm.relationship.back_populates`
- configuration.
-
- In modern Python, explicit use of :func:`_orm.relationship`
- with :paramref:`_orm.relationship.back_populates` should be preferred,
- as it is more robust in terms of mapper configuration as well as
- more conceptually straightforward. It also integrates with
- new :pep:`484` typing features introduced in SQLAlchemy 2.0 which
- is not possible with dynamically generated attributes.
-
- .. seealso::
-
- :ref:`relationships_backref` - notes on using
- :paramref:`_orm.relationship.backref`
-
- :ref:`tutorial_orm_related_objects` - in the :ref:`unified_tutorial`,
- presents an overview of bi-directional relationship configuration
- and behaviors using :paramref:`_orm.relationship.back_populates`
-
- :func:`.backref` - allows control over :func:`_orm.relationship`
- configuration when using :paramref:`_orm.relationship.backref`.
-
-
- :param back_populates:
- Indicates the name of a :func:`_orm.relationship` on the related
- class that will be synchronized with this one. It is usually
- expected that the :func:`_orm.relationship` on the related class
- also refer to this one. This allows objects on both sides of
- each :func:`_orm.relationship` to synchronize in-Python state
- changes and also provides directives to the :term:`unit of work`
- flush process how changes along these relationships should
- be persisted.
-
- .. seealso::
-
- :ref:`tutorial_orm_related_objects` - in the :ref:`unified_tutorial`,
- presents an overview of bi-directional relationship configuration
- and behaviors.
-
- :ref:`relationship_patterns` - includes many examples of
- :paramref:`_orm.relationship.back_populates`.
-
- :paramref:`_orm.relationship.backref` - legacy form which allows
- more succinct configuration, but does not support explicit typing
-
- :param overlaps:
- A string name or comma-delimited set of names of other relationships
- on either this mapper, a descendant mapper, or a target mapper with
- which this relationship may write to the same foreign keys upon
- persistence. The only effect this has is to eliminate the
- warning that this relationship will conflict with another upon
- persistence. This is used for such relationships that are truly
- capable of conflicting with each other on write, but the application
- will ensure that no such conflicts occur.
-
- .. versionadded:: 1.4
-
- .. seealso::
-
- :ref:`error_qzyx` - usage example
-
- :param cascade:
- A comma-separated list of cascade rules which determines how
- Session operations should be "cascaded" from parent to child.
- This defaults to ``False``, which means the default cascade
- should be used - this default cascade is ``"save-update, merge"``.
-
- The available cascades are ``save-update``, ``merge``,
- ``expunge``, ``delete``, ``delete-orphan``, and ``refresh-expire``.
- An additional option, ``all`` indicates shorthand for
- ``"save-update, merge, refresh-expire,
- expunge, delete"``, and is often used as in ``"all, delete-orphan"``
- to indicate that related objects should follow along with the
- parent object in all cases, and be deleted when de-associated.
-
- .. seealso::
-
- :ref:`unitofwork_cascades` - Full detail on each of the available
- cascade options.
-
- :param cascade_backrefs=False:
- Legacy; this flag is always False.
-
- .. versionchanged:: 2.0 "cascade_backrefs" functionality has been
- removed.
-
- :param collection_class:
- A class or callable that returns a new list-holding object. will
- be used in place of a plain list for storing elements.
-
- .. seealso::
-
- :ref:`custom_collections` - Introductory documentation and
- examples.
-
- :param comparator_factory:
- A class which extends :class:`.Relationship.Comparator`
- which provides custom SQL clause generation for comparison
- operations.
-
- .. seealso::
-
- :class:`.PropComparator` - some detail on redefining comparators
- at this level.
-
- :ref:`custom_comparators` - Brief intro to this feature.
-
-
- :param distinct_target_key=None:
- Indicate if a "subquery" eager load should apply the DISTINCT
- keyword to the innermost SELECT statement. When left as ``None``,
- the DISTINCT keyword will be applied in those cases when the target
- columns do not comprise the full primary key of the target table.
- When set to ``True``, the DISTINCT keyword is applied to the
- innermost SELECT unconditionally.
-
- It may be desirable to set this flag to False when the DISTINCT is
- reducing performance of the innermost subquery beyond that of what
- duplicate innermost rows may be causing.
-
- .. seealso::
-
- :ref:`loading_toplevel` - includes an introduction to subquery
- eager loading.
-
- :param doc:
- Docstring which will be applied to the resulting descriptor.
-
- :param foreign_keys:
-
- A list of columns which are to be used as "foreign key"
- columns, or columns which refer to the value in a remote
- column, within the context of this :func:`_orm.relationship`
- object's :paramref:`_orm.relationship.primaryjoin` condition.
- That is, if the :paramref:`_orm.relationship.primaryjoin`
- condition of this :func:`_orm.relationship` is ``a.id ==
- b.a_id``, and the values in ``b.a_id`` are required to be
- present in ``a.id``, then the "foreign key" column of this
- :func:`_orm.relationship` is ``b.a_id``.
-
- In normal cases, the :paramref:`_orm.relationship.foreign_keys`
- parameter is **not required.** :func:`_orm.relationship` will
- automatically determine which columns in the
- :paramref:`_orm.relationship.primaryjoin` condition are to be
- considered "foreign key" columns based on those
- :class:`_schema.Column` objects that specify
- :class:`_schema.ForeignKey`,
- or are otherwise listed as referencing columns in a
- :class:`_schema.ForeignKeyConstraint` construct.
- :paramref:`_orm.relationship.foreign_keys` is only needed when:
-
- 1. There is more than one way to construct a join from the local
- table to the remote table, as there are multiple foreign key
- references present. Setting ``foreign_keys`` will limit the
- :func:`_orm.relationship`
- to consider just those columns specified
- here as "foreign".
-
- 2. The :class:`_schema.Table` being mapped does not actually have
- :class:`_schema.ForeignKey` or
- :class:`_schema.ForeignKeyConstraint`
- constructs present, often because the table
- was reflected from a database that does not support foreign key
- reflection (MySQL MyISAM).
-
- 3. The :paramref:`_orm.relationship.primaryjoin`
- argument is used to
- construct a non-standard join condition, which makes use of
- columns or expressions that do not normally refer to their
- "parent" column, such as a join condition expressed by a
- complex comparison using a SQL function.
-
- The :func:`_orm.relationship` construct will raise informative
- error messages that suggest the use of the
- :paramref:`_orm.relationship.foreign_keys` parameter when
- presented with an ambiguous condition. In typical cases,
- if :func:`_orm.relationship` doesn't raise any exceptions, the
- :paramref:`_orm.relationship.foreign_keys` parameter is usually
- not needed.
-
- :paramref:`_orm.relationship.foreign_keys` may also be passed as a
- callable function which is evaluated at mapper initialization time,
- and may be passed as a Python-evaluable string when using
- Declarative.
-
- .. warning:: When passed as a Python-evaluable string, the
- argument is interpreted using Python's ``eval()`` function.
- **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
- See :ref:`declarative_relationship_eval` for details on
- declarative evaluation of :func:`_orm.relationship` arguments.
-
- .. seealso::
-
- :ref:`relationship_foreign_keys`
-
- :ref:`relationship_custom_foreign`
-
- :func:`.foreign` - allows direct annotation of the "foreign"
- columns within a :paramref:`_orm.relationship.primaryjoin`
- condition.
-
- :param info: Optional data dictionary which will be populated into the
- :attr:`.MapperProperty.info` attribute of this object.
-
- :param innerjoin=False:
- When ``True``, joined eager loads will use an inner join to join
- against related tables instead of an outer join. The purpose
- of this option is generally one of performance, as inner joins
- generally perform better than outer joins.
-
- This flag can be set to ``True`` when the relationship references an
- object via many-to-one using local foreign keys that are not
- nullable, or when the reference is one-to-one or a collection that
- is guaranteed to have one or at least one entry.
-
- The option supports the same "nested" and "unnested" options as
- that of :paramref:`_orm.joinedload.innerjoin`. See that flag
- for details on nested / unnested behaviors.
-
- .. seealso::
-
- :paramref:`_orm.joinedload.innerjoin` - the option as specified by
- loader option, including detail on nesting behavior.
-
- :ref:`what_kind_of_loading` - Discussion of some details of
- various loader options.
-
-
- :param join_depth:
- When non-``None``, an integer value indicating how many levels
- deep "eager" loaders should join on a self-referring or cyclical
- relationship. The number counts how many times the same Mapper
- shall be present in the loading condition along a particular join
- branch. When left at its default of ``None``, eager loaders
- will stop chaining when they encounter a the same target mapper
- which is already higher up in the chain. This option applies
- both to joined- and subquery- eager loaders.
-
- .. seealso::
-
- :ref:`self_referential_eager_loading` - Introductory documentation
- and examples.
-
- :param lazy='select': specifies
- How the related items should be loaded. Default value is
- ``select``. Values include:
-
- * ``select`` - items should be loaded lazily when the property is
- first accessed, using a separate SELECT statement, or identity map
- fetch for simple many-to-one references.
-
- * ``immediate`` - items should be loaded as the parents are loaded,
- using a separate SELECT statement, or identity map fetch for
- simple many-to-one references.
-
- * ``joined`` - items should be loaded "eagerly" in the same query as
- that of the parent, using a JOIN or LEFT OUTER JOIN. Whether
- the join is "outer" or not is determined by the
- :paramref:`_orm.relationship.innerjoin` parameter.
-
- * ``subquery`` - items should be loaded "eagerly" as the parents are
- loaded, using one additional SQL statement, which issues a JOIN to
- a subquery of the original statement, for each collection
- requested.
-
- * ``selectin`` - items should be loaded "eagerly" as the parents
- are loaded, using one or more additional SQL statements, which
- issues a JOIN to the immediate parent object, specifying primary
- key identifiers using an IN clause.
-
- * ``noload`` - no loading should occur at any time. The related
- collection will remain empty. The ``noload`` strategy is not
- recommended for general use. For a general use "never load"
- approach, see :ref:`write_only_relationship`
-
- * ``raise`` - lazy loading is disallowed; accessing
- the attribute, if its value were not already loaded via eager
- loading, will raise an :exc:`~sqlalchemy.exc.InvalidRequestError`.
- This strategy can be used when objects are to be detached from
- their attached :class:`.Session` after they are loaded.
-
- * ``raise_on_sql`` - lazy loading that emits SQL is disallowed;
- accessing the attribute, if its value were not already loaded via
- eager loading, will raise an
- :exc:`~sqlalchemy.exc.InvalidRequestError`, **if the lazy load
- needs to emit SQL**. If the lazy load can pull the related value
- from the identity map or determine that it should be None, the
- value is loaded. This strategy can be used when objects will
- remain associated with the attached :class:`.Session`, however
- additional SELECT statements should be blocked.
-
- * ``write_only`` - the attribute will be configured with a special
- "virtual collection" that may receive
- :meth:`_orm.WriteOnlyCollection.add` and
- :meth:`_orm.WriteOnlyCollection.remove` commands to add or remove
- individual objects, but will not under any circumstances load or
- iterate the full set of objects from the database directly. Instead,
- methods such as :meth:`_orm.WriteOnlyCollection.select`,
- :meth:`_orm.WriteOnlyCollection.insert`,
- :meth:`_orm.WriteOnlyCollection.update` and
- :meth:`_orm.WriteOnlyCollection.delete` are provided which generate SQL
- constructs that may be used to load and modify rows in bulk. Used for
- large collections that are never appropriate to load at once into
- memory.
-
- The ``write_only`` loader style is configured automatically when
- the :class:`_orm.WriteOnlyMapped` annotation is provided on the
- left hand side within a Declarative mapping. See the section
- :ref:`write_only_relationship` for examples.
-
- .. versionadded:: 2.0
-
- .. seealso::
-
- :ref:`write_only_relationship` - in the :ref:`queryguide_toplevel`
-
- * ``dynamic`` - the attribute will return a pre-configured
- :class:`_query.Query` object for all read
- operations, onto which further filtering operations can be
- applied before iterating the results.
-
- The ``dynamic`` loader style is configured automatically when
- the :class:`_orm.DynamicMapped` annotation is provided on the
- left hand side within a Declarative mapping. See the section
- :ref:`dynamic_relationship` for examples.
-
- .. legacy:: The "dynamic" lazy loader strategy is the legacy form of
- what is now the "write_only" strategy described in the section
- :ref:`write_only_relationship`.
-
- .. seealso::
-
- :ref:`dynamic_relationship` - in the :ref:`queryguide_toplevel`
-
- :ref:`write_only_relationship` - more generally useful approach
- for large collections that should not fully load into memory
-
- * True - a synonym for 'select'
-
- * False - a synonym for 'joined'
-
- * None - a synonym for 'noload'
-
- .. seealso::
-
- :ref:`orm_queryguide_relationship_loaders` - Full documentation on
- relationship loader configuration in the :ref:`queryguide_toplevel`.
-
-
- :param load_on_pending=False:
- Indicates loading behavior for transient or pending parent objects.
-
- When set to ``True``, causes the lazy-loader to
- issue a query for a parent object that is not persistent, meaning it
- has never been flushed. This may take effect for a pending object
- when autoflush is disabled, or for a transient object that has been
- "attached" to a :class:`.Session` but is not part of its pending
- collection.
-
- The :paramref:`_orm.relationship.load_on_pending`
- flag does not improve
- behavior when the ORM is used normally - object references should be
- constructed at the object level, not at the foreign key level, so
- that they are present in an ordinary way before a flush proceeds.
- This flag is not not intended for general use.
-
- .. seealso::
-
- :meth:`.Session.enable_relationship_loading` - this method
- establishes "load on pending" behavior for the whole object, and
- also allows loading on objects that remain transient or
- detached.
-
- :param order_by:
- Indicates the ordering that should be applied when loading these
- items. :paramref:`_orm.relationship.order_by`
- is expected to refer to
- one of the :class:`_schema.Column`
- objects to which the target class is
- mapped, or the attribute itself bound to the target class which
- refers to the column.
-
- :paramref:`_orm.relationship.order_by`
- may also be passed as a callable
- function which is evaluated at mapper initialization time, and may
- be passed as a Python-evaluable string when using Declarative.
-
- .. warning:: When passed as a Python-evaluable string, the
- argument is interpreted using Python's ``eval()`` function.
- **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
- See :ref:`declarative_relationship_eval` for details on
- declarative evaluation of :func:`_orm.relationship` arguments.
-
- :param passive_deletes=False:
- Indicates loading behavior during delete operations.
-
- A value of True indicates that unloaded child items should not
- be loaded during a delete operation on the parent. Normally,
- when a parent item is deleted, all child items are loaded so
- that they can either be marked as deleted, or have their
- foreign key to the parent set to NULL. Marking this flag as
- True usually implies an ON DELETE <CASCADE|SET NULL> rule is in
- place which will handle updating/deleting child rows on the
- database side.
-
- Additionally, setting the flag to the string value 'all' will
- disable the "nulling out" of the child foreign keys, when the parent
- object is deleted and there is no delete or delete-orphan cascade
- enabled. This is typically used when a triggering or error raise
- scenario is in place on the database side. Note that the foreign
- key attributes on in-session child objects will not be changed after
- a flush occurs so this is a very special use-case setting.
- Additionally, the "nulling out" will still occur if the child
- object is de-associated with the parent.
-
- .. seealso::
-
- :ref:`passive_deletes` - Introductory documentation
- and examples.
-
- :param passive_updates=True:
- Indicates the persistence behavior to take when a referenced
- primary key value changes in place, indicating that the referencing
- foreign key columns will also need their value changed.
-
- When True, it is assumed that ``ON UPDATE CASCADE`` is configured on
- the foreign key in the database, and that the database will
- handle propagation of an UPDATE from a source column to
- dependent rows. When False, the SQLAlchemy
- :func:`_orm.relationship`
- construct will attempt to emit its own UPDATE statements to
- modify related targets. However note that SQLAlchemy **cannot**
- emit an UPDATE for more than one level of cascade. Also,
- setting this flag to False is not compatible in the case where
- the database is in fact enforcing referential integrity, unless
- those constraints are explicitly "deferred", if the target backend
- supports it.
-
- It is highly advised that an application which is employing
- mutable primary keys keeps ``passive_updates`` set to True,
- and instead uses the referential integrity features of the database
- itself in order to handle the change efficiently and fully.
-
- .. seealso::
-
- :ref:`passive_updates` - Introductory documentation and
- examples.
-
- :paramref:`.mapper.passive_updates` - a similar flag which
- takes effect for joined-table inheritance mappings.
-
- :param post_update:
- This indicates that the relationship should be handled by a
- second UPDATE statement after an INSERT or before a
- DELETE. This flag is used to handle saving bi-directional
- dependencies between two individual rows (i.e. each row
- references the other), where it would otherwise be impossible to
- INSERT or DELETE both rows fully since one row exists before the
- other. Use this flag when a particular mapping arrangement will
- incur two rows that are dependent on each other, such as a table
- that has a one-to-many relationship to a set of child rows, and
- also has a column that references a single child row within that
- list (i.e. both tables contain a foreign key to each other). If
- a flush operation returns an error that a "cyclical
- dependency" was detected, this is a cue that you might want to
- use :paramref:`_orm.relationship.post_update` to "break" the cycle.
-
- .. seealso::
-
- :ref:`post_update` - Introductory documentation and examples.
-
- :param primaryjoin:
- A SQL expression that will be used as the primary
- join of the child object against the parent object, or in a
- many-to-many relationship the join of the parent object to the
- association table. By default, this value is computed based on the
- foreign key relationships of the parent and child tables (or
- association table).
-
- :paramref:`_orm.relationship.primaryjoin` may also be passed as a
- callable function which is evaluated at mapper initialization time,
- and may be passed as a Python-evaluable string when using
- Declarative.
-
- .. warning:: When passed as a Python-evaluable string, the
- argument is interpreted using Python's ``eval()`` function.
- **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
- See :ref:`declarative_relationship_eval` for details on
- declarative evaluation of :func:`_orm.relationship` arguments.
-
- .. seealso::
-
- :ref:`relationship_primaryjoin`
-
- :param remote_side:
- Used for self-referential relationships, indicates the column or
- list of columns that form the "remote side" of the relationship.
-
- :paramref:`_orm.relationship.remote_side` may also be passed as a
- callable function which is evaluated at mapper initialization time,
- and may be passed as a Python-evaluable string when using
- Declarative.
-
- .. warning:: When passed as a Python-evaluable string, the
- argument is interpreted using Python's ``eval()`` function.
- **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
- See :ref:`declarative_relationship_eval` for details on
- declarative evaluation of :func:`_orm.relationship` arguments.
-
- .. seealso::
-
- :ref:`self_referential` - in-depth explanation of how
- :paramref:`_orm.relationship.remote_side`
- is used to configure self-referential relationships.
-
- :func:`.remote` - an annotation function that accomplishes the
- same purpose as :paramref:`_orm.relationship.remote_side`,
- typically
- when a custom :paramref:`_orm.relationship.primaryjoin` condition
- is used.
-
- :param query_class:
- A :class:`_query.Query`
- subclass that will be used internally by the
- ``AppenderQuery`` returned by a "dynamic" relationship, that
- is, a relationship that specifies ``lazy="dynamic"`` or was
- otherwise constructed using the :func:`_orm.dynamic_loader`
- function.
-
- .. seealso::
-
- :ref:`dynamic_relationship` - Introduction to "dynamic"
- relationship loaders.
-
- :param secondaryjoin:
- A SQL expression that will be used as the join of
- an association table to the child object. By default, this value is
- computed based on the foreign key relationships of the association
- and child tables.
-
- :paramref:`_orm.relationship.secondaryjoin` may also be passed as a
- callable function which is evaluated at mapper initialization time,
- and may be passed as a Python-evaluable string when using
- Declarative.
-
- .. warning:: When passed as a Python-evaluable string, the
- argument is interpreted using Python's ``eval()`` function.
- **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
- See :ref:`declarative_relationship_eval` for details on
- declarative evaluation of :func:`_orm.relationship` arguments.
-
- .. seealso::
-
- :ref:`relationship_primaryjoin`
-
- :param single_parent:
- When True, installs a validator which will prevent objects
- from being associated with more than one parent at a time.
- This is used for many-to-one or many-to-many relationships that
- should be treated either as one-to-one or one-to-many. Its usage
- is optional, except for :func:`_orm.relationship` constructs which
- are many-to-one or many-to-many and also
- specify the ``delete-orphan`` cascade option. The
- :func:`_orm.relationship` construct itself will raise an error
- instructing when this option is required.
-
- .. seealso::
-
- :ref:`unitofwork_cascades` - includes detail on when the
- :paramref:`_orm.relationship.single_parent`
- flag may be appropriate.
-
- :param uselist:
- A boolean that indicates if this property should be loaded as a
- list or a scalar. In most cases, this value is determined
- automatically by :func:`_orm.relationship` at mapper configuration
- time. When using explicit :class:`_orm.Mapped` annotations,
- :paramref:`_orm.relationship.uselist` may be derived from the
- whether or not the annotation within :class:`_orm.Mapped` contains
- a collection class.
- Otherwise, :paramref:`_orm.relationship.uselist` may be derived from
- the type and direction
- of the relationship - one to many forms a list, many to one
- forms a scalar, many to many is a list. If a scalar is desired
- where normally a list would be present, such as a bi-directional
- one-to-one relationship, use an appropriate :class:`_orm.Mapped`
- annotation or set :paramref:`_orm.relationship.uselist` to False.
-
- The :paramref:`_orm.relationship.uselist`
- flag is also available on an
- existing :func:`_orm.relationship`
- construct as a read-only attribute,
- which can be used to determine if this :func:`_orm.relationship`
- deals
- with collections or scalar attributes::
-
- >>> User.addresses.property.uselist
- True
-
- .. seealso::
-
- :ref:`relationships_one_to_one` - Introduction to the "one to
- one" relationship pattern, which is typically when an alternate
- setting for :paramref:`_orm.relationship.uselist` is involved.
-
- :param viewonly=False:
- When set to ``True``, the relationship is used only for loading
- objects, and not for any persistence operation. A
- :func:`_orm.relationship` which specifies
- :paramref:`_orm.relationship.viewonly` can work
- with a wider range of SQL operations within the
- :paramref:`_orm.relationship.primaryjoin` condition, including
- operations that feature the use of a variety of comparison operators
- as well as SQL functions such as :func:`_expression.cast`. The
- :paramref:`_orm.relationship.viewonly`
- flag is also of general use when defining any kind of
- :func:`_orm.relationship` that doesn't represent
- the full set of related objects, to prevent modifications of the
- collection from resulting in persistence operations.
-
- .. seealso::
-
- :ref:`relationship_viewonly_notes` - more details on best practices
- when using :paramref:`_orm.relationship.viewonly`.
-
- :param sync_backref:
- A boolean that enables the events used to synchronize the in-Python
- attributes when this relationship is target of either
- :paramref:`_orm.relationship.backref` or
- :paramref:`_orm.relationship.back_populates`.
-
- Defaults to ``None``, which indicates that an automatic value should
- be selected based on the value of the
- :paramref:`_orm.relationship.viewonly` flag. When left at its
- default, changes in state will be back-populated only if neither
- sides of a relationship is viewonly.
-
- .. versionadded:: 1.3.17
-
- .. versionchanged:: 1.4 - A relationship that specifies
- :paramref:`_orm.relationship.viewonly` automatically implies
- that :paramref:`_orm.relationship.sync_backref` is ``False``.
-
- .. seealso::
-
- :paramref:`_orm.relationship.viewonly`
-
- :param omit_join:
- Allows manual control over the "selectin" automatic join
- optimization. Set to ``False`` to disable the "omit join" feature
- added in SQLAlchemy 1.3; or leave as ``None`` to leave automatic
- optimization in place.
-
- .. note:: This flag may only be set to ``False``. It is not
- necessary to set it to ``True`` as the "omit_join" optimization is
- automatically detected; if it is not detected, then the
- optimization is not supported.
-
- .. versionchanged:: 1.3.11 setting ``omit_join`` to True will now
- emit a warning as this was not the intended use of this flag.
-
- .. versionadded:: 1.3
-
- :param init: Specific to :ref:`orm_declarative_native_dataclasses`,
- specifies if the mapped attribute should be part of the ``__init__()``
- method as generated by the dataclass process.
- :param repr: Specific to :ref:`orm_declarative_native_dataclasses`,
- specifies if the mapped attribute should be part of the ``__repr__()``
- method as generated by the dataclass process.
- :param default_factory: Specific to
- :ref:`orm_declarative_native_dataclasses`,
- specifies a default-value generation function that will take place
- as part of the ``__init__()``
- method as generated by the dataclass process.
- :param compare: Specific to
- :ref:`orm_declarative_native_dataclasses`, indicates if this field
- should be included in comparison operations when generating the
- ``__eq__()`` and ``__ne__()`` methods for the mapped class.
-
- .. versionadded:: 2.0.0b4
-
- :param kw_only: Specific to
- :ref:`orm_declarative_native_dataclasses`, indicates if this field
- should be marked as keyword-only when generating the ``__init__()``.
-
-
- """
-
- return _RelationshipDeclared(
- argument,
- secondary=secondary,
- uselist=uselist,
- collection_class=collection_class,
- primaryjoin=primaryjoin,
- secondaryjoin=secondaryjoin,
- back_populates=back_populates,
- order_by=order_by,
- backref=backref,
- overlaps=overlaps,
- post_update=post_update,
- cascade=cascade,
- viewonly=viewonly,
- attribute_options=_AttributeOptions(
- init, repr, default, default_factory, compare, kw_only
- ),
- lazy=lazy,
- passive_deletes=passive_deletes,
- passive_updates=passive_updates,
- active_history=active_history,
- enable_typechecks=enable_typechecks,
- foreign_keys=foreign_keys,
- remote_side=remote_side,
- join_depth=join_depth,
- comparator_factory=comparator_factory,
- single_parent=single_parent,
- innerjoin=innerjoin,
- distinct_target_key=distinct_target_key,
- load_on_pending=load_on_pending,
- query_class=query_class,
- info=info,
- omit_join=omit_join,
- sync_backref=sync_backref,
- **kw,
- )
-
-
-def synonym(
- name: str,
- *,
- map_column: Optional[bool] = None,
- descriptor: Optional[Any] = None,
- comparator_factory: Optional[Type[PropComparator[_T]]] = None,
- init: Union[_NoArg, bool] = _NoArg.NO_ARG,
- repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
- default: Union[_NoArg, _T] = _NoArg.NO_ARG,
- default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
- compare: Union[_NoArg, bool] = _NoArg.NO_ARG,
- kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
- info: Optional[_InfoType] = None,
- doc: Optional[str] = None,
-) -> Synonym[Any]:
- """Denote an attribute name as a synonym to a mapped property,
- in that the attribute will mirror the value and expression behavior
- of another attribute.
-
- e.g.::
-
- class MyClass(Base):
- __tablename__ = 'my_table'
-
- id = Column(Integer, primary_key=True)
- job_status = Column(String(50))
-
- status = synonym("job_status")
-
-
- :param name: the name of the existing mapped property. This
- can refer to the string name ORM-mapped attribute
- configured on the class, including column-bound attributes
- and relationships.
-
- :param descriptor: a Python :term:`descriptor` that will be used
- as a getter (and potentially a setter) when this attribute is
- accessed at the instance level.
-
- :param map_column: **For classical mappings and mappings against
- an existing Table object only**. if ``True``, the :func:`.synonym`
- construct will locate the :class:`_schema.Column`
- object upon the mapped
- table that would normally be associated with the attribute name of
- this synonym, and produce a new :class:`.ColumnProperty` that instead
- maps this :class:`_schema.Column`
- to the alternate name given as the "name"
- argument of the synonym; in this way, the usual step of redefining
- the mapping of the :class:`_schema.Column`
- to be under a different name is
- unnecessary. This is usually intended to be used when a
- :class:`_schema.Column`
- is to be replaced with an attribute that also uses a
- descriptor, that is, in conjunction with the
- :paramref:`.synonym.descriptor` parameter::
-
- my_table = Table(
- "my_table", metadata,
- Column('id', Integer, primary_key=True),
- Column('job_status', String(50))
- )
-
- class MyClass:
- @property
- def _job_status_descriptor(self):
- return "Status: %s" % self._job_status
-
-
- mapper(
- MyClass, my_table, properties={
- "job_status": synonym(
- "_job_status", map_column=True,
- descriptor=MyClass._job_status_descriptor)
- }
- )
-
- Above, the attribute named ``_job_status`` is automatically
- mapped to the ``job_status`` column::
-
- >>> j1 = MyClass()
- >>> j1._job_status = "employed"
- >>> j1.job_status
- Status: employed
-
- When using Declarative, in order to provide a descriptor in
- conjunction with a synonym, use the
- :func:`sqlalchemy.ext.declarative.synonym_for` helper. However,
- note that the :ref:`hybrid properties <mapper_hybrids>` feature
- should usually be preferred, particularly when redefining attribute
- behavior.
-
- :param info: Optional data dictionary which will be populated into the
- :attr:`.InspectionAttr.info` attribute of this object.
-
- :param comparator_factory: A subclass of :class:`.PropComparator`
- that will provide custom comparison behavior at the SQL expression
- level.
-
- .. note::
-
- For the use case of providing an attribute which redefines both
- Python-level and SQL-expression level behavior of an attribute,
- please refer to the Hybrid attribute introduced at
- :ref:`mapper_hybrids` for a more effective technique.
-
- .. seealso::
-
- :ref:`synonyms` - Overview of synonyms
-
- :func:`.synonym_for` - a helper oriented towards Declarative
-
- :ref:`mapper_hybrids` - The Hybrid Attribute extension provides an
- updated approach to augmenting attribute behavior more flexibly
- than can be achieved with synonyms.
-
- """
- return Synonym(
- name,
- map_column=map_column,
- descriptor=descriptor,
- comparator_factory=comparator_factory,
- attribute_options=_AttributeOptions(
- init, repr, default, default_factory, compare, kw_only
- ),
- doc=doc,
- info=info,
- )
-
-
-def create_session(
- bind: Optional[_SessionBind] = None, **kwargs: Any
-) -> Session:
- r"""Create a new :class:`.Session`
- with no automation enabled by default.
-
- This function is used primarily for testing. The usual
- route to :class:`.Session` creation is via its constructor
- or the :func:`.sessionmaker` function.
-
- :param bind: optional, a single Connectable to use for all
- database access in the created
- :class:`~sqlalchemy.orm.session.Session`.
-
- :param \*\*kwargs: optional, passed through to the
- :class:`.Session` constructor.
-
- :returns: an :class:`~sqlalchemy.orm.session.Session` instance
-
- The defaults of create_session() are the opposite of that of
- :func:`sessionmaker`; ``autoflush`` and ``expire_on_commit`` are
- False.
-
- Usage::
-
- >>> from sqlalchemy.orm import create_session
- >>> session = create_session()
-
- It is recommended to use :func:`sessionmaker` instead of
- create_session().
-
- """
-
- kwargs.setdefault("autoflush", False)
- kwargs.setdefault("expire_on_commit", False)
- return Session(bind=bind, **kwargs)
-
-
-def _mapper_fn(*arg: Any, **kw: Any) -> NoReturn:
- """Placeholder for the now-removed ``mapper()`` function.
-
- Classical mappings should be performed using the
- :meth:`_orm.registry.map_imperatively` method.
-
- This symbol remains in SQLAlchemy 2.0 to suit the deprecated use case
- of using the ``mapper()`` function as a target for ORM event listeners,
- which failed to be marked as deprecated in the 1.4 series.
-
- Global ORM mapper listeners should instead use the :class:`_orm.Mapper`
- class as the target.
-
- .. versionchanged:: 2.0 The ``mapper()`` function was removed; the
- symbol remains temporarily as a placeholder for the event listening
- use case.
-
- """
- raise InvalidRequestError(
- "The 'sqlalchemy.orm.mapper()' function is removed as of "
- "SQLAlchemy 2.0. Use the "
- "'sqlalchemy.orm.registry.map_imperatively()` "
- "method of the ``sqlalchemy.orm.registry`` class to perform "
- "classical mapping."
- )
-
-
-def dynamic_loader(
- argument: Optional[_RelationshipArgumentType[Any]] = None, **kw: Any
-) -> RelationshipProperty[Any]:
- """Construct a dynamically-loading mapper property.
-
- This is essentially the same as
- using the ``lazy='dynamic'`` argument with :func:`relationship`::
-
- dynamic_loader(SomeClass)
-
- # is the same as
-
- relationship(SomeClass, lazy="dynamic")
-
- See the section :ref:`dynamic_relationship` for more details
- on dynamic loading.
-
- """
- kw["lazy"] = "dynamic"
- return relationship(argument, **kw)
-
-
-def backref(name: str, **kwargs: Any) -> ORMBackrefArgument:
- """When using the :paramref:`_orm.relationship.backref` parameter,
- provides specific parameters to be used when the new
- :func:`_orm.relationship` is generated.
-
- E.g.::
-
- 'items':relationship(
- SomeItem, backref=backref('parent', lazy='subquery'))
-
- The :paramref:`_orm.relationship.backref` parameter is generally
- considered to be legacy; for modern applications, using
- explicit :func:`_orm.relationship` constructs linked together using
- the :paramref:`_orm.relationship.back_populates` parameter should be
- preferred.
-
- .. seealso::
-
- :ref:`relationships_backref` - background on backrefs
-
- """
-
- return (name, kwargs)
-
-
-def deferred(
- column: _ORMColumnExprArgument[_T],
- *additional_columns: _ORMColumnExprArgument[Any],
- group: Optional[str] = None,
- raiseload: bool = False,
- comparator_factory: Optional[Type[PropComparator[_T]]] = None,
- init: Union[_NoArg, bool] = _NoArg.NO_ARG,
- repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
- default: Optional[Any] = _NoArg.NO_ARG,
- default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
- compare: Union[_NoArg, bool] = _NoArg.NO_ARG,
- kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
- active_history: bool = False,
- expire_on_flush: bool = True,
- info: Optional[_InfoType] = None,
- doc: Optional[str] = None,
-) -> MappedSQLExpression[_T]:
- r"""Indicate a column-based mapped attribute that by default will
- not load unless accessed.
-
- When using :func:`_orm.mapped_column`, the same functionality as
- that of :func:`_orm.deferred` construct is provided by using the
- :paramref:`_orm.mapped_column.deferred` parameter.
-
- :param \*columns: columns to be mapped. This is typically a single
- :class:`_schema.Column` object,
- however a collection is supported in order
- to support multiple columns mapped under the same attribute.
-
- :param raiseload: boolean, if True, indicates an exception should be raised
- if the load operation is to take place.
-
- .. versionadded:: 1.4
-
-
- Additional arguments are the same as that of :func:`_orm.column_property`.
-
- .. seealso::
-
- :ref:`orm_queryguide_deferred_imperative`
-
- """
- return MappedSQLExpression(
- column,
- *additional_columns,
- attribute_options=_AttributeOptions(
- init, repr, default, default_factory, compare, kw_only
- ),
- group=group,
- deferred=True,
- raiseload=raiseload,
- comparator_factory=comparator_factory,
- active_history=active_history,
- expire_on_flush=expire_on_flush,
- info=info,
- doc=doc,
- )
-
-
-def query_expression(
- default_expr: _ORMColumnExprArgument[_T] = sql.null(),
- *,
- repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
- compare: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
- expire_on_flush: bool = True,
- info: Optional[_InfoType] = None,
- doc: Optional[str] = None,
-) -> MappedSQLExpression[_T]:
- """Indicate an attribute that populates from a query-time SQL expression.
-
- :param default_expr: Optional SQL expression object that will be used in
- all cases if not assigned later with :func:`_orm.with_expression`.
-
- .. versionadded:: 1.2
-
- .. seealso::
-
- :ref:`orm_queryguide_with_expression` - background and usage examples
-
- """
- prop = MappedSQLExpression(
- default_expr,
- attribute_options=_AttributeOptions(
- False,
- repr,
- _NoArg.NO_ARG,
- _NoArg.NO_ARG,
- compare,
- _NoArg.NO_ARG,
- ),
- expire_on_flush=expire_on_flush,
- info=info,
- doc=doc,
- _assume_readonly_dc_attributes=True,
- )
-
- prop.strategy_key = (("query_expression", True),)
- return prop
-
-
-def clear_mappers() -> None:
- """Remove all mappers from all classes.
-
- .. versionchanged:: 1.4 This function now locates all
- :class:`_orm.registry` objects and calls upon the
- :meth:`_orm.registry.dispose` method of each.
-
- This function removes all instrumentation from classes and disposes
- of their associated mappers. Once called, the classes are unmapped
- and can be later re-mapped with new mappers.
-
- :func:`.clear_mappers` is *not* for normal use, as there is literally no
- valid usage for it outside of very specific testing scenarios. Normally,
- mappers are permanent structural components of user-defined classes, and
- are never discarded independently of their class. If a mapped class
- itself is garbage collected, its mapper is automatically disposed of as
- well. As such, :func:`.clear_mappers` is only for usage in test suites
- that re-use the same classes with different mappings, which is itself an
- extremely rare use case - the only such use case is in fact SQLAlchemy's
- own test suite, and possibly the test suites of other ORM extension
- libraries which intend to test various combinations of mapper construction
- upon a fixed set of classes.
-
- """
-
- mapperlib._dispose_registries(mapperlib._all_registries(), False)
-
-
-# I would really like a way to get the Type[] here that shows up
-# in a different way in typing tools, however there is no current method
-# that is accepted by mypy (subclass of Type[_O] works in pylance, rejected
-# by mypy).
-AliasedType = Annotated[Type[_O], "aliased"]
-
-
-@overload
-def aliased(
- element: Type[_O],
- alias: Optional[FromClause] = None,
- name: Optional[str] = None,
- flat: bool = False,
- adapt_on_names: bool = False,
-) -> AliasedType[_O]: ...
-
-
-@overload
-def aliased(
- element: Union[AliasedClass[_O], Mapper[_O], AliasedInsp[_O]],
- alias: Optional[FromClause] = None,
- name: Optional[str] = None,
- flat: bool = False,
- adapt_on_names: bool = False,
-) -> AliasedClass[_O]: ...
-
-
-@overload
-def aliased(
- element: FromClause,
- alias: None = None,
- name: Optional[str] = None,
- flat: bool = False,
- adapt_on_names: bool = False,
-) -> FromClause: ...
-
-
-def aliased(
- element: Union[_EntityType[_O], FromClause],
- alias: Optional[FromClause] = None,
- name: Optional[str] = None,
- flat: bool = False,
- adapt_on_names: bool = False,
-) -> Union[AliasedClass[_O], FromClause, AliasedType[_O]]:
- """Produce an alias of the given element, usually an :class:`.AliasedClass`
- instance.
-
- E.g.::
-
- my_alias = aliased(MyClass)
-
- stmt = select(MyClass, my_alias).filter(MyClass.id > my_alias.id)
- result = session.execute(stmt)
-
- The :func:`.aliased` function is used to create an ad-hoc mapping of a
- mapped class to a new selectable. By default, a selectable is generated
- from the normally mapped selectable (typically a :class:`_schema.Table`
- ) using the
- :meth:`_expression.FromClause.alias` method. However, :func:`.aliased`
- can also be
- used to link the class to a new :func:`_expression.select` statement.
- Also, the :func:`.with_polymorphic` function is a variant of
- :func:`.aliased` that is intended to specify a so-called "polymorphic
- selectable", that corresponds to the union of several joined-inheritance
- subclasses at once.
-
- For convenience, the :func:`.aliased` function also accepts plain
- :class:`_expression.FromClause` constructs, such as a
- :class:`_schema.Table` or
- :func:`_expression.select` construct. In those cases, the
- :meth:`_expression.FromClause.alias`
- method is called on the object and the new
- :class:`_expression.Alias` object returned. The returned
- :class:`_expression.Alias` is not
- ORM-mapped in this case.
-
- .. seealso::
-
- :ref:`tutorial_orm_entity_aliases` - in the :ref:`unified_tutorial`
-
- :ref:`orm_queryguide_orm_aliases` - in the :ref:`queryguide_toplevel`
-
- :param element: element to be aliased. Is normally a mapped class,
- but for convenience can also be a :class:`_expression.FromClause`
- element.
-
- :param alias: Optional selectable unit to map the element to. This is
- usually used to link the object to a subquery, and should be an aliased
- select construct as one would produce from the
- :meth:`_query.Query.subquery` method or
- the :meth:`_expression.Select.subquery` or
- :meth:`_expression.Select.alias` methods of the :func:`_expression.select`
- construct.
-
- :param name: optional string name to use for the alias, if not specified
- by the ``alias`` parameter. The name, among other things, forms the
- attribute name that will be accessible via tuples returned by a
- :class:`_query.Query` object. Not supported when creating aliases
- of :class:`_sql.Join` objects.
-
- :param flat: Boolean, will be passed through to the
- :meth:`_expression.FromClause.alias` call so that aliases of
- :class:`_expression.Join` objects will alias the individual tables
- inside the join, rather than creating a subquery. This is generally
- supported by all modern databases with regards to right-nested joins
- and generally produces more efficient queries.
-
- :param adapt_on_names: if True, more liberal "matching" will be used when
- mapping the mapped columns of the ORM entity to those of the
- given selectable - a name-based match will be performed if the
- given selectable doesn't otherwise have a column that corresponds
- to one on the entity. The use case for this is when associating
- an entity with some derived selectable such as one that uses
- aggregate functions::
-
- class UnitPrice(Base):
- __tablename__ = 'unit_price'
- ...
- unit_id = Column(Integer)
- price = Column(Numeric)
-
- aggregated_unit_price = Session.query(
- func.sum(UnitPrice.price).label('price')
- ).group_by(UnitPrice.unit_id).subquery()
-
- aggregated_unit_price = aliased(UnitPrice,
- alias=aggregated_unit_price, adapt_on_names=True)
-
- Above, functions on ``aggregated_unit_price`` which refer to
- ``.price`` will return the
- ``func.sum(UnitPrice.price).label('price')`` column, as it is
- matched on the name "price". Ordinarily, the "price" function
- wouldn't have any "column correspondence" to the actual
- ``UnitPrice.price`` column as it is not a proxy of the original.
-
- """
- return AliasedInsp._alias_factory(
- element,
- alias=alias,
- name=name,
- flat=flat,
- adapt_on_names=adapt_on_names,
- )
-
-
-def with_polymorphic(
- base: Union[Type[_O], Mapper[_O]],
- classes: Union[Literal["*"], Iterable[Type[Any]]],
- selectable: Union[Literal[False, None], FromClause] = False,
- flat: bool = False,
- polymorphic_on: Optional[ColumnElement[Any]] = None,
- aliased: bool = False,
- innerjoin: bool = False,
- adapt_on_names: bool = False,
- _use_mapper_path: bool = False,
-) -> AliasedClass[_O]:
- """Produce an :class:`.AliasedClass` construct which specifies
- columns for descendant mappers of the given base.
-
- Using this method will ensure that each descendant mapper's
- tables are included in the FROM clause, and will allow filter()
- criterion to be used against those tables. The resulting
- instances will also have those columns already loaded so that
- no "post fetch" of those columns will be required.
-
- .. seealso::
-
- :ref:`with_polymorphic` - full discussion of
- :func:`_orm.with_polymorphic`.
-
- :param base: Base class to be aliased.
-
- :param classes: a single class or mapper, or list of
- class/mappers, which inherit from the base class.
- Alternatively, it may also be the string ``'*'``, in which case
- all descending mapped classes will be added to the FROM clause.
-
- :param aliased: when True, the selectable will be aliased. For a
- JOIN, this means the JOIN will be SELECTed from inside of a subquery
- unless the :paramref:`_orm.with_polymorphic.flat` flag is set to
- True, which is recommended for simpler use cases.
-
- :param flat: Boolean, will be passed through to the
- :meth:`_expression.FromClause.alias` call so that aliases of
- :class:`_expression.Join` objects will alias the individual tables
- inside the join, rather than creating a subquery. This is generally
- supported by all modern databases with regards to right-nested joins
- and generally produces more efficient queries. Setting this flag is
- recommended as long as the resulting SQL is functional.
-
- :param selectable: a table or subquery that will
- be used in place of the generated FROM clause. This argument is
- required if any of the desired classes use concrete table
- inheritance, since SQLAlchemy currently cannot generate UNIONs
- among tables automatically. If used, the ``selectable`` argument
- must represent the full set of tables and columns mapped by every
- mapped class. Otherwise, the unaccounted mapped columns will
- result in their table being appended directly to the FROM clause
- which will usually lead to incorrect results.
-
- When left at its default value of ``False``, the polymorphic
- selectable assigned to the base mapper is used for selecting rows.
- However, it may also be passed as ``None``, which will bypass the
- configured polymorphic selectable and instead construct an ad-hoc
- selectable for the target classes given; for joined table inheritance
- this will be a join that includes all target mappers and their
- subclasses.
-
- :param polymorphic_on: a column to be used as the "discriminator"
- column for the given selectable. If not given, the polymorphic_on
- attribute of the base classes' mapper will be used, if any. This
- is useful for mappings that don't have polymorphic loading
- behavior by default.
-
- :param innerjoin: if True, an INNER JOIN will be used. This should
- only be specified if querying for one specific subtype only
-
- :param adapt_on_names: Passes through the
- :paramref:`_orm.aliased.adapt_on_names`
- parameter to the aliased object. This may be useful in situations where
- the given selectable is not directly related to the existing mapped
- selectable.
-
- .. versionadded:: 1.4.33
-
- """
- return AliasedInsp._with_polymorphic_factory(
- base,
- classes,
- selectable=selectable,
- flat=flat,
- polymorphic_on=polymorphic_on,
- adapt_on_names=adapt_on_names,
- aliased=aliased,
- innerjoin=innerjoin,
- _use_mapper_path=_use_mapper_path,
- )
-
-
-def join(
- left: _FromClauseArgument,
- right: _FromClauseArgument,
- onclause: Optional[_OnClauseArgument] = None,
- isouter: bool = False,
- full: bool = False,
-) -> _ORMJoin:
- r"""Produce an inner join between left and right clauses.
-
- :func:`_orm.join` is an extension to the core join interface
- provided by :func:`_expression.join()`, where the
- left and right selectable may be not only core selectable
- objects such as :class:`_schema.Table`, but also mapped classes or
- :class:`.AliasedClass` instances. The "on" clause can
- be a SQL expression or an ORM mapped attribute
- referencing a configured :func:`_orm.relationship`.
-
- :func:`_orm.join` is not commonly needed in modern usage,
- as its functionality is encapsulated within that of the
- :meth:`_sql.Select.join` and :meth:`_query.Query.join`
- methods. which feature a
- significant amount of automation beyond :func:`_orm.join`
- by itself. Explicit use of :func:`_orm.join`
- with ORM-enabled SELECT statements involves use of the
- :meth:`_sql.Select.select_from` method, as in::
-
- from sqlalchemy.orm import join
- stmt = select(User).\
- select_from(join(User, Address, User.addresses)).\
- filter(Address.email_address=='foo@bar.com')
-
- In modern SQLAlchemy the above join can be written more
- succinctly as::
-
- stmt = select(User).\
- join(User.addresses).\
- filter(Address.email_address=='foo@bar.com')
-
- .. warning:: using :func:`_orm.join` directly may not work properly
- with modern ORM options such as :func:`_orm.with_loader_criteria`.
- It is strongly recommended to use the idiomatic join patterns
- provided by methods such as :meth:`.Select.join` and
- :meth:`.Select.join_from` when creating ORM joins.
-
- .. seealso::
-
- :ref:`orm_queryguide_joins` - in the :ref:`queryguide_toplevel` for
- background on idiomatic ORM join patterns
-
- """
- return _ORMJoin(left, right, onclause, isouter, full)
-
-
-def outerjoin(
- left: _FromClauseArgument,
- right: _FromClauseArgument,
- onclause: Optional[_OnClauseArgument] = None,
- full: bool = False,
-) -> _ORMJoin:
- """Produce a left outer join between left and right clauses.
-
- This is the "outer join" version of the :func:`_orm.join` function,
- featuring the same behavior except that an OUTER JOIN is generated.
- See that function's documentation for other usage details.
-
- """
- return _ORMJoin(left, right, onclause, True, full)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/_typing.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/_typing.py
deleted file mode 100644
index f8ac059..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/_typing.py
+++ /dev/null
@@ -1,179 +0,0 @@
-# orm/_typing.py
-# Copyright (C) 2022-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
-
-from __future__ import annotations
-
-import operator
-from typing import Any
-from typing import Dict
-from typing import Mapping
-from typing import Optional
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from ..engine.interfaces import _CoreKnownExecutionOptions
-from ..sql import roles
-from ..sql._orm_types import DMLStrategyArgument as DMLStrategyArgument
-from ..sql._orm_types import (
- SynchronizeSessionArgument as SynchronizeSessionArgument,
-)
-from ..sql._typing import _HasClauseElement
-from ..sql.elements import ColumnElement
-from ..util.typing import Protocol
-from ..util.typing import TypeGuard
-
-if TYPE_CHECKING:
- from .attributes import AttributeImpl
- from .attributes import CollectionAttributeImpl
- from .attributes import HasCollectionAdapter
- from .attributes import QueryableAttribute
- from .base import PassiveFlag
- from .decl_api import registry as _registry_type
- from .interfaces import InspectionAttr
- from .interfaces import MapperProperty
- from .interfaces import ORMOption
- from .interfaces import UserDefinedOption
- from .mapper import Mapper
- from .relationships import RelationshipProperty
- from .state import InstanceState
- from .util import AliasedClass
- from .util import AliasedInsp
- from ..sql._typing import _CE
- from ..sql.base import ExecutableOption
-
-_T = TypeVar("_T", bound=Any)
-
-
-_T_co = TypeVar("_T_co", bound=Any, covariant=True)
-
-_O = TypeVar("_O", bound=object)
-"""The 'ORM mapped object' type.
-
-"""
-
-
-if TYPE_CHECKING:
- _RegistryType = _registry_type
-
-_InternalEntityType = Union["Mapper[_T]", "AliasedInsp[_T]"]
-
-_ExternalEntityType = Union[Type[_T], "AliasedClass[_T]"]
-
-_EntityType = Union[
- Type[_T], "AliasedClass[_T]", "Mapper[_T]", "AliasedInsp[_T]"
-]
-
-
-_ClassDict = Mapping[str, Any]
-_InstanceDict = Dict[str, Any]
-
-_IdentityKeyType = Tuple[Type[_T], Tuple[Any, ...], Optional[Any]]
-
-_ORMColumnExprArgument = Union[
- ColumnElement[_T],
- _HasClauseElement[_T],
- roles.ExpressionElementRole[_T],
-]
-
-
-_ORMCOLEXPR = TypeVar("_ORMCOLEXPR", bound=ColumnElement[Any])
-
-
-class _OrmKnownExecutionOptions(_CoreKnownExecutionOptions, total=False):
- populate_existing: bool
- autoflush: bool
- synchronize_session: SynchronizeSessionArgument
- dml_strategy: DMLStrategyArgument
- is_delete_using: bool
- is_update_from: bool
- render_nulls: bool
-
-
-OrmExecuteOptionsParameter = Union[
- _OrmKnownExecutionOptions, Mapping[str, Any]
-]
-
-
-class _ORMAdapterProto(Protocol):
- """protocol for the :class:`.AliasedInsp._orm_adapt_element` method
- which is a synonym for :class:`.AliasedInsp._adapt_element`.
-
-
- """
-
- def __call__(self, obj: _CE, key: Optional[str] = None) -> _CE: ...
-
-
-class _LoaderCallable(Protocol):
- def __call__(
- self, state: InstanceState[Any], passive: PassiveFlag
- ) -> Any: ...
-
-
-def is_orm_option(
- opt: ExecutableOption,
-) -> TypeGuard[ORMOption]:
- return not opt._is_core
-
-
-def is_user_defined_option(
- opt: ExecutableOption,
-) -> TypeGuard[UserDefinedOption]:
- return not opt._is_core and opt._is_user_defined # type: ignore
-
-
-def is_composite_class(obj: Any) -> bool:
- # inlining is_dataclass(obj)
- return hasattr(obj, "__composite_values__") or hasattr(
- obj, "__dataclass_fields__"
- )
-
-
-if TYPE_CHECKING:
-
- def insp_is_mapper_property(
- obj: Any,
- ) -> TypeGuard[MapperProperty[Any]]: ...
-
- def insp_is_mapper(obj: Any) -> TypeGuard[Mapper[Any]]: ...
-
- def insp_is_aliased_class(obj: Any) -> TypeGuard[AliasedInsp[Any]]: ...
-
- def insp_is_attribute(
- obj: InspectionAttr,
- ) -> TypeGuard[QueryableAttribute[Any]]: ...
-
- def attr_is_internal_proxy(
- obj: InspectionAttr,
- ) -> TypeGuard[QueryableAttribute[Any]]: ...
-
- def prop_is_relationship(
- prop: MapperProperty[Any],
- ) -> TypeGuard[RelationshipProperty[Any]]: ...
-
- def is_collection_impl(
- impl: AttributeImpl,
- ) -> TypeGuard[CollectionAttributeImpl]: ...
-
- def is_has_collection_adapter(
- impl: AttributeImpl,
- ) -> TypeGuard[HasCollectionAdapter]: ...
-
-else:
- insp_is_mapper_property = operator.attrgetter("is_property")
- insp_is_mapper = operator.attrgetter("is_mapper")
- insp_is_aliased_class = operator.attrgetter("is_aliased_class")
- insp_is_attribute = operator.attrgetter("is_attribute")
- attr_is_internal_proxy = operator.attrgetter("_is_internal_proxy")
- is_collection_impl = operator.attrgetter("collection")
- prop_is_relationship = operator.attrgetter("_is_relationship")
- is_has_collection_adapter = operator.attrgetter(
- "_is_has_collection_adapter"
- )
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/attributes.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/attributes.py
deleted file mode 100644
index 5b16ce3..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/attributes.py
+++ /dev/null
@@ -1,2835 +0,0 @@
-# orm/attributes.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
-# mypy: allow-untyped-defs, allow-untyped-calls
-
-"""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.
-
-
-"""
-
-from __future__ import annotations
-
-import dataclasses
-import operator
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import ClassVar
-from typing import Dict
-from typing import Iterable
-from typing import List
-from typing import NamedTuple
-from typing import Optional
-from typing import overload
-from typing import Sequence
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from . import collections
-from . import exc as orm_exc
-from . import interfaces
-from ._typing import insp_is_aliased_class
-from .base import _DeclarativeMapped
-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 INCLUDE_PENDING_MUTATIONS # noqa
-from .base import INIT_OK
-from .base import instance_dict as instance_dict
-from .base import instance_state as instance_state
-from .base import instance_str
-from .base import LOAD_AGAINST_COMMITTED
-from .base import LoaderCallableStatus
-from .base import manager_of_class as manager_of_class
-from .base import Mapped as Mapped # noqa
-from .base import NEVER_SET # noqa
-from .base import NO_AUTOFLUSH
-from .base import NO_CHANGE # noqa
-from .base import NO_KEY
-from .base import NO_RAISE
-from .base import NO_VALUE
-from .base import NON_PERSISTENT_OK # noqa
-from .base import opt_manager_of_class as opt_manager_of_class
-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 PassiveFlag
-from .base import RELATED_OBJECT_OK # noqa
-from .base import SQL_OK # noqa
-from .base import SQLORMExpression
-from .base import state_str
-from .. import event
-from .. import exc
-from .. import inspection
-from .. import util
-from ..event import dispatcher
-from ..event import EventTarget
-from ..sql import base as sql_base
-from ..sql import cache_key
-from ..sql import coercions
-from ..sql import roles
-from ..sql import visitors
-from ..sql.cache_key import HasCacheKey
-from ..sql.visitors import _TraverseInternalsType
-from ..sql.visitors import InternalTraversal
-from ..util.typing import Literal
-from ..util.typing import Self
-from ..util.typing import TypeGuard
-
-if TYPE_CHECKING:
- from ._typing import _EntityType
- from ._typing import _ExternalEntityType
- from ._typing import _InstanceDict
- from ._typing import _InternalEntityType
- from ._typing import _LoaderCallable
- from ._typing import _O
- from .collections import _AdaptedCollectionProtocol
- from .collections import CollectionAdapter
- from .interfaces import MapperProperty
- from .relationships import RelationshipProperty
- from .state import InstanceState
- from .util import AliasedInsp
- from .writeonly import WriteOnlyAttributeImpl
- from ..event.base import _Dispatch
- from ..sql._typing import _ColumnExpressionArgument
- from ..sql._typing import _DMLColumnArgument
- from ..sql._typing import _InfoType
- from ..sql._typing import _PropagateAttrsType
- from ..sql.annotation import _AnnotationDict
- from ..sql.elements import ColumnElement
- from ..sql.elements import Label
- from ..sql.operators import OperatorType
- from ..sql.selectable import FromClause
-
-
-_T = TypeVar("_T")
-_T_co = TypeVar("_T_co", bound=Any, covariant=True)
-
-
-_AllPendingType = Sequence[
- Tuple[Optional["InstanceState[Any]"], Optional[object]]
-]
-
-
-_UNKNOWN_ATTR_KEY = object()
-
-
-@inspection._self_inspects
-class QueryableAttribute(
- _DeclarativeMapped[_T_co],
- SQLORMExpression[_T_co],
- interfaces.InspectionAttr,
- interfaces.PropComparator[_T_co],
- roles.JoinTargetRole,
- roles.OnClauseRole,
- sql_base.Immutable,
- cache_key.SlotsMemoizedHasCacheKey,
- util.MemoizedSlots,
- EventTarget,
-):
- """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`
- """
-
- __slots__ = (
- "class_",
- "key",
- "impl",
- "comparator",
- "property",
- "parent",
- "expression",
- "_of_type",
- "_extra_criteria",
- "_slots_dispatch",
- "_propagate_attrs",
- "_doc",
- )
-
- is_attribute = True
-
- dispatch: dispatcher[QueryableAttribute[_T_co]]
-
- class_: _ExternalEntityType[Any]
- key: str
- parententity: _InternalEntityType[Any]
- impl: AttributeImpl
- comparator: interfaces.PropComparator[_T_co]
- _of_type: Optional[_InternalEntityType[Any]]
- _extra_criteria: Tuple[ColumnElement[bool], ...]
- _doc: Optional[str]
-
- # PropComparator has a __visit_name__ to participate within
- # traversals. Disambiguate the attribute vs. a comparator.
- __visit_name__ = "orm_instrumented_attribute"
-
- def __init__(
- self,
- class_: _ExternalEntityType[_O],
- key: str,
- parententity: _InternalEntityType[_O],
- comparator: interfaces.PropComparator[_T_co],
- impl: Optional[AttributeImpl] = None,
- of_type: Optional[_InternalEntityType[Any]] = None,
- extra_criteria: Tuple[ColumnElement[bool], ...] = (),
- ):
- self.class_ = class_
- self.key = key
-
- self._parententity = self.parent = parententity
-
- # this attribute is non-None after mappers are set up, however in the
- # interim class manager setup, there's a check for None to see if it
- # needs to be populated, so we assign None here leaving the attribute
- # in a temporarily not-type-correct state
- self.impl = impl # type: ignore
-
- assert comparator is not None
- self.comparator = comparator
- self._of_type = of_type
- self._extra_criteria = extra_criteria
- self._doc = None
-
- manager = opt_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 # type: ignore
-
- _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) -> Any:
- # 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,
- ),
- )
-
- @property
- def _impl_uses_objects(self) -> bool:
- return self.impl.uses_objects
-
- def get_history(
- self, instance: Any, passive: PassiveFlag = PASSIVE_OFF
- ) -> History:
- return self.impl.get_history(
- instance_state(instance), instance_dict(instance), passive
- )
-
- @property
- def info(self) -> _InfoType:
- """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:`.Relationship`,
- 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
-
- parent: _InternalEntityType[Any]
- """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.
-
- """
-
- expression: ColumnElement[_T_co]
- """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.
-
- """
-
- def _memoized_attr_expression(self) -> ColumnElement[_T]:
- annotations: _AnnotationDict
-
- # applies only to Proxy() as used by hybrid.
- # currently is an exception to typing rather than feeding through
- # non-string keys.
- # ideally Proxy() would have a separate set of methods to deal
- # with this case.
- entity_namespace = self._entity_namespace
- assert isinstance(entity_namespace, HasCacheKey)
-
- if self.key is _UNKNOWN_ATTR_KEY:
- annotations = {"entity_namespace": entity_namespace}
- else:
- annotations = {
- "proxy_key": self.key,
- "proxy_owner": self._parententity,
- "entity_namespace": entity_namespace,
- }
-
- ce = self.comparator.__clause_element__()
- try:
- if TYPE_CHECKING:
- assert isinstance(ce, ColumnElement)
- anno = ce._annotate
- except AttributeError as ae:
- 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)
-
- def _memoized_attr__propagate_attrs(self) -> _PropagateAttrsType:
- # this suits the case in coercions where we don't actually
- # call ``__clause_element__()`` but still need to get
- # resolved._propagate_attrs. See #6558.
- return util.immutabledict(
- {
- "compile_state_plugin": "orm",
- "plugin_subject": self._parentmapper,
- }
- )
-
- @property
- def _entity_namespace(self) -> _InternalEntityType[Any]:
- return self._parententity
-
- @property
- def _annotations(self) -> _AnnotationDict:
- return self.__clause_element__()._annotations
-
- def __clause_element__(self) -> ColumnElement[_T_co]:
- return self.expression
-
- @property
- def _from_objects(self) -> List[FromClause]:
- return self.expression._from_objects
-
- def _bulk_update_tuples(
- self, value: Any
- ) -> Sequence[Tuple[_DMLColumnArgument, Any]]:
- """Return setter tuples for a bulk UPDATE."""
-
- return self.comparator._bulk_update_tuples(value)
-
- def adapt_to_entity(self, adapt_to_entity: AliasedInsp[Any]) -> Self:
- 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: _EntityType[Any]) -> QueryableAttribute[_T]:
- 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, *clauses: _ColumnExpressionArgument[bool]
- ) -> QueryableAttribute[bool]:
- if TYPE_CHECKING:
- assert isinstance(self.comparator, RelationshipProperty.Comparator)
-
- exprs = tuple(
- coercions.expect(roles.WhereHavingRole, clause)
- for clause in util.coerce_generator_arg(clauses)
- )
-
- return QueryableAttribute(
- self.class_,
- self.key,
- self._parententity,
- impl=self.impl,
- comparator=self.comparator.and_(*exprs),
- of_type=self._of_type,
- extra_criteria=self._extra_criteria + exprs,
- )
-
- def _clone(self, **kw: Any) -> QueryableAttribute[_T]:
- 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: Optional[str]) -> Label[_T_co]:
- return self.__clause_element__().label(name)
-
- def operate(
- self, op: OperatorType, *other: Any, **kwargs: Any
- ) -> ColumnElement[Any]:
- return op(self.comparator, *other, **kwargs) # type: ignore[no-any-return] # noqa: E501
-
- def reverse_operate(
- self, op: OperatorType, other: Any, **kwargs: Any
- ) -> ColumnElement[Any]:
- return op(other, self.comparator, **kwargs) # type: ignore[no-any-return] # noqa: E501
-
- def hasparent(
- self, state: InstanceState[Any], optimistic: bool = False
- ) -> bool:
- return self.impl.hasparent(state, optimistic=optimistic) is not False
-
- def __getattr__(self, key: str) -> Any:
- try:
- return util.MemoizedSlots.__getattr__(self, key)
- except AttributeError:
- pass
-
- try:
- return getattr(self.comparator, key)
- except AttributeError as err:
- raise AttributeError(
- "Neither %r object nor %r object associated with %s "
- "has an attribute %r"
- % (
- type(self).__name__,
- type(self.comparator).__name__,
- self,
- key,
- )
- ) from err
-
- def __str__(self) -> str:
- return f"{self.class_.__name__}.{self.key}"
-
- def _memoized_attr_property(self) -> Optional[MapperProperty[Any]]:
- return self.comparator.property
-
-
-def _queryable_attribute_unreduce(
- key: str,
- mapped_class: Type[_O],
- parententity: _InternalEntityType[_O],
- entity: _ExternalEntityType[Any],
-) -> Any:
- # this method is only used in terms of the
- # sqlalchemy.ext.serializer extension
- if insp_is_aliased_class(parententity):
- return entity._get_from_serialized(key, mapped_class, parententity)
- else:
- return getattr(entity, key)
-
-
-class InstrumentedAttribute(QueryableAttribute[_T_co]):
- """Class bound instrumented attribute which adds basic
- :term:`descriptor` methods.
-
- See :class:`.QueryableAttribute` for a description of most features.
-
-
- """
-
- __slots__ = ()
-
- inherit_cache = True
- """:meta private:"""
-
- # hack to make __doc__ writeable on instances of
- # InstrumentedAttribute, while still keeping classlevel
- # __doc__ correct
-
- @util.rw_hybridproperty
- def __doc__(self) -> Optional[str]:
- return self._doc
-
- @__doc__.setter # type: ignore
- def __doc__(self, value: Optional[str]) -> None:
- self._doc = value
-
- @__doc__.classlevel # type: ignore
- def __doc__(cls) -> Optional[str]:
- return super().__doc__
-
- def __set__(self, instance: object, value: Any) -> None:
- self.impl.set(
- instance_state(instance), instance_dict(instance), value, None
- )
-
- def __delete__(self, instance: object) -> None:
- self.impl.delete(instance_state(instance), instance_dict(instance))
-
- @overload
- def __get__(
- self, instance: None, owner: Any
- ) -> InstrumentedAttribute[_T_co]: ...
-
- @overload
- def __get__(self, instance: object, owner: Any) -> _T_co: ...
-
- def __get__(
- self, instance: Optional[object], owner: Any
- ) -> Union[InstrumentedAttribute[_T_co], _T_co]:
- if instance is None:
- return self
-
- dict_ = instance_dict(instance)
- if self.impl.supports_population and self.key in dict_:
- return dict_[self.key] # type: ignore[no-any-return]
- else:
- try:
- state = instance_state(instance)
- except AttributeError as err:
- raise orm_exc.UnmappedInstanceError(instance) from err
- return self.impl.get(state, dict_) # type: ignore[no-any-return]
-
-
-@dataclasses.dataclass(frozen=True)
-class AdHocHasEntityNamespace(HasCacheKey):
- _traverse_internals: ClassVar[_TraverseInternalsType] = [
- ("_entity_namespace", InternalTraversal.dp_has_cache_key),
- ]
-
- # py37 compat, no slots=True on dataclass
- __slots__ = ("_entity_namespace",)
- _entity_namespace: _InternalEntityType[Any]
- is_mapper: ClassVar[bool] = False
- is_aliased_class: ClassVar[bool] = False
-
- @property
- def entity_namespace(self):
- return self._entity_namespace.entity_namespace
-
-
-def create_proxied_attribute(
- descriptor: Any,
-) -> Callable[..., QueryableAttribute[Any]]:
- """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[Any]):
- """Presents the :class:`.QueryableAttribute` interface as a
- proxy on top of a Python descriptor / :class:`.PropComparator`
- combination.
-
- """
-
- _extra_criteria = ()
-
- # the attribute error catches inside of __getattr__ basically create a
- # singularity if you try putting slots on this too
- # __slots__ = ("descriptor", "original_property", "_comparator")
-
- 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 = self.__doc__ = doc
-
- @property
- def _parententity(self):
- return inspection.inspect(self.class_, raiseerr=False)
-
- @property
- def parent(self):
- return inspection.inspect(self.class_, raiseerr=False)
-
- _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 _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 AdHocHasEntityNamespace(self._parententity)
-
- @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) -> str:
- return f"{self.class_.__name__}.{self.key}"
-
- def __getattr__(self, attribute):
- """Delegate __getattr__ to the original descriptor and/or
- comparator."""
-
- # this is unfortunately very complicated, and is easily prone
- # to recursion overflows when implementations of related
- # __getattr__ schemes are changed
-
- try:
- return util.MemoizedSlots.__getattr__(self, attribute)
- except AttributeError:
- pass
-
- try:
- return getattr(descriptor, attribute)
- except AttributeError as err:
- if attribute == "comparator":
- raise AttributeError("comparator") from err
- try:
- # comparator itself might be unreachable
- comparator = self.comparator
- except AttributeError as err2:
- raise AttributeError(
- "Neither %r object nor unconfigured comparator "
- "object associated with %s has an attribute %r"
- % (type(descriptor).__name__, self, attribute)
- ) from err2
- else:
- try:
- return getattr(comparator, attribute)
- except AttributeError as err3:
- raise AttributeError(
- "Neither %r object nor %r object "
- "associated with %s has an attribute %r"
- % (
- type(descriptor).__name__,
- type(comparator).__name__,
- self,
- attribute,
- )
- ) from 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 AttributeEventToken:
- """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.
-
- .. versionchanged:: 2.0 Changed the name from ``AttributeEvent``
- to ``AttributeEventToken``.
-
- :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: AttributeImpl, op: util.symbol):
- self.impl = attribute_impl
- self.op = op
- self.parent_token = self.impl.parent_token
-
- def __eq__(self, other):
- return (
- isinstance(other, AttributeEventToken)
- 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)
-
-
-AttributeEvent = AttributeEventToken # legacy
-Event = AttributeEventToken # legacy
-
-
-class AttributeImpl:
- """internal implementation for instrumented attributes."""
-
- collection: bool
- default_accepts_scalar_loader: bool
- uses_objects: bool
- supports_population: bool
- dynamic: bool
-
- _is_has_collection_adapter = False
-
- _replace_token: AttributeEventToken
- _remove_token: AttributeEventToken
- _append_token: AttributeEventToken
-
- def __init__(
- self,
- class_: _ExternalEntityType[_O],
- key: str,
- callable_: Optional[_LoaderCallable],
- dispatch: _Dispatch[QueryableAttribute[Any]],
- trackparent: bool = False,
- compare_function: Optional[Callable[..., bool]] = None,
- active_history: bool = False,
- parent_token: Optional[AttributeEventToken] = None,
- load_on_unexpire: bool = True,
- send_modified_events: bool = True,
- accepts_scalar_loader: Optional[bool] = None,
- **kwargs: Any,
- ):
- 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 = AttributeEventToken(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) -> str:
- return f"{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: InstanceState[Any], optimistic: bool = False
- ) -> bool:
- """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: InstanceState[Any],
- parent_state: InstanceState[Any],
- value: bool,
- ) -> None:
- """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: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PASSIVE_OFF,
- ) -> History:
- raise NotImplementedError()
-
- def get_all_pending(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PASSIVE_NO_INITIALIZE,
- ) -> _AllPendingType:
- """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: InstanceState[Any], dict_: _InstanceDict
- ) -> Any:
- """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: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PASSIVE_OFF,
- ) -> Any:
- """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.
- raise KeyError(
- "Deferred loader for attribute "
- "%r failed to populate "
- "correctly" % key
- ) from 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: InstanceState[Any], key: str, passive: PassiveFlag
- ) -> Any:
- 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: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken],
- passive: PassiveFlag = PASSIVE_OFF,
- ) -> None:
- self.set(state, dict_, value, initiator, passive=passive)
-
- def remove(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken],
- passive: PassiveFlag = PASSIVE_OFF,
- ) -> None:
- self.set(
- state, dict_, None, initiator, passive=passive, check_old=value
- )
-
- def pop(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken],
- passive: PassiveFlag = PASSIVE_OFF,
- ) -> None:
- self.set(
- state,
- dict_,
- None,
- initiator,
- passive=passive,
- check_old=value,
- pop=True,
- )
-
- def set(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken] = None,
- passive: PassiveFlag = PASSIVE_OFF,
- check_old: Any = None,
- pop: bool = False,
- ) -> None:
- raise NotImplementedError()
-
- def delete(self, state: InstanceState[Any], dict_: _InstanceDict) -> None:
- raise NotImplementedError()
-
- def get_committed_value(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PASSIVE_OFF,
- ) -> Any:
- """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().__init__(*arg, **kw)
- self._replace_token = self._append_token = AttributeEventToken(
- self, OP_REPLACE
- )
- self._remove_token = AttributeEventToken(self, OP_REMOVE)
-
- def delete(self, state: InstanceState[Any], dict_: _InstanceDict) -> None:
- 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: InstanceState[Any],
- dict_: Dict[str, Any],
- passive: PassiveFlag = PASSIVE_OFF,
- ) -> History:
- 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: InstanceState[Any],
- dict_: Dict[str, Any],
- value: Any,
- initiator: Optional[AttributeEventToken] = None,
- passive: PassiveFlag = PASSIVE_OFF,
- check_old: Optional[object] = None,
- pop: bool = False,
- ) -> None:
- 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: InstanceState[Any],
- dict_: _InstanceDict,
- value: _T,
- previous: Any,
- initiator: Optional[AttributeEventToken],
- ) -> _T:
- for fn in self.dispatch.set:
- value = fn(
- state, value, previous, initiator or self._replace_token
- )
- return value
-
- def fire_remove_event(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken],
- ) -> None:
- for fn in self.dispatch.remove:
- fn(state, value, initiator or self._remove_token)
-
-
-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: InstanceState[Any], dict_: _InstanceDict) -> None:
- 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: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PASSIVE_OFF,
- ) -> History:
- 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: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PASSIVE_NO_INITIALIZE,
- ) -> _AllPendingType:
- if self.key in dict_:
- current = dict_[self.key]
- elif passive & CALLABLES_OK:
- current = self.get(state, dict_, passive=passive)
- else:
- return []
-
- ret: _AllPendingType
-
- # 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: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken] = None,
- passive: PassiveFlag = PASSIVE_OFF,
- check_old: Any = None,
- pop: bool = False,
- ) -> None:
- """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: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken],
- ) -> None:
- 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: InstanceState[Any],
- dict_: _InstanceDict,
- value: _T,
- previous: Any,
- initiator: Optional[AttributeEventToken],
- ) -> _T:
- 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 HasCollectionAdapter:
- __slots__ = ()
-
- collection: bool
- _is_has_collection_adapter = True
-
- def _dispose_previous_collection(
- self,
- state: InstanceState[Any],
- collection: _AdaptedCollectionProtocol,
- adapter: CollectionAdapter,
- fire_event: bool,
- ) -> None:
- raise NotImplementedError()
-
- @overload
- def get_collection(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- user_data: Literal[None] = ...,
- passive: Literal[PassiveFlag.PASSIVE_OFF] = ...,
- ) -> CollectionAdapter: ...
-
- @overload
- def get_collection(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- user_data: _AdaptedCollectionProtocol = ...,
- passive: PassiveFlag = ...,
- ) -> CollectionAdapter: ...
-
- @overload
- def get_collection(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- user_data: Optional[_AdaptedCollectionProtocol] = ...,
- passive: PassiveFlag = ...,
- ) -> Union[
- Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
- ]: ...
-
- def get_collection(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- user_data: Optional[_AdaptedCollectionProtocol] = None,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- ) -> Union[
- Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
- ]:
- raise NotImplementedError()
-
- def set(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken] = None,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- check_old: Any = None,
- pop: bool = False,
- _adapt: bool = True,
- ) -> None:
- raise NotImplementedError()
-
-
-if TYPE_CHECKING:
-
- def _is_collection_attribute_impl(
- impl: AttributeImpl,
- ) -> TypeGuard[CollectionAttributeImpl]: ...
-
-else:
- _is_collection_attribute_impl = operator.attrgetter("collection")
-
-
-class CollectionAttributeImpl(HasCollectionAdapter, 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.
-
- """
-
- uses_objects = True
- collection = True
- default_accepts_scalar_loader = False
- supports_population = True
- dynamic = False
-
- _bulk_replace_token: AttributeEventToken
-
- __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().__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 = AttributeEventToken(self, OP_APPEND)
- self._remove_token = AttributeEventToken(self, OP_REMOVE)
- self._bulk_replace_token = AttributeEventToken(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: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PASSIVE_OFF,
- ) -> History:
- current = self.get(state, dict_, passive=passive)
-
- if current is PASSIVE_NO_RESULT:
- if (
- passive & PassiveFlag.INCLUDE_PENDING_MUTATIONS
- and self.key in state._pending_mutations
- ):
- pending = state._pending_mutations[self.key]
- return pending.merge_with_history(HISTORY_BLANK)
- else:
- return HISTORY_BLANK
- else:
- if passive & PassiveFlag.INCLUDE_PENDING_MUTATIONS:
- # this collection is loaded / present. should not be any
- # pending mutations
- assert self.key not in state._pending_mutations
-
- return History.from_collection(self, state, current)
-
- def get_all_pending(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PASSIVE_NO_INITIALIZE,
- ) -> _AllPendingType:
- # 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: InstanceState[Any],
- dict_: _InstanceDict,
- value: _T,
- initiator: Optional[AttributeEventToken],
- key: Optional[Any],
- ) -> _T:
- for fn in self.dispatch.append:
- value = fn(state, value, initiator or self._append_token, key=key)
-
- 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: InstanceState[Any],
- dict_: _InstanceDict,
- value: _T,
- initiator: Optional[AttributeEventToken],
- key: Optional[Any],
- ) -> _T:
- for fn in self.dispatch.append_wo_mutation:
- value = fn(state, value, initiator or self._append_token, key=key)
-
- return value
-
- def fire_pre_remove_event(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- initiator: Optional[AttributeEventToken],
- key: Optional[Any],
- ) -> None:
- """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: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken],
- key: Optional[Any],
- ) -> None:
- 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, key=key)
-
- state._modified_event(dict_, self, NO_VALUE, True)
-
- def delete(self, state: InstanceState[Any], dict_: _InstanceDict) -> None:
- 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: InstanceState[Any], dict_: _InstanceDict
- ) -> _AdaptedCollectionProtocol:
- """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: InstanceState[Any]
- ) -> Tuple[CollectionAdapter, _AdaptedCollectionProtocol]:
- 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: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken],
- passive: PassiveFlag = PASSIVE_OFF,
- ) -> None:
- collection = self.get_collection(
- state, dict_, user_data=None, passive=passive
- )
- if collection is PASSIVE_NO_RESULT:
- value = self.fire_append_event(
- state, dict_, value, initiator, key=NO_KEY
- )
- assert (
- self.key not in dict_
- ), "Collection was loaded during event handling."
- state._get_pending_mutation(self.key).append(value)
- else:
- if TYPE_CHECKING:
- assert isinstance(collection, CollectionAdapter)
- collection.append_with_event(value, initiator)
-
- def remove(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken],
- passive: PassiveFlag = PASSIVE_OFF,
- ) -> None:
- collection = self.get_collection(
- state, state.dict, user_data=None, passive=passive
- )
- if collection is PASSIVE_NO_RESULT:
- self.fire_remove_event(state, dict_, value, initiator, key=NO_KEY)
- assert (
- self.key not in dict_
- ), "Collection was loaded during event handling."
- state._get_pending_mutation(self.key).remove(value)
- else:
- if TYPE_CHECKING:
- assert isinstance(collection, CollectionAdapter)
- collection.remove_with_event(value, initiator)
-
- def pop(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken],
- passive: PassiveFlag = PASSIVE_OFF,
- ) -> None:
- 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: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken] = None,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- check_old: Any = None,
- pop: bool = False,
- _adapt: bool = True,
- ) -> None:
- iterable = orig_iterable = value
- new_keys = None
-
- # 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:
- new_keys = list(iterable)
- iterable = iterable.values()
- else:
- iterable = iter(iterable)
- elif util.duck_type_collection(iterable) is dict:
- new_keys = list(value)
-
- new_values = list(iterable)
-
- evt = self._bulk_replace_token
-
- self.dispatch.bulk_replace(state, new_values, evt, keys=new_keys)
-
- # propagate NO_RAISE in passive through to the get() for the
- # existing object (ticket #8862)
- old = self.get(
- state,
- dict_,
- passive=PASSIVE_ONLY_PERSISTENT ^ (passive & PassiveFlag.NO_RAISE),
- )
- 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: InstanceState[Any],
- collection: _AdaptedCollectionProtocol,
- adapter: CollectionAdapter,
- fire_event: bool,
- ) -> None:
- 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: _AdaptedCollectionProtocol
- ) -> None:
- adapter = getattr(collection, "_sa_adapter")
- adapter.invalidated = True
-
- def set_committed_value(
- self, state: InstanceState[Any], dict_: _InstanceDict, value: Any
- ) -> _AdaptedCollectionProtocol:
- """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
-
- @overload
- def get_collection(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- user_data: Literal[None] = ...,
- passive: Literal[PassiveFlag.PASSIVE_OFF] = ...,
- ) -> CollectionAdapter: ...
-
- @overload
- def get_collection(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- user_data: _AdaptedCollectionProtocol = ...,
- passive: PassiveFlag = ...,
- ) -> CollectionAdapter: ...
-
- @overload
- def get_collection(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- user_data: Optional[_AdaptedCollectionProtocol] = ...,
- passive: PassiveFlag = PASSIVE_OFF,
- ) -> Union[
- Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
- ]: ...
-
- def get_collection(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- user_data: Optional[_AdaptedCollectionProtocol] = None,
- passive: PassiveFlag = PASSIVE_OFF,
- ) -> Union[
- Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
- ]:
- """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:
- fetch_user_data = self.get(state, dict_, passive=passive)
- if fetch_user_data is LoaderCallableStatus.PASSIVE_NO_RESULT:
- return fetch_user_data
- else:
- user_data = cast("_AdaptedCollectionProtocol", fetch_user_data)
-
- return user_data._sa_adapter
-
-
-def backref_listeners(
- attribute: QueryableAttribute[Any], key: str, uselist: bool
-) -> None:
- """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, **kw
- ):
- 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 _is_collection_attribute_impl(child_impl)
- 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, **kw
- ):
- 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 _is_collection_attribute_impl(child_impl)
- 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, **kw
- ):
- 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
-
- check_replace_token: Optional[AttributeEventToken]
-
- # 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 _is_collection_attribute_impl(child_impl)
- 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,
- include_key=True,
- )
- else:
- event.listen(
- attribute,
- "set",
- emit_backref_from_scalar_set_event,
- retval=True,
- raw=True,
- include_key=True,
- )
- # TODO: need coverage in test/orm/ of remove event
- event.listen(
- attribute,
- "remove",
- emit_backref_from_collection_remove_event,
- retval=True,
- raw=True,
- include_key=True,
- )
-
-
-_NO_HISTORY = util.symbol("NO_HISTORY")
-_NO_STATE_SYMBOLS = frozenset([id(PASSIVE_NO_RESULT), id(NO_VALUE)])
-
-
-class History(NamedTuple):
- """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).
-
- """
-
- added: Union[Tuple[()], List[Any]]
- unchanged: Union[Tuple[()], List[Any]]
- deleted: Union[Tuple[()], List[Any]]
-
- def __bool__(self) -> bool:
- return self != HISTORY_BLANK
-
- def empty(self) -> bool:
- """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) -> Sequence[Any]:
- """Return a collection of added + unchanged + deleted."""
-
- return (
- (self.added or []) + (self.unchanged or []) + (self.deleted or [])
- )
-
- def non_deleted(self) -> Sequence[Any]:
- """Return a collection of added + unchanged."""
-
- return (self.added or []) + (self.unchanged or [])
-
- def non_added(self) -> Sequence[Any]:
- """Return a collection of unchanged + deleted."""
-
- return (self.unchanged or []) + (self.deleted or [])
-
- def has_changes(self) -> bool:
- """Return True if this :class:`.History` has changes."""
-
- return bool(self.added or self.deleted)
-
- def _merge(self, added: Iterable[Any], deleted: Iterable[Any]) -> History:
- return History(
- list(self.added) + list(added),
- self.unchanged,
- list(self.deleted) + list(deleted),
- )
-
- def as_state(self) -> History:
- 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: ScalarAttributeImpl,
- state: InstanceState[Any],
- current: Any,
- ) -> History:
- original = state.committed_state.get(attribute.key, _NO_HISTORY)
-
- deleted: Union[Tuple[()], List[Any]]
-
- 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: ScalarObjectAttributeImpl,
- state: InstanceState[Any],
- current: Any,
- original: Any = _NO_HISTORY,
- ) -> History:
- deleted: Union[Tuple[()], List[Any]]
-
- 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: CollectionAttributeImpl,
- state: InstanceState[Any],
- current: Any,
- ) -> History:
- 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((), (), ())
-
-
-def get_history(
- obj: object, key: str, passive: PassiveFlag = PASSIVE_OFF
-) -> History:
- """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: InstanceState[Any], key: str, passive: PassiveFlag = PASSIVE_OFF
-) -> History:
- return state.get_history(key, passive)
-
-
-def has_parent(
- cls: Type[_O], obj: _O, key: str, optimistic: bool = False
-) -> bool:
- """TODO"""
- manager = manager_of_class(cls)
- state = instance_state(obj)
- return manager.has_parent(state, key, optimistic)
-
-
-def register_attribute(
- class_: Type[_O],
- key: str,
- *,
- comparator: interfaces.PropComparator[_T],
- parententity: _InternalEntityType[_O],
- doc: Optional[str] = None,
- **kw: Any,
-) -> InstrumentedAttribute[_T]:
- desc = register_descriptor(
- class_, key, comparator=comparator, parententity=parententity, doc=doc
- )
- register_attribute_impl(class_, key, **kw)
- return desc
-
-
-def register_attribute_impl(
- class_: Type[_O],
- key: str,
- uselist: bool = False,
- callable_: Optional[_LoaderCallable] = None,
- useobject: bool = False,
- impl_class: Optional[Type[AttributeImpl]] = None,
- backref: Optional[str] = None,
- **kw: Any,
-) -> QueryableAttribute[Any]:
- 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 = cast(
- "_Dispatch[QueryableAttribute[Any]]", manager[key].dispatch
- ) # noqa: E501
-
- impl: AttributeImpl
-
- if impl_class:
- # TODO: this appears to be the WriteOnlyAttributeImpl /
- # DynamicAttributeImpl constructor which is hardcoded
- impl = cast("Type[WriteOnlyAttributeImpl]", impl_class)(
- class_, key, 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_: Type[Any],
- key: str,
- *,
- comparator: interfaces.PropComparator[_T],
- parententity: _InternalEntityType[Any],
- doc: Optional[str] = None,
-) -> InstrumentedAttribute[_T]:
- manager = manager_of_class(class_)
-
- descriptor = InstrumentedAttribute(
- class_, key, comparator=comparator, parententity=parententity
- )
-
- descriptor.__doc__ = doc # type: ignore
-
- manager.instrument_attribute(key, descriptor)
- return descriptor
-
-
-def unregister_attribute(class_: Type[Any], key: str) -> None:
- manager_of_class(class_).uninstrument_attribute(key)
-
-
-def init_collection(obj: object, key: str) -> CollectionAdapter:
- """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: InstanceState[Any], dict_: _InstanceDict, key: str
-) -> CollectionAdapter:
- """Initialize a collection attribute and return the collection adapter.
-
- Discards any existing collection which may be there.
-
- """
- attr = state.manager[key].impl
-
- if TYPE_CHECKING:
- assert isinstance(attr, HasCollectionAdapter)
-
- 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: CollectionAdapter = attr.get_collection(
- state, dict_, user_data, passive=PassiveFlag.PASSIVE_NO_FETCH
- )
- 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: object,
- key: str,
- value: Any,
- initiator: Optional[AttributeEventToken] = None,
-) -> 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: object, key: str) -> Any:
- """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: object, key: str) -> None:
- """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: object, key: str) -> None:
- """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: object) -> None:
- """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)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/base.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/base.py
deleted file mode 100644
index c900529..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/base.py
+++ /dev/null
@@ -1,971 +0,0 @@
-# orm/base.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
-
-"""Constants and rudimental functions used throughout the ORM.
-
-"""
-
-from __future__ import annotations
-
-from enum import Enum
-import operator
-import typing
-from typing import Any
-from typing import Callable
-from typing import Dict
-from typing import Generic
-from typing import no_type_check
-from typing import Optional
-from typing import overload
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from . import exc
-from ._typing import insp_is_mapper
-from .. import exc as sa_exc
-from .. import inspection
-from .. import util
-from ..sql import roles
-from ..sql.elements import SQLColumnExpression
-from ..sql.elements import SQLCoreOperations
-from ..util import FastIntFlag
-from ..util.langhelpers import TypingOnly
-from ..util.typing import Literal
-
-if typing.TYPE_CHECKING:
- from ._typing import _EntityType
- from ._typing import _ExternalEntityType
- from ._typing import _InternalEntityType
- from .attributes import InstrumentedAttribute
- from .dynamic import AppenderQuery
- from .instrumentation import ClassManager
- from .interfaces import PropComparator
- from .mapper import Mapper
- from .state import InstanceState
- from .util import AliasedClass
- from .writeonly import WriteOnlyCollection
- from ..sql._typing import _ColumnExpressionArgument
- from ..sql._typing import _InfoType
- from ..sql.elements import ColumnElement
- from ..sql.operators import OperatorType
-
-_T = TypeVar("_T", bound=Any)
-_T_co = TypeVar("_T_co", bound=Any, covariant=True)
-
-_O = TypeVar("_O", bound=object)
-
-
-class LoaderCallableStatus(Enum):
- PASSIVE_NO_RESULT = 0
- """Symbol returned by a loader callable or other attribute/history
- retrieval operation when a value could not be determined, based
- on loader callable flags.
- """
-
- PASSIVE_CLASS_MISMATCH = 1
- """Symbol indicating that an object is locally present for a given
- primary key identity but it is not of the requested class. The
- return value is therefore None and no SQL should be emitted."""
-
- ATTR_WAS_SET = 2
- """Symbol returned by a loader callable to indicate the
- retrieved value, or values, were assigned to their attributes
- on the target object.
- """
-
- ATTR_EMPTY = 3
- """Symbol used internally to indicate an attribute had no callable."""
-
- NO_VALUE = 4
- """Symbol which may be placed as the 'previous' value of an attribute,
- indicating no value was loaded for an attribute when it was modified,
- and flags indicated we were not to load it.
- """
-
- NEVER_SET = NO_VALUE
- """
- Synonymous with NO_VALUE
-
- .. versionchanged:: 1.4 NEVER_SET was merged with NO_VALUE
-
- """
-
-
-(
- PASSIVE_NO_RESULT,
- PASSIVE_CLASS_MISMATCH,
- ATTR_WAS_SET,
- ATTR_EMPTY,
- NO_VALUE,
-) = tuple(LoaderCallableStatus)
-
-NEVER_SET = NO_VALUE
-
-
-class PassiveFlag(FastIntFlag):
- """Bitflag interface that passes options onto loader callables"""
-
- NO_CHANGE = 0
- """No callables or SQL should be emitted on attribute access
- and no state should change
- """
-
- CALLABLES_OK = 1
- """Loader callables can be fired off if a value
- is not present.
- """
-
- SQL_OK = 2
- """Loader callables can emit SQL at least on scalar value attributes."""
-
- RELATED_OBJECT_OK = 4
- """Callables can use SQL to load related objects as well
- as scalar value attributes.
- """
-
- INIT_OK = 8
- """Attributes should be initialized with a blank
- value (None or an empty collection) upon get, if no other
- value can be obtained.
- """
-
- NON_PERSISTENT_OK = 16
- """Callables can be emitted if the parent is not persistent."""
-
- LOAD_AGAINST_COMMITTED = 32
- """Callables should use committed values as primary/foreign keys during a
- load.
- """
-
- NO_AUTOFLUSH = 64
- """Loader callables should disable autoflush.""",
-
- NO_RAISE = 128
- """Loader callables should not raise any assertions"""
-
- DEFERRED_HISTORY_LOAD = 256
- """indicates special load of the previous value of an attribute"""
-
- INCLUDE_PENDING_MUTATIONS = 512
-
- # pre-packaged sets of flags used as inputs
- PASSIVE_OFF = (
- RELATED_OBJECT_OK | NON_PERSISTENT_OK | INIT_OK | CALLABLES_OK | SQL_OK
- )
- "Callables can be emitted in all cases."
-
- PASSIVE_RETURN_NO_VALUE = PASSIVE_OFF ^ INIT_OK
- """PASSIVE_OFF ^ INIT_OK"""
-
- PASSIVE_NO_INITIALIZE = PASSIVE_RETURN_NO_VALUE ^ CALLABLES_OK
- "PASSIVE_RETURN_NO_VALUE ^ CALLABLES_OK"
-
- PASSIVE_NO_FETCH = PASSIVE_OFF ^ SQL_OK
- "PASSIVE_OFF ^ SQL_OK"
-
- PASSIVE_NO_FETCH_RELATED = PASSIVE_OFF ^ RELATED_OBJECT_OK
- "PASSIVE_OFF ^ RELATED_OBJECT_OK"
-
- PASSIVE_ONLY_PERSISTENT = PASSIVE_OFF ^ NON_PERSISTENT_OK
- "PASSIVE_OFF ^ NON_PERSISTENT_OK"
-
- PASSIVE_MERGE = PASSIVE_OFF | NO_RAISE
- """PASSIVE_OFF | NO_RAISE
-
- Symbol used specifically for session.merge() and similar cases
-
- """
-
-
-(
- NO_CHANGE,
- CALLABLES_OK,
- SQL_OK,
- RELATED_OBJECT_OK,
- INIT_OK,
- NON_PERSISTENT_OK,
- LOAD_AGAINST_COMMITTED,
- NO_AUTOFLUSH,
- NO_RAISE,
- DEFERRED_HISTORY_LOAD,
- INCLUDE_PENDING_MUTATIONS,
- PASSIVE_OFF,
- PASSIVE_RETURN_NO_VALUE,
- PASSIVE_NO_INITIALIZE,
- PASSIVE_NO_FETCH,
- PASSIVE_NO_FETCH_RELATED,
- PASSIVE_ONLY_PERSISTENT,
- PASSIVE_MERGE,
-) = PassiveFlag.__members__.values()
-
-DEFAULT_MANAGER_ATTR = "_sa_class_manager"
-DEFAULT_STATE_ATTR = "_sa_instance_state"
-
-
-class EventConstants(Enum):
- EXT_CONTINUE = 1
- EXT_STOP = 2
- EXT_SKIP = 3
- NO_KEY = 4
- """indicates an :class:`.AttributeEvent` event that did not have any
- key argument.
-
- .. versionadded:: 2.0
-
- """
-
-
-EXT_CONTINUE, EXT_STOP, EXT_SKIP, NO_KEY = tuple(EventConstants)
-
-
-class RelationshipDirection(Enum):
- """enumeration which indicates the 'direction' of a
- :class:`_orm.RelationshipProperty`.
-
- :class:`.RelationshipDirection` is accessible from the
- :attr:`_orm.Relationship.direction` attribute of
- :class:`_orm.RelationshipProperty`.
-
- """
-
- ONETOMANY = 1
- """Indicates the one-to-many direction for a :func:`_orm.relationship`.
-
- This symbol is typically used by the internals but may be exposed within
- certain API features.
-
- """
-
- MANYTOONE = 2
- """Indicates the many-to-one direction for a :func:`_orm.relationship`.
-
- This symbol is typically used by the internals but may be exposed within
- certain API features.
-
- """
-
- MANYTOMANY = 3
- """Indicates the many-to-many direction for a :func:`_orm.relationship`.
-
- This symbol is typically used by the internals but may be exposed within
- certain API features.
-
- """
-
-
-ONETOMANY, MANYTOONE, MANYTOMANY = tuple(RelationshipDirection)
-
-
-class InspectionAttrExtensionType(Enum):
- """Symbols indicating the type of extension that a
- :class:`.InspectionAttr` is part of."""
-
-
-class NotExtension(InspectionAttrExtensionType):
- NOT_EXTENSION = "not_extension"
- """Symbol indicating an :class:`InspectionAttr` that's
- not part of sqlalchemy.ext.
-
- Is assigned to the :attr:`.InspectionAttr.extension_type`
- attribute.
-
- """
-
-
-_never_set = frozenset([NEVER_SET])
-
-_none_set = frozenset([None, NEVER_SET, PASSIVE_NO_RESULT])
-
-_SET_DEFERRED_EXPIRED = util.symbol("SET_DEFERRED_EXPIRED")
-
-_DEFER_FOR_STATE = util.symbol("DEFER_FOR_STATE")
-
-_RAISE_FOR_STATE = util.symbol("RAISE_FOR_STATE")
-
-
-_F = TypeVar("_F", bound=Callable[..., Any])
-_Self = TypeVar("_Self")
-
-
-def _assertions(
- *assertions: Any,
-) -> Callable[[_F], _F]:
- @util.decorator
- def generate(fn: _F, self: _Self, *args: Any, **kw: Any) -> _Self:
- for assertion in assertions:
- assertion(self, fn.__name__)
- fn(self, *args, **kw)
- return self
-
- return generate
-
-
-if TYPE_CHECKING:
-
- def manager_of_class(cls: Type[_O]) -> ClassManager[_O]: ...
-
- @overload
- def opt_manager_of_class(cls: AliasedClass[Any]) -> None: ...
-
- @overload
- def opt_manager_of_class(
- cls: _ExternalEntityType[_O],
- ) -> Optional[ClassManager[_O]]: ...
-
- def opt_manager_of_class(
- cls: _ExternalEntityType[_O],
- ) -> Optional[ClassManager[_O]]: ...
-
- def instance_state(instance: _O) -> InstanceState[_O]: ...
-
- def instance_dict(instance: object) -> Dict[str, Any]: ...
-
-else:
- # these can be replaced by sqlalchemy.ext.instrumentation
- # if augmented class instrumentation is enabled.
-
- def manager_of_class(cls):
- try:
- return cls.__dict__[DEFAULT_MANAGER_ATTR]
- except KeyError as ke:
- raise exc.UnmappedClassError(
- cls, f"Can't locate an instrumentation manager for class {cls}"
- ) from ke
-
- def opt_manager_of_class(cls):
- return cls.__dict__.get(DEFAULT_MANAGER_ATTR)
-
- instance_state = operator.attrgetter(DEFAULT_STATE_ATTR)
-
- instance_dict = operator.attrgetter("__dict__")
-
-
-def instance_str(instance: object) -> str:
- """Return a string describing an instance."""
-
- return state_str(instance_state(instance))
-
-
-def state_str(state: InstanceState[Any]) -> str:
- """Return a string describing an instance via its InstanceState."""
-
- if state is None:
- return "None"
- else:
- return "<%s at 0x%x>" % (state.class_.__name__, id(state.obj()))
-
-
-def state_class_str(state: InstanceState[Any]) -> str:
- """Return a string describing an instance's class via its
- InstanceState.
- """
-
- if state is None:
- return "None"
- else:
- return "<%s>" % (state.class_.__name__,)
-
-
-def attribute_str(instance: object, attribute: str) -> str:
- return instance_str(instance) + "." + attribute
-
-
-def state_attribute_str(state: InstanceState[Any], attribute: str) -> str:
- return state_str(state) + "." + attribute
-
-
-def object_mapper(instance: _T) -> Mapper[_T]:
- """Given an object, return the primary Mapper associated with the object
- instance.
-
- Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
- if no mapping is configured.
-
- This function is available via the inspection system as::
-
- inspect(instance).mapper
-
- Using the inspection system will raise
- :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
- not part of a mapping.
-
- """
- return object_state(instance).mapper
-
-
-def object_state(instance: _T) -> InstanceState[_T]:
- """Given an object, return the :class:`.InstanceState`
- associated with the object.
-
- Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
- if no mapping is configured.
-
- Equivalent functionality is available via the :func:`_sa.inspect`
- function as::
-
- inspect(instance)
-
- Using the inspection system will raise
- :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
- not part of a mapping.
-
- """
- state = _inspect_mapped_object(instance)
- if state is None:
- raise exc.UnmappedInstanceError(instance)
- else:
- return state
-
-
-@inspection._inspects(object)
-def _inspect_mapped_object(instance: _T) -> Optional[InstanceState[_T]]:
- try:
- return instance_state(instance)
- except (exc.UnmappedClassError,) + exc.NO_STATE:
- return None
-
-
-def _class_to_mapper(
- class_or_mapper: Union[Mapper[_T], Type[_T]]
-) -> Mapper[_T]:
- # can't get mypy to see an overload for this
- insp = inspection.inspect(class_or_mapper, False)
- if insp is not None:
- return insp.mapper # type: ignore
- else:
- assert isinstance(class_or_mapper, type)
- raise exc.UnmappedClassError(class_or_mapper)
-
-
-def _mapper_or_none(
- entity: Union[Type[_T], _InternalEntityType[_T]]
-) -> Optional[Mapper[_T]]:
- """Return the :class:`_orm.Mapper` for the given class or None if the
- class is not mapped.
- """
-
- # can't get mypy to see an overload for this
- insp = inspection.inspect(entity, False)
- if insp is not None:
- return insp.mapper # type: ignore
- else:
- return None
-
-
-def _is_mapped_class(entity: Any) -> bool:
- """Return True if the given object is a mapped class,
- :class:`_orm.Mapper`, or :class:`.AliasedClass`.
- """
-
- insp = inspection.inspect(entity, False)
- return (
- insp is not None
- and not insp.is_clause_element
- and (insp.is_mapper or insp.is_aliased_class)
- )
-
-
-def _is_aliased_class(entity: Any) -> bool:
- insp = inspection.inspect(entity, False)
- return insp is not None and getattr(insp, "is_aliased_class", False)
-
-
-@no_type_check
-def _entity_descriptor(entity: _EntityType[Any], key: str) -> Any:
- """Return a class attribute given an entity and string name.
-
- May return :class:`.InstrumentedAttribute` or user-defined
- attribute.
-
- """
- insp = inspection.inspect(entity)
- if insp.is_selectable:
- description = entity
- entity = insp.c
- elif insp.is_aliased_class:
- entity = insp.entity
- description = entity
- elif hasattr(insp, "mapper"):
- description = entity = insp.mapper.class_
- else:
- description = entity
-
- try:
- return getattr(entity, key)
- except AttributeError as err:
- raise sa_exc.InvalidRequestError(
- "Entity '%s' has no property '%s'" % (description, key)
- ) from err
-
-
-if TYPE_CHECKING:
-
- def _state_mapper(state: InstanceState[_O]) -> Mapper[_O]: ...
-
-else:
- _state_mapper = util.dottedgetter("manager.mapper")
-
-
-def _inspect_mapped_class(
- class_: Type[_O], configure: bool = False
-) -> Optional[Mapper[_O]]:
- try:
- class_manager = opt_manager_of_class(class_)
- if class_manager is None or not class_manager.is_mapped:
- return None
- mapper = class_manager.mapper
- except exc.NO_STATE:
- return None
- else:
- if configure:
- mapper._check_configure()
- return mapper
-
-
-def _parse_mapper_argument(arg: Union[Mapper[_O], Type[_O]]) -> Mapper[_O]:
- insp = inspection.inspect(arg, raiseerr=False)
- if insp_is_mapper(insp):
- return insp
-
- raise sa_exc.ArgumentError(f"Mapper or mapped class expected, got {arg!r}")
-
-
-def class_mapper(class_: Type[_O], configure: bool = True) -> Mapper[_O]:
- """Given a class, return the primary :class:`_orm.Mapper` associated
- with the key.
-
- Raises :exc:`.UnmappedClassError` if no mapping is configured
- on the given class, or :exc:`.ArgumentError` if a non-class
- object is passed.
-
- Equivalent functionality is available via the :func:`_sa.inspect`
- function as::
-
- inspect(some_mapped_class)
-
- Using the inspection system will raise
- :class:`sqlalchemy.exc.NoInspectionAvailable` if the class is not mapped.
-
- """
- mapper = _inspect_mapped_class(class_, configure=configure)
- if mapper is None:
- if not isinstance(class_, type):
- raise sa_exc.ArgumentError(
- "Class object expected, got '%r'." % (class_,)
- )
- raise exc.UnmappedClassError(class_)
- else:
- return mapper
-
-
-class InspectionAttr:
- """A base class applied to all ORM objects and attributes that are
- related to things that can be returned by the :func:`_sa.inspect` function.
-
- The attributes defined here allow the usage of simple boolean
- checks to test basic facts about the object returned.
-
- While the boolean checks here are basically the same as using
- the Python isinstance() function, the flags here can be used without
- the need to import all of these classes, and also such that
- the SQLAlchemy class system can change while leaving the flags
- here intact for forwards-compatibility.
-
- """
-
- __slots__: Tuple[str, ...] = ()
-
- is_selectable = False
- """Return True if this object is an instance of
- :class:`_expression.Selectable`."""
-
- is_aliased_class = False
- """True if this object is an instance of :class:`.AliasedClass`."""
-
- is_instance = False
- """True if this object is an instance of :class:`.InstanceState`."""
-
- is_mapper = False
- """True if this object is an instance of :class:`_orm.Mapper`."""
-
- is_bundle = False
- """True if this object is an instance of :class:`.Bundle`."""
-
- is_property = False
- """True if this object is an instance of :class:`.MapperProperty`."""
-
- is_attribute = False
- """True if this object is a Python :term:`descriptor`.
-
- This can refer to one of many types. Usually a
- :class:`.QueryableAttribute` which handles attributes events on behalf
- of a :class:`.MapperProperty`. But can also be an extension type
- such as :class:`.AssociationProxy` or :class:`.hybrid_property`.
- The :attr:`.InspectionAttr.extension_type` will refer to a constant
- identifying the specific subtype.
-
- .. seealso::
-
- :attr:`_orm.Mapper.all_orm_descriptors`
-
- """
-
- _is_internal_proxy = False
- """True if this object is an internal proxy object.
-
- .. versionadded:: 1.2.12
-
- """
-
- is_clause_element = False
- """True if this object is an instance of
- :class:`_expression.ClauseElement`."""
-
- extension_type: InspectionAttrExtensionType = NotExtension.NOT_EXTENSION
- """The extension type, if any.
- Defaults to :attr:`.interfaces.NotExtension.NOT_EXTENSION`
-
- .. seealso::
-
- :class:`.HybridExtensionType`
-
- :class:`.AssociationProxyExtensionType`
-
- """
-
-
-class InspectionAttrInfo(InspectionAttr):
- """Adds the ``.info`` attribute to :class:`.InspectionAttr`.
-
- The rationale for :class:`.InspectionAttr` vs. :class:`.InspectionAttrInfo`
- is that the former is compatible as a mixin for classes that specify
- ``__slots__``; this is essentially an implementation artifact.
-
- """
-
- __slots__ = ()
-
- @util.ro_memoized_property
- def info(self) -> _InfoType:
- """Info dictionary associated with the object, allowing user-defined
- data to be associated with this :class:`.InspectionAttr`.
-
- The dictionary is generated when first accessed. Alternatively,
- it can be specified as a constructor argument to the
- :func:`.column_property`, :func:`_orm.relationship`, or
- :func:`.composite`
- functions.
-
- .. seealso::
-
- :attr:`.QueryableAttribute.info`
-
- :attr:`.SchemaItem.info`
-
- """
- return {}
-
-
-class SQLORMOperations(SQLCoreOperations[_T_co], TypingOnly):
- __slots__ = ()
-
- if typing.TYPE_CHECKING:
-
- def of_type(
- self, class_: _EntityType[Any]
- ) -> PropComparator[_T_co]: ...
-
- def and_(
- self, *criteria: _ColumnExpressionArgument[bool]
- ) -> PropComparator[bool]: ...
-
- def any( # noqa: A001
- self,
- criterion: Optional[_ColumnExpressionArgument[bool]] = None,
- **kwargs: Any,
- ) -> ColumnElement[bool]: ...
-
- def has(
- self,
- criterion: Optional[_ColumnExpressionArgument[bool]] = None,
- **kwargs: Any,
- ) -> ColumnElement[bool]: ...
-
-
-class ORMDescriptor(Generic[_T_co], TypingOnly):
- """Represent any Python descriptor that provides a SQL expression
- construct at the class level."""
-
- __slots__ = ()
-
- if typing.TYPE_CHECKING:
-
- @overload
- def __get__(
- self, instance: Any, owner: Literal[None]
- ) -> ORMDescriptor[_T_co]: ...
-
- @overload
- def __get__(
- self, instance: Literal[None], owner: Any
- ) -> SQLCoreOperations[_T_co]: ...
-
- @overload
- def __get__(self, instance: object, owner: Any) -> _T_co: ...
-
- def __get__(
- self, instance: object, owner: Any
- ) -> Union[ORMDescriptor[_T_co], SQLCoreOperations[_T_co], _T_co]: ...
-
-
-class _MappedAnnotationBase(Generic[_T_co], TypingOnly):
- """common class for Mapped and similar ORM container classes.
-
- these are classes that can appear on the left side of an ORM declarative
- mapping, containing a mapped class or in some cases a collection
- surrounding a mapped class.
-
- """
-
- __slots__ = ()
-
-
-class SQLORMExpression(
- SQLORMOperations[_T_co], SQLColumnExpression[_T_co], TypingOnly
-):
- """A type that may be used to indicate any ORM-level attribute or
- object that acts in place of one, in the context of SQL expression
- construction.
-
- :class:`.SQLORMExpression` extends from the Core
- :class:`.SQLColumnExpression` to add additional SQL methods that are ORM
- specific, such as :meth:`.PropComparator.of_type`, and is part of the bases
- for :class:`.InstrumentedAttribute`. It may be used in :pep:`484` typing to
- indicate arguments or return values that should behave as ORM-level
- attribute expressions.
-
- .. versionadded:: 2.0.0b4
-
-
- """
-
- __slots__ = ()
-
-
-class Mapped(
- SQLORMExpression[_T_co],
- ORMDescriptor[_T_co],
- _MappedAnnotationBase[_T_co],
- roles.DDLConstraintColumnRole,
-):
- """Represent an ORM mapped attribute on a mapped class.
-
- This class represents the complete descriptor interface for any class
- attribute that will have been :term:`instrumented` by the ORM
- :class:`_orm.Mapper` class. Provides appropriate information to type
- checkers such as pylance and mypy so that ORM-mapped attributes
- are correctly typed.
-
- The most prominent use of :class:`_orm.Mapped` is in
- the :ref:`Declarative Mapping <orm_explicit_declarative_base>` form
- of :class:`_orm.Mapper` configuration, where used explicitly it drives
- the configuration of ORM attributes such as :func:`_orm.mapped_class`
- and :func:`_orm.relationship`.
-
- .. seealso::
-
- :ref:`orm_explicit_declarative_base`
-
- :ref:`orm_declarative_table`
-
- .. 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.
-
- .. versionadded:: 1.4
-
-
- """
-
- __slots__ = ()
-
- if typing.TYPE_CHECKING:
-
- @overload
- def __get__(
- self, instance: None, owner: Any
- ) -> InstrumentedAttribute[_T_co]: ...
-
- @overload
- def __get__(self, instance: object, owner: Any) -> _T_co: ...
-
- def __get__(
- self, instance: Optional[object], owner: Any
- ) -> Union[InstrumentedAttribute[_T_co], _T_co]: ...
-
- @classmethod
- def _empty_constructor(cls, arg1: Any) -> Mapped[_T_co]: ...
-
- def __set__(
- self, instance: Any, value: Union[SQLCoreOperations[_T_co], _T_co]
- ) -> None: ...
-
- def __delete__(self, instance: Any) -> None: ...
-
-
-class _MappedAttribute(Generic[_T_co], TypingOnly):
- """Mixin for attributes which should be replaced by mapper-assigned
- attributes.
-
- """
-
- __slots__ = ()
-
-
-class _DeclarativeMapped(Mapped[_T_co], _MappedAttribute[_T_co]):
- """Mixin for :class:`.MapperProperty` subclasses that allows them to
- be compatible with ORM-annotated declarative mappings.
-
- """
-
- __slots__ = ()
-
- # MappedSQLExpression, Relationship, Composite etc. dont actually do
- # SQL expression behavior. yet there is code that compares them with
- # __eq__(), __ne__(), etc. Since #8847 made Mapped even more full
- # featured including ColumnOperators, we need to have those methods
- # be no-ops for these objects, so return NotImplemented to fall back
- # to normal comparison behavior.
- def operate(self, op: OperatorType, *other: Any, **kwargs: Any) -> Any:
- return NotImplemented
-
- __sa_operate__ = operate
-
- def reverse_operate(
- self, op: OperatorType, other: Any, **kwargs: Any
- ) -> Any:
- return NotImplemented
-
-
-class DynamicMapped(_MappedAnnotationBase[_T_co]):
- """Represent the ORM mapped attribute type for a "dynamic" relationship.
-
- The :class:`_orm.DynamicMapped` type annotation may be used in an
- :ref:`Annotated Declarative Table <orm_declarative_mapped_column>` mapping
- to indicate that the ``lazy="dynamic"`` loader strategy should be used
- for a particular :func:`_orm.relationship`.
-
- .. legacy:: The "dynamic" lazy loader strategy is the legacy form of what
- is now the "write_only" strategy described in the section
- :ref:`write_only_relationship`.
-
- E.g.::
-
- class User(Base):
- __tablename__ = "user"
- id: Mapped[int] = mapped_column(primary_key=True)
- addresses: DynamicMapped[Address] = relationship(
- cascade="all,delete-orphan"
- )
-
- See the section :ref:`dynamic_relationship` for background.
-
- .. versionadded:: 2.0
-
- .. seealso::
-
- :ref:`dynamic_relationship` - complete background
-
- :class:`.WriteOnlyMapped` - fully 2.0 style version
-
- """
-
- __slots__ = ()
-
- if TYPE_CHECKING:
-
- @overload
- def __get__(
- self, instance: None, owner: Any
- ) -> InstrumentedAttribute[_T_co]: ...
-
- @overload
- def __get__(
- self, instance: object, owner: Any
- ) -> AppenderQuery[_T_co]: ...
-
- def __get__(
- self, instance: Optional[object], owner: Any
- ) -> Union[InstrumentedAttribute[_T_co], AppenderQuery[_T_co]]: ...
-
- def __set__(
- self, instance: Any, value: typing.Collection[_T_co]
- ) -> None: ...
-
-
-class WriteOnlyMapped(_MappedAnnotationBase[_T_co]):
- """Represent the ORM mapped attribute type for a "write only" relationship.
-
- The :class:`_orm.WriteOnlyMapped` type annotation may be used in an
- :ref:`Annotated Declarative Table <orm_declarative_mapped_column>` mapping
- to indicate that the ``lazy="write_only"`` loader strategy should be used
- for a particular :func:`_orm.relationship`.
-
- E.g.::
-
- class User(Base):
- __tablename__ = "user"
- id: Mapped[int] = mapped_column(primary_key=True)
- addresses: WriteOnlyMapped[Address] = relationship(
- cascade="all,delete-orphan"
- )
-
- See the section :ref:`write_only_relationship` for background.
-
- .. versionadded:: 2.0
-
- .. seealso::
-
- :ref:`write_only_relationship` - complete background
-
- :class:`.DynamicMapped` - includes legacy :class:`_orm.Query` support
-
- """
-
- __slots__ = ()
-
- if TYPE_CHECKING:
-
- @overload
- def __get__(
- self, instance: None, owner: Any
- ) -> InstrumentedAttribute[_T_co]: ...
-
- @overload
- def __get__(
- self, instance: object, owner: Any
- ) -> WriteOnlyCollection[_T_co]: ...
-
- def __get__(
- self, instance: Optional[object], owner: Any
- ) -> Union[
- InstrumentedAttribute[_T_co], WriteOnlyCollection[_T_co]
- ]: ...
-
- def __set__(
- self, instance: Any, value: typing.Collection[_T_co]
- ) -> None: ...
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/bulk_persistence.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/bulk_persistence.py
deleted file mode 100644
index 5d2558d..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/bulk_persistence.py
+++ /dev/null
@@ -1,2048 +0,0 @@
-# orm/bulk_persistence.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
-# mypy: ignore-errors
-
-
-"""additional ORM persistence classes related to "bulk" operations,
-specifically outside of the flush() process.
-
-"""
-
-from __future__ import annotations
-
-from typing import Any
-from typing import cast
-from typing import Dict
-from typing import Iterable
-from typing import Optional
-from typing import overload
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from . import attributes
-from . import context
-from . import evaluator
-from . import exc as orm_exc
-from . import loading
-from . import persistence
-from .base import NO_VALUE
-from .context import AbstractORMCompileState
-from .context import FromStatement
-from .context import ORMFromStatementCompileState
-from .context import QueryContext
-from .. import exc as sa_exc
-from .. import util
-from ..engine import Dialect
-from ..engine import result as _result
-from ..sql import coercions
-from ..sql import dml
-from ..sql import expression
-from ..sql import roles
-from ..sql import select
-from ..sql import sqltypes
-from ..sql.base import _entity_namespace_key
-from ..sql.base import CompileState
-from ..sql.base import Options
-from ..sql.dml import DeleteDMLState
-from ..sql.dml import InsertDMLState
-from ..sql.dml import UpdateDMLState
-from ..util import EMPTY_DICT
-from ..util.typing import Literal
-
-if TYPE_CHECKING:
- from ._typing import DMLStrategyArgument
- from ._typing import OrmExecuteOptionsParameter
- from ._typing import SynchronizeSessionArgument
- from .mapper import Mapper
- from .session import _BindArguments
- from .session import ORMExecuteState
- from .session import Session
- from .session import SessionTransaction
- from .state import InstanceState
- from ..engine import Connection
- from ..engine import cursor
- from ..engine.interfaces import _CoreAnyExecuteParams
-
-_O = TypeVar("_O", bound=object)
-
-
-@overload
-def _bulk_insert(
- mapper: Mapper[_O],
- mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
- session_transaction: SessionTransaction,
- isstates: bool,
- return_defaults: bool,
- render_nulls: bool,
- use_orm_insert_stmt: Literal[None] = ...,
- execution_options: Optional[OrmExecuteOptionsParameter] = ...,
-) -> None: ...
-
-
-@overload
-def _bulk_insert(
- mapper: Mapper[_O],
- mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
- session_transaction: SessionTransaction,
- isstates: bool,
- return_defaults: bool,
- render_nulls: bool,
- use_orm_insert_stmt: Optional[dml.Insert] = ...,
- execution_options: Optional[OrmExecuteOptionsParameter] = ...,
-) -> cursor.CursorResult[Any]: ...
-
-
-def _bulk_insert(
- mapper: Mapper[_O],
- mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
- session_transaction: SessionTransaction,
- isstates: bool,
- return_defaults: bool,
- render_nulls: bool,
- use_orm_insert_stmt: Optional[dml.Insert] = None,
- execution_options: Optional[OrmExecuteOptionsParameter] = None,
-) -> Optional[cursor.CursorResult[Any]]:
- base_mapper = mapper.base_mapper
-
- if session_transaction.session.connection_callable:
- raise NotImplementedError(
- "connection_callable / per-instance sharding "
- "not supported in bulk_insert()"
- )
-
- if isstates:
- if return_defaults:
- states = [(state, state.dict) for state in mappings]
- mappings = [dict_ for (state, dict_) in states]
- else:
- mappings = [state.dict for state in mappings]
- else:
- mappings = [dict(m) for m in mappings]
- _expand_composites(mapper, mappings)
-
- connection = session_transaction.connection(base_mapper)
-
- return_result: Optional[cursor.CursorResult[Any]] = None
-
- mappers_to_run = [
- (table, mp)
- for table, mp in base_mapper._sorted_tables.items()
- if table in mapper._pks_by_table
- ]
-
- if return_defaults:
- # not used by new-style bulk inserts, only used for legacy
- bookkeeping = True
- elif len(mappers_to_run) > 1:
- # if we have more than one table, mapper to run where we will be
- # either horizontally splicing, or copying values between tables,
- # we need the "bookkeeping" / deterministic returning order
- bookkeeping = True
- else:
- bookkeeping = False
-
- for table, super_mapper in mappers_to_run:
- # find bindparams in the statement. For bulk, we don't really know if
- # a key in the params applies to a different table since we are
- # potentially inserting for multiple tables here; looking at the
- # bindparam() is a lot more direct. in most cases this will
- # use _generate_cache_key() which is memoized, although in practice
- # the ultimate statement that's executed is probably not the same
- # object so that memoization might not matter much.
- extra_bp_names = (
- [
- b.key
- for b in use_orm_insert_stmt._get_embedded_bindparams()
- if b.key in mappings[0]
- ]
- if use_orm_insert_stmt is not None
- else ()
- )
-
- records = (
- (
- None,
- state_dict,
- params,
- mapper,
- connection,
- value_params,
- has_all_pks,
- has_all_defaults,
- )
- for (
- state,
- state_dict,
- params,
- mp,
- conn,
- value_params,
- has_all_pks,
- has_all_defaults,
- ) in persistence._collect_insert_commands(
- table,
- ((None, mapping, mapper, connection) for mapping in mappings),
- bulk=True,
- return_defaults=bookkeeping,
- render_nulls=render_nulls,
- include_bulk_keys=extra_bp_names,
- )
- )
-
- result = persistence._emit_insert_statements(
- base_mapper,
- None,
- super_mapper,
- table,
- records,
- bookkeeping=bookkeeping,
- use_orm_insert_stmt=use_orm_insert_stmt,
- execution_options=execution_options,
- )
- if use_orm_insert_stmt is not None:
- if not use_orm_insert_stmt._returning or return_result is None:
- return_result = result
- elif result.returns_rows:
- assert bookkeeping
- return_result = return_result.splice_horizontally(result)
-
- if return_defaults and isstates:
- identity_cls = mapper._identity_class
- identity_props = [p.key for p in mapper._identity_key_props]
- for state, dict_ in states:
- state.key = (
- identity_cls,
- tuple([dict_[key] for key in identity_props]),
- )
-
- if use_orm_insert_stmt is not None:
- assert return_result is not None
- return return_result
-
-
-@overload
-def _bulk_update(
- mapper: Mapper[Any],
- mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
- session_transaction: SessionTransaction,
- isstates: bool,
- update_changed_only: bool,
- use_orm_update_stmt: Literal[None] = ...,
- enable_check_rowcount: bool = True,
-) -> None: ...
-
-
-@overload
-def _bulk_update(
- mapper: Mapper[Any],
- mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
- session_transaction: SessionTransaction,
- isstates: bool,
- update_changed_only: bool,
- use_orm_update_stmt: Optional[dml.Update] = ...,
- enable_check_rowcount: bool = True,
-) -> _result.Result[Any]: ...
-
-
-def _bulk_update(
- mapper: Mapper[Any],
- mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
- session_transaction: SessionTransaction,
- isstates: bool,
- update_changed_only: bool,
- use_orm_update_stmt: Optional[dml.Update] = None,
- enable_check_rowcount: bool = True,
-) -> Optional[_result.Result[Any]]:
- base_mapper = mapper.base_mapper
-
- search_keys = mapper._primary_key_propkeys
- if mapper._version_id_prop:
- search_keys = {mapper._version_id_prop.key}.union(search_keys)
-
- def _changed_dict(mapper, state):
- return {
- k: v
- for k, v in state.dict.items()
- if k in state.committed_state or k in search_keys
- }
-
- if isstates:
- if update_changed_only:
- mappings = [_changed_dict(mapper, state) for state in mappings]
- else:
- mappings = [state.dict for state in mappings]
- else:
- mappings = [dict(m) for m in mappings]
- _expand_composites(mapper, mappings)
-
- if session_transaction.session.connection_callable:
- raise NotImplementedError(
- "connection_callable / per-instance sharding "
- "not supported in bulk_update()"
- )
-
- connection = session_transaction.connection(base_mapper)
-
- # find bindparams in the statement. see _bulk_insert for similar
- # notes for the insert case
- extra_bp_names = (
- [
- b.key
- for b in use_orm_update_stmt._get_embedded_bindparams()
- if b.key in mappings[0]
- ]
- if use_orm_update_stmt is not None
- else ()
- )
-
- for table, super_mapper in base_mapper._sorted_tables.items():
- if not mapper.isa(super_mapper) or table not in mapper._pks_by_table:
- continue
-
- records = persistence._collect_update_commands(
- None,
- table,
- (
- (
- None,
- mapping,
- mapper,
- connection,
- (
- mapping[mapper._version_id_prop.key]
- if mapper._version_id_prop
- else None
- ),
- )
- for mapping in mappings
- ),
- bulk=True,
- use_orm_update_stmt=use_orm_update_stmt,
- include_bulk_keys=extra_bp_names,
- )
- persistence._emit_update_statements(
- base_mapper,
- None,
- super_mapper,
- table,
- records,
- bookkeeping=False,
- use_orm_update_stmt=use_orm_update_stmt,
- enable_check_rowcount=enable_check_rowcount,
- )
-
- if use_orm_update_stmt is not None:
- return _result.null_result()
-
-
-def _expand_composites(mapper, mappings):
- composite_attrs = mapper.composites
- if not composite_attrs:
- return
-
- composite_keys = set(composite_attrs.keys())
- populators = {
- key: composite_attrs[key]._populate_composite_bulk_save_mappings_fn()
- for key in composite_keys
- }
- for mapping in mappings:
- for key in composite_keys.intersection(mapping):
- populators[key](mapping)
-
-
-class ORMDMLState(AbstractORMCompileState):
- is_dml_returning = True
- from_statement_ctx: Optional[ORMFromStatementCompileState] = None
-
- @classmethod
- def _get_orm_crud_kv_pairs(
- cls, mapper, statement, kv_iterator, needs_to_be_cacheable
- ):
- core_get_crud_kv_pairs = UpdateDMLState._get_crud_kv_pairs
-
- for k, v in kv_iterator:
- k = coercions.expect(roles.DMLColumnRole, k)
-
- if isinstance(k, str):
- desc = _entity_namespace_key(mapper, k, default=NO_VALUE)
- if desc is NO_VALUE:
- yield (
- coercions.expect(roles.DMLColumnRole, k),
- (
- coercions.expect(
- roles.ExpressionElementRole,
- v,
- type_=sqltypes.NullType(),
- is_crud=True,
- )
- if needs_to_be_cacheable
- else v
- ),
- )
- else:
- yield from core_get_crud_kv_pairs(
- statement,
- desc._bulk_update_tuples(v),
- needs_to_be_cacheable,
- )
- elif "entity_namespace" in k._annotations:
- k_anno = k._annotations
- attr = _entity_namespace_key(
- k_anno["entity_namespace"], k_anno["proxy_key"]
- )
- yield from core_get_crud_kv_pairs(
- statement,
- attr._bulk_update_tuples(v),
- needs_to_be_cacheable,
- )
- else:
- yield (
- k,
- (
- v
- if not needs_to_be_cacheable
- else coercions.expect(
- roles.ExpressionElementRole,
- v,
- type_=sqltypes.NullType(),
- is_crud=True,
- )
- ),
- )
-
- @classmethod
- def _get_multi_crud_kv_pairs(cls, statement, kv_iterator):
- plugin_subject = statement._propagate_attrs["plugin_subject"]
-
- if not plugin_subject or not plugin_subject.mapper:
- return UpdateDMLState._get_multi_crud_kv_pairs(
- statement, kv_iterator
- )
-
- return [
- dict(
- cls._get_orm_crud_kv_pairs(
- plugin_subject.mapper, statement, value_dict.items(), False
- )
- )
- for value_dict in kv_iterator
- ]
-
- @classmethod
- def _get_crud_kv_pairs(cls, statement, kv_iterator, needs_to_be_cacheable):
- assert (
- needs_to_be_cacheable
- ), "no test coverage for needs_to_be_cacheable=False"
-
- plugin_subject = statement._propagate_attrs["plugin_subject"]
-
- if not plugin_subject or not plugin_subject.mapper:
- return UpdateDMLState._get_crud_kv_pairs(
- statement, kv_iterator, needs_to_be_cacheable
- )
-
- return list(
- cls._get_orm_crud_kv_pairs(
- plugin_subject.mapper,
- statement,
- kv_iterator,
- needs_to_be_cacheable,
- )
- )
-
- @classmethod
- def get_entity_description(cls, statement):
- ext_info = statement.table._annotations["parententity"]
- mapper = ext_info.mapper
- if ext_info.is_aliased_class:
- _label_name = ext_info.name
- else:
- _label_name = mapper.class_.__name__
-
- return {
- "name": _label_name,
- "type": mapper.class_,
- "expr": ext_info.entity,
- "entity": ext_info.entity,
- "table": mapper.local_table,
- }
-
- @classmethod
- def get_returning_column_descriptions(cls, statement):
- def _ent_for_col(c):
- return c._annotations.get("parententity", None)
-
- def _attr_for_col(c, ent):
- if ent is None:
- return c
- proxy_key = c._annotations.get("proxy_key", None)
- if not proxy_key:
- return c
- else:
- return getattr(ent.entity, proxy_key, c)
-
- return [
- {
- "name": c.key,
- "type": c.type,
- "expr": _attr_for_col(c, ent),
- "aliased": ent.is_aliased_class,
- "entity": ent.entity,
- }
- for c, ent in [
- (c, _ent_for_col(c)) for c in statement._all_selected_columns
- ]
- ]
-
- def _setup_orm_returning(
- self,
- compiler,
- orm_level_statement,
- dml_level_statement,
- dml_mapper,
- *,
- use_supplemental_cols=True,
- ):
- """establish ORM column handlers for an INSERT, UPDATE, or DELETE
- which uses explicit returning().
-
- called within compilation level create_for_statement.
-
- The _return_orm_returning() method then receives the Result
- after the statement was executed, and applies ORM loading to the
- state that we first established here.
-
- """
-
- if orm_level_statement._returning:
- fs = FromStatement(
- orm_level_statement._returning,
- dml_level_statement,
- _adapt_on_names=False,
- )
- fs = fs.execution_options(**orm_level_statement._execution_options)
- fs = fs.options(*orm_level_statement._with_options)
- self.select_statement = fs
- self.from_statement_ctx = fsc = (
- ORMFromStatementCompileState.create_for_statement(fs, compiler)
- )
- fsc.setup_dml_returning_compile_state(dml_mapper)
-
- dml_level_statement = dml_level_statement._generate()
- dml_level_statement._returning = ()
-
- cols_to_return = [c for c in fsc.primary_columns if c is not None]
-
- # since we are splicing result sets together, make sure there
- # are columns of some kind returned in each result set
- if not cols_to_return:
- cols_to_return.extend(dml_mapper.primary_key)
-
- if use_supplemental_cols:
- dml_level_statement = dml_level_statement.return_defaults(
- # this is a little weird looking, but by passing
- # primary key as the main list of cols, this tells
- # return_defaults to omit server-default cols (and
- # actually all cols, due to some weird thing we should
- # clean up in crud.py).
- # Since we have cols_to_return, just return what we asked
- # for (plus primary key, which ORM persistence needs since
- # we likely set bookkeeping=True here, which is another
- # whole thing...). We dont want to clutter the
- # statement up with lots of other cols the user didn't
- # ask for. see #9685
- *dml_mapper.primary_key,
- supplemental_cols=cols_to_return,
- )
- else:
- dml_level_statement = dml_level_statement.returning(
- *cols_to_return
- )
-
- return dml_level_statement
-
- @classmethod
- def _return_orm_returning(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- result,
- ):
- execution_context = result.context
- compile_state = execution_context.compiled.compile_state
-
- if (
- compile_state.from_statement_ctx
- and not compile_state.from_statement_ctx.compile_options._is_star
- ):
- load_options = execution_options.get(
- "_sa_orm_load_options", QueryContext.default_load_options
- )
-
- querycontext = QueryContext(
- compile_state.from_statement_ctx,
- compile_state.select_statement,
- params,
- session,
- load_options,
- execution_options,
- bind_arguments,
- )
- return loading.instances(result, querycontext)
- else:
- return result
-
-
-class BulkUDCompileState(ORMDMLState):
- class default_update_options(Options):
- _dml_strategy: DMLStrategyArgument = "auto"
- _synchronize_session: SynchronizeSessionArgument = "auto"
- _can_use_returning: bool = False
- _is_delete_using: bool = False
- _is_update_from: bool = False
- _autoflush: bool = True
- _subject_mapper: Optional[Mapper[Any]] = None
- _resolved_values = EMPTY_DICT
- _eval_condition = None
- _matched_rows = None
- _identity_token = None
-
- @classmethod
- def can_use_returning(
- cls,
- dialect: Dialect,
- mapper: Mapper[Any],
- *,
- is_multitable: bool = False,
- is_update_from: bool = False,
- is_delete_using: bool = False,
- is_executemany: bool = False,
- ) -> bool:
- raise NotImplementedError()
-
- @classmethod
- def orm_pre_session_exec(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- is_pre_event,
- ):
- (
- update_options,
- execution_options,
- ) = BulkUDCompileState.default_update_options.from_execution_options(
- "_sa_orm_update_options",
- {
- "synchronize_session",
- "autoflush",
- "identity_token",
- "is_delete_using",
- "is_update_from",
- "dml_strategy",
- },
- execution_options,
- statement._execution_options,
- )
- bind_arguments["clause"] = statement
- try:
- plugin_subject = statement._propagate_attrs["plugin_subject"]
- except KeyError:
- assert False, "statement had 'orm' plugin but no plugin_subject"
- else:
- if plugin_subject:
- bind_arguments["mapper"] = plugin_subject.mapper
- update_options += {"_subject_mapper": plugin_subject.mapper}
-
- if "parententity" not in statement.table._annotations:
- update_options += {"_dml_strategy": "core_only"}
- elif not isinstance(params, list):
- if update_options._dml_strategy == "auto":
- update_options += {"_dml_strategy": "orm"}
- elif update_options._dml_strategy == "bulk":
- raise sa_exc.InvalidRequestError(
- 'Can\'t use "bulk" ORM insert strategy without '
- "passing separate parameters"
- )
- else:
- if update_options._dml_strategy == "auto":
- update_options += {"_dml_strategy": "bulk"}
-
- sync = update_options._synchronize_session
- if sync is not None:
- if sync not in ("auto", "evaluate", "fetch", False):
- raise sa_exc.ArgumentError(
- "Valid strategies for session synchronization "
- "are 'auto', 'evaluate', 'fetch', False"
- )
- if update_options._dml_strategy == "bulk" and sync == "fetch":
- raise sa_exc.InvalidRequestError(
- "The 'fetch' synchronization strategy is not available "
- "for 'bulk' ORM updates (i.e. multiple parameter sets)"
- )
-
- if not is_pre_event:
- if update_options._autoflush:
- session._autoflush()
-
- if update_options._dml_strategy == "orm":
- if update_options._synchronize_session == "auto":
- update_options = cls._do_pre_synchronize_auto(
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- update_options,
- )
- elif update_options._synchronize_session == "evaluate":
- update_options = cls._do_pre_synchronize_evaluate(
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- update_options,
- )
- elif update_options._synchronize_session == "fetch":
- update_options = cls._do_pre_synchronize_fetch(
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- update_options,
- )
- elif update_options._dml_strategy == "bulk":
- if update_options._synchronize_session == "auto":
- update_options += {"_synchronize_session": "evaluate"}
-
- # indicators from the "pre exec" step that are then
- # added to the DML statement, which will also be part of the cache
- # key. The compile level create_for_statement() method will then
- # consume these at compiler time.
- statement = statement._annotate(
- {
- "synchronize_session": update_options._synchronize_session,
- "is_delete_using": update_options._is_delete_using,
- "is_update_from": update_options._is_update_from,
- "dml_strategy": update_options._dml_strategy,
- "can_use_returning": update_options._can_use_returning,
- }
- )
-
- return (
- statement,
- util.immutabledict(execution_options).union(
- {"_sa_orm_update_options": update_options}
- ),
- )
-
- @classmethod
- def orm_setup_cursor_result(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- result,
- ):
- # this stage of the execution is called after the
- # do_orm_execute event hook. meaning for an extension like
- # horizontal sharding, this step happens *within* the horizontal
- # sharding event handler which calls session.execute() re-entrantly
- # and will occur for each backend individually.
- # the sharding extension then returns its own merged result from the
- # individual ones we return here.
-
- update_options = execution_options["_sa_orm_update_options"]
- if update_options._dml_strategy == "orm":
- if update_options._synchronize_session == "evaluate":
- cls._do_post_synchronize_evaluate(
- session, statement, result, update_options
- )
- elif update_options._synchronize_session == "fetch":
- cls._do_post_synchronize_fetch(
- session, statement, result, update_options
- )
- elif update_options._dml_strategy == "bulk":
- if update_options._synchronize_session == "evaluate":
- cls._do_post_synchronize_bulk_evaluate(
- session, params, result, update_options
- )
- return result
-
- return cls._return_orm_returning(
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- result,
- )
-
- @classmethod
- def _adjust_for_extra_criteria(cls, global_attributes, ext_info):
- """Apply extra criteria filtering.
-
- For all distinct single-table-inheritance mappers represented in the
- table being updated or deleted, produce additional WHERE criteria such
- that only the appropriate subtypes are selected from the total results.
-
- Additionally, add WHERE criteria originating from LoaderCriteriaOptions
- collected from the statement.
-
- """
-
- return_crit = ()
-
- adapter = ext_info._adapter if ext_info.is_aliased_class else None
-
- if (
- "additional_entity_criteria",
- ext_info.mapper,
- ) in global_attributes:
- return_crit += tuple(
- ae._resolve_where_criteria(ext_info)
- for ae in global_attributes[
- ("additional_entity_criteria", ext_info.mapper)
- ]
- if ae.include_aliases or ae.entity is ext_info
- )
-
- if ext_info.mapper._single_table_criterion is not None:
- return_crit += (ext_info.mapper._single_table_criterion,)
-
- if adapter:
- return_crit = tuple(adapter.traverse(crit) for crit in return_crit)
-
- return return_crit
-
- @classmethod
- def _interpret_returning_rows(cls, mapper, rows):
- """translate from local inherited table columns to base mapper
- primary key columns.
-
- Joined inheritance mappers always establish the primary key in terms of
- the base table. When we UPDATE a sub-table, we can only get
- RETURNING for the sub-table's columns.
-
- Here, we create a lookup from the local sub table's primary key
- columns to the base table PK columns so that we can get identity
- key values from RETURNING that's against the joined inheritance
- sub-table.
-
- the complexity here is to support more than one level deep of
- inheritance, where we have to link columns to each other across
- the inheritance hierarchy.
-
- """
-
- if mapper.local_table is not mapper.base_mapper.local_table:
- return rows
-
- # this starts as a mapping of
- # local_pk_col: local_pk_col.
- # we will then iteratively rewrite the "value" of the dict with
- # each successive superclass column
- local_pk_to_base_pk = {pk: pk for pk in mapper.local_table.primary_key}
-
- for mp in mapper.iterate_to_root():
- if mp.inherits is None:
- break
- elif mp.local_table is mp.inherits.local_table:
- continue
-
- t_to_e = dict(mp._table_to_equated[mp.inherits.local_table])
- col_to_col = {sub_pk: super_pk for super_pk, sub_pk in t_to_e[mp]}
- for pk, super_ in local_pk_to_base_pk.items():
- local_pk_to_base_pk[pk] = col_to_col[super_]
-
- lookup = {
- local_pk_to_base_pk[lpk]: idx
- for idx, lpk in enumerate(mapper.local_table.primary_key)
- }
- primary_key_convert = [
- lookup[bpk] for bpk in mapper.base_mapper.primary_key
- ]
- return [tuple(row[idx] for idx in primary_key_convert) for row in rows]
-
- @classmethod
- def _get_matched_objects_on_criteria(cls, update_options, states):
- mapper = update_options._subject_mapper
- eval_condition = update_options._eval_condition
-
- raw_data = [
- (state.obj(), state, state.dict)
- for state in states
- if state.mapper.isa(mapper) and not state.expired
- ]
-
- identity_token = update_options._identity_token
- if identity_token is not None:
- raw_data = [
- (obj, state, dict_)
- for obj, state, dict_ in raw_data
- if state.identity_token == identity_token
- ]
-
- result = []
- for obj, state, dict_ in raw_data:
- evaled_condition = eval_condition(obj)
-
- # caution: don't use "in ()" or == here, _EXPIRE_OBJECT
- # evaluates as True for all comparisons
- if (
- evaled_condition is True
- or evaled_condition is evaluator._EXPIRED_OBJECT
- ):
- result.append(
- (
- obj,
- state,
- dict_,
- evaled_condition is evaluator._EXPIRED_OBJECT,
- )
- )
- return result
-
- @classmethod
- def _eval_condition_from_statement(cls, update_options, statement):
- mapper = update_options._subject_mapper
- target_cls = mapper.class_
-
- evaluator_compiler = evaluator._EvaluatorCompiler(target_cls)
- crit = ()
- if statement._where_criteria:
- crit += statement._where_criteria
-
- global_attributes = {}
- for opt in statement._with_options:
- if opt._is_criteria_option:
- opt.get_global_criteria(global_attributes)
-
- if global_attributes:
- crit += cls._adjust_for_extra_criteria(global_attributes, mapper)
-
- if crit:
- eval_condition = evaluator_compiler.process(*crit)
- else:
- # workaround for mypy https://github.com/python/mypy/issues/14027
- def _eval_condition(obj):
- return True
-
- eval_condition = _eval_condition
-
- return eval_condition
-
- @classmethod
- def _do_pre_synchronize_auto(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- update_options,
- ):
- """setup auto sync strategy
-
-
- "auto" checks if we can use "evaluate" first, then falls back
- to "fetch"
-
- evaluate is vastly more efficient for the common case
- where session is empty, only has a few objects, and the UPDATE
- statement can potentially match thousands/millions of rows.
-
- OTOH more complex criteria that fails to work with "evaluate"
- we would hope usually correlates with fewer net rows.
-
- """
-
- try:
- eval_condition = cls._eval_condition_from_statement(
- update_options, statement
- )
-
- except evaluator.UnevaluatableError:
- pass
- else:
- return update_options + {
- "_eval_condition": eval_condition,
- "_synchronize_session": "evaluate",
- }
-
- update_options += {"_synchronize_session": "fetch"}
- return cls._do_pre_synchronize_fetch(
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- update_options,
- )
-
- @classmethod
- def _do_pre_synchronize_evaluate(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- update_options,
- ):
- try:
- eval_condition = cls._eval_condition_from_statement(
- update_options, statement
- )
-
- except evaluator.UnevaluatableError as err:
- raise sa_exc.InvalidRequestError(
- 'Could not evaluate current criteria in Python: "%s". '
- "Specify 'fetch' or False for the "
- "synchronize_session execution option." % err
- ) from err
-
- return update_options + {
- "_eval_condition": eval_condition,
- }
-
- @classmethod
- def _get_resolved_values(cls, mapper, statement):
- if statement._multi_values:
- return []
- elif statement._ordered_values:
- return list(statement._ordered_values)
- elif statement._values:
- return list(statement._values.items())
- else:
- return []
-
- @classmethod
- def _resolved_keys_as_propnames(cls, mapper, resolved_values):
- values = []
- for k, v in resolved_values:
- if mapper and isinstance(k, expression.ColumnElement):
- try:
- attr = mapper._columntoproperty[k]
- except orm_exc.UnmappedColumnError:
- pass
- else:
- values.append((attr.key, v))
- else:
- raise sa_exc.InvalidRequestError(
- "Attribute name not found, can't be "
- "synchronized back to objects: %r" % k
- )
- return values
-
- @classmethod
- def _do_pre_synchronize_fetch(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- update_options,
- ):
- mapper = update_options._subject_mapper
-
- select_stmt = (
- select(*(mapper.primary_key + (mapper.select_identity_token,)))
- .select_from(mapper)
- .options(*statement._with_options)
- )
- select_stmt._where_criteria = statement._where_criteria
-
- # conditionally run the SELECT statement for pre-fetch, testing the
- # "bind" for if we can use RETURNING or not using the do_orm_execute
- # event. If RETURNING is available, the do_orm_execute event
- # will cancel the SELECT from being actually run.
- #
- # The way this is organized seems strange, why don't we just
- # call can_use_returning() before invoking the statement and get
- # answer?, why does this go through the whole execute phase using an
- # event? Answer: because we are integrating with extensions such
- # as the horizontal sharding extention that "multiplexes" an individual
- # statement run through multiple engines, and it uses
- # do_orm_execute() to do that.
-
- can_use_returning = None
-
- def skip_for_returning(orm_context: ORMExecuteState) -> Any:
- bind = orm_context.session.get_bind(**orm_context.bind_arguments)
- nonlocal can_use_returning
-
- per_bind_result = cls.can_use_returning(
- bind.dialect,
- mapper,
- is_update_from=update_options._is_update_from,
- is_delete_using=update_options._is_delete_using,
- is_executemany=orm_context.is_executemany,
- )
-
- if can_use_returning is not None:
- if can_use_returning != per_bind_result:
- raise sa_exc.InvalidRequestError(
- "For synchronize_session='fetch', can't mix multiple "
- "backends where some support RETURNING and others "
- "don't"
- )
- elif orm_context.is_executemany and not per_bind_result:
- raise sa_exc.InvalidRequestError(
- "For synchronize_session='fetch', can't use multiple "
- "parameter sets in ORM mode, which this backend does not "
- "support with RETURNING"
- )
- else:
- can_use_returning = per_bind_result
-
- if per_bind_result:
- return _result.null_result()
- else:
- return None
-
- result = session.execute(
- select_stmt,
- params,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- _add_event=skip_for_returning,
- )
- matched_rows = result.fetchall()
-
- return update_options + {
- "_matched_rows": matched_rows,
- "_can_use_returning": can_use_returning,
- }
-
-
-@CompileState.plugin_for("orm", "insert")
-class BulkORMInsert(ORMDMLState, InsertDMLState):
- class default_insert_options(Options):
- _dml_strategy: DMLStrategyArgument = "auto"
- _render_nulls: bool = False
- _return_defaults: bool = False
- _subject_mapper: Optional[Mapper[Any]] = None
- _autoflush: bool = True
- _populate_existing: bool = False
-
- select_statement: Optional[FromStatement] = None
-
- @classmethod
- def orm_pre_session_exec(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- is_pre_event,
- ):
- (
- insert_options,
- execution_options,
- ) = BulkORMInsert.default_insert_options.from_execution_options(
- "_sa_orm_insert_options",
- {"dml_strategy", "autoflush", "populate_existing", "render_nulls"},
- execution_options,
- statement._execution_options,
- )
- bind_arguments["clause"] = statement
- try:
- plugin_subject = statement._propagate_attrs["plugin_subject"]
- except KeyError:
- assert False, "statement had 'orm' plugin but no plugin_subject"
- else:
- if plugin_subject:
- bind_arguments["mapper"] = plugin_subject.mapper
- insert_options += {"_subject_mapper": plugin_subject.mapper}
-
- if not params:
- if insert_options._dml_strategy == "auto":
- insert_options += {"_dml_strategy": "orm"}
- elif insert_options._dml_strategy == "bulk":
- raise sa_exc.InvalidRequestError(
- 'Can\'t use "bulk" ORM insert strategy without '
- "passing separate parameters"
- )
- else:
- if insert_options._dml_strategy == "auto":
- insert_options += {"_dml_strategy": "bulk"}
-
- if insert_options._dml_strategy != "raw":
- # for ORM object loading, like ORMContext, we have to disable
- # result set adapt_to_context, because we will be generating a
- # new statement with specific columns that's cached inside of
- # an ORMFromStatementCompileState, which we will re-use for
- # each result.
- if not execution_options:
- execution_options = context._orm_load_exec_options
- else:
- execution_options = execution_options.union(
- context._orm_load_exec_options
- )
-
- if not is_pre_event and insert_options._autoflush:
- session._autoflush()
-
- statement = statement._annotate(
- {"dml_strategy": insert_options._dml_strategy}
- )
-
- return (
- statement,
- util.immutabledict(execution_options).union(
- {"_sa_orm_insert_options": insert_options}
- ),
- )
-
- @classmethod
- def orm_execute_statement(
- cls,
- session: Session,
- statement: dml.Insert,
- params: _CoreAnyExecuteParams,
- execution_options: OrmExecuteOptionsParameter,
- bind_arguments: _BindArguments,
- conn: Connection,
- ) -> _result.Result:
- insert_options = execution_options.get(
- "_sa_orm_insert_options", cls.default_insert_options
- )
-
- if insert_options._dml_strategy not in (
- "raw",
- "bulk",
- "orm",
- "auto",
- ):
- raise sa_exc.ArgumentError(
- "Valid strategies for ORM insert strategy "
- "are 'raw', 'orm', 'bulk', 'auto"
- )
-
- result: _result.Result[Any]
-
- if insert_options._dml_strategy == "raw":
- result = conn.execute(
- statement, params or {}, execution_options=execution_options
- )
- return result
-
- if insert_options._dml_strategy == "bulk":
- mapper = insert_options._subject_mapper
-
- if (
- statement._post_values_clause is not None
- and mapper._multiple_persistence_tables
- ):
- raise sa_exc.InvalidRequestError(
- "bulk INSERT with a 'post values' clause "
- "(typically upsert) not supported for multi-table "
- f"mapper {mapper}"
- )
-
- assert mapper is not None
- assert session._transaction is not None
- result = _bulk_insert(
- mapper,
- cast(
- "Iterable[Dict[str, Any]]",
- [params] if isinstance(params, dict) else params,
- ),
- session._transaction,
- isstates=False,
- return_defaults=insert_options._return_defaults,
- render_nulls=insert_options._render_nulls,
- use_orm_insert_stmt=statement,
- execution_options=execution_options,
- )
- elif insert_options._dml_strategy == "orm":
- result = conn.execute(
- statement, params or {}, execution_options=execution_options
- )
- else:
- raise AssertionError()
-
- if not bool(statement._returning):
- return result
-
- if insert_options._populate_existing:
- load_options = execution_options.get(
- "_sa_orm_load_options", QueryContext.default_load_options
- )
- load_options += {"_populate_existing": True}
- execution_options = execution_options.union(
- {"_sa_orm_load_options": load_options}
- )
-
- return cls._return_orm_returning(
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- result,
- )
-
- @classmethod
- def create_for_statement(cls, statement, compiler, **kw) -> BulkORMInsert:
- self = cast(
- BulkORMInsert,
- super().create_for_statement(statement, compiler, **kw),
- )
-
- if compiler is not None:
- toplevel = not compiler.stack
- else:
- toplevel = True
- if not toplevel:
- return self
-
- mapper = statement._propagate_attrs["plugin_subject"]
- dml_strategy = statement._annotations.get("dml_strategy", "raw")
- if dml_strategy == "bulk":
- self._setup_for_bulk_insert(compiler)
- elif dml_strategy == "orm":
- self._setup_for_orm_insert(compiler, mapper)
-
- return self
-
- @classmethod
- def _resolved_keys_as_col_keys(cls, mapper, resolved_value_dict):
- return {
- col.key if col is not None else k: v
- for col, k, v in (
- (mapper.c.get(k), k, v) for k, v in resolved_value_dict.items()
- )
- }
-
- def _setup_for_orm_insert(self, compiler, mapper):
- statement = orm_level_statement = cast(dml.Insert, self.statement)
-
- statement = self._setup_orm_returning(
- compiler,
- orm_level_statement,
- statement,
- dml_mapper=mapper,
- use_supplemental_cols=False,
- )
- self.statement = statement
-
- def _setup_for_bulk_insert(self, compiler):
- """establish an INSERT statement within the context of
- bulk insert.
-
- This method will be within the "conn.execute()" call that is invoked
- by persistence._emit_insert_statement().
-
- """
- statement = orm_level_statement = cast(dml.Insert, self.statement)
- an = statement._annotations
-
- emit_insert_table, emit_insert_mapper = (
- an["_emit_insert_table"],
- an["_emit_insert_mapper"],
- )
-
- statement = statement._clone()
-
- statement.table = emit_insert_table
- if self._dict_parameters:
- self._dict_parameters = {
- col: val
- for col, val in self._dict_parameters.items()
- if col.table is emit_insert_table
- }
-
- statement = self._setup_orm_returning(
- compiler,
- orm_level_statement,
- statement,
- dml_mapper=emit_insert_mapper,
- use_supplemental_cols=True,
- )
-
- if (
- self.from_statement_ctx is not None
- and self.from_statement_ctx.compile_options._is_star
- ):
- raise sa_exc.CompileError(
- "Can't use RETURNING * with bulk ORM INSERT. "
- "Please use a different INSERT form, such as INSERT..VALUES "
- "or INSERT with a Core Connection"
- )
-
- self.statement = statement
-
-
-@CompileState.plugin_for("orm", "update")
-class BulkORMUpdate(BulkUDCompileState, UpdateDMLState):
- @classmethod
- def create_for_statement(cls, statement, compiler, **kw):
- self = cls.__new__(cls)
-
- dml_strategy = statement._annotations.get(
- "dml_strategy", "unspecified"
- )
-
- toplevel = not compiler.stack
-
- if toplevel and dml_strategy == "bulk":
- self._setup_for_bulk_update(statement, compiler)
- elif (
- dml_strategy == "core_only"
- or dml_strategy == "unspecified"
- and "parententity" not in statement.table._annotations
- ):
- UpdateDMLState.__init__(self, statement, compiler, **kw)
- elif not toplevel or dml_strategy in ("orm", "unspecified"):
- self._setup_for_orm_update(statement, compiler)
-
- return self
-
- def _setup_for_orm_update(self, statement, compiler, **kw):
- orm_level_statement = statement
-
- toplevel = not compiler.stack
-
- ext_info = statement.table._annotations["parententity"]
-
- self.mapper = mapper = ext_info.mapper
-
- self._resolved_values = self._get_resolved_values(mapper, statement)
-
- self._init_global_attributes(
- statement,
- compiler,
- toplevel=toplevel,
- process_criteria_for_toplevel=toplevel,
- )
-
- if statement._values:
- self._resolved_values = dict(self._resolved_values)
-
- new_stmt = statement._clone()
-
- # note if the statement has _multi_values, these
- # are passed through to the new statement, which will then raise
- # InvalidRequestError because UPDATE doesn't support multi_values
- # right now.
- if statement._ordered_values:
- new_stmt._ordered_values = self._resolved_values
- elif statement._values:
- new_stmt._values = self._resolved_values
-
- new_crit = self._adjust_for_extra_criteria(
- self.global_attributes, mapper
- )
- if new_crit:
- new_stmt = new_stmt.where(*new_crit)
-
- # if we are against a lambda statement we might not be the
- # topmost object that received per-execute annotations
-
- # do this first as we need to determine if there is
- # UPDATE..FROM
-
- UpdateDMLState.__init__(self, new_stmt, compiler, **kw)
-
- use_supplemental_cols = False
-
- if not toplevel:
- synchronize_session = None
- else:
- synchronize_session = compiler._annotations.get(
- "synchronize_session", None
- )
- can_use_returning = compiler._annotations.get(
- "can_use_returning", None
- )
- if can_use_returning is not False:
- # even though pre_exec has determined basic
- # can_use_returning for the dialect, if we are to use
- # RETURNING we need to run can_use_returning() at this level
- # unconditionally because is_delete_using was not known
- # at the pre_exec level
- can_use_returning = (
- synchronize_session == "fetch"
- and self.can_use_returning(
- compiler.dialect, mapper, is_multitable=self.is_multitable
- )
- )
-
- if synchronize_session == "fetch" and can_use_returning:
- use_supplemental_cols = True
-
- # NOTE: we might want to RETURNING the actual columns to be
- # synchronized also. however this is complicated and difficult
- # to align against the behavior of "evaluate". Additionally,
- # in a large number (if not the majority) of cases, we have the
- # "evaluate" answer, usually a fixed value, in memory already and
- # there's no need to re-fetch the same value
- # over and over again. so perhaps if it could be RETURNING just
- # the elements that were based on a SQL expression and not
- # a constant. For now it doesn't quite seem worth it
- new_stmt = new_stmt.return_defaults(*new_stmt.table.primary_key)
-
- if toplevel:
- new_stmt = self._setup_orm_returning(
- compiler,
- orm_level_statement,
- new_stmt,
- dml_mapper=mapper,
- use_supplemental_cols=use_supplemental_cols,
- )
-
- self.statement = new_stmt
-
- def _setup_for_bulk_update(self, statement, compiler, **kw):
- """establish an UPDATE statement within the context of
- bulk insert.
-
- This method will be within the "conn.execute()" call that is invoked
- by persistence._emit_update_statement().
-
- """
- statement = cast(dml.Update, statement)
- an = statement._annotations
-
- emit_update_table, _ = (
- an["_emit_update_table"],
- an["_emit_update_mapper"],
- )
-
- statement = statement._clone()
- statement.table = emit_update_table
-
- UpdateDMLState.__init__(self, statement, compiler, **kw)
-
- if self._ordered_values:
- raise sa_exc.InvalidRequestError(
- "bulk ORM UPDATE does not support ordered_values() for "
- "custom UPDATE statements with bulk parameter sets. Use a "
- "non-bulk UPDATE statement or use values()."
- )
-
- if self._dict_parameters:
- self._dict_parameters = {
- col: val
- for col, val in self._dict_parameters.items()
- if col.table is emit_update_table
- }
- self.statement = statement
-
- @classmethod
- def orm_execute_statement(
- cls,
- session: Session,
- statement: dml.Update,
- params: _CoreAnyExecuteParams,
- execution_options: OrmExecuteOptionsParameter,
- bind_arguments: _BindArguments,
- conn: Connection,
- ) -> _result.Result:
- update_options = execution_options.get(
- "_sa_orm_update_options", cls.default_update_options
- )
-
- if update_options._dml_strategy not in (
- "orm",
- "auto",
- "bulk",
- "core_only",
- ):
- raise sa_exc.ArgumentError(
- "Valid strategies for ORM UPDATE strategy "
- "are 'orm', 'auto', 'bulk', 'core_only'"
- )
-
- result: _result.Result[Any]
-
- if update_options._dml_strategy == "bulk":
- enable_check_rowcount = not statement._where_criteria
-
- assert update_options._synchronize_session != "fetch"
-
- if (
- statement._where_criteria
- and update_options._synchronize_session == "evaluate"
- ):
- raise sa_exc.InvalidRequestError(
- "bulk synchronize of persistent objects not supported "
- "when using bulk update with additional WHERE "
- "criteria right now. add synchronize_session=None "
- "execution option to bypass synchronize of persistent "
- "objects."
- )
- mapper = update_options._subject_mapper
- assert mapper is not None
- assert session._transaction is not None
- result = _bulk_update(
- mapper,
- cast(
- "Iterable[Dict[str, Any]]",
- [params] if isinstance(params, dict) else params,
- ),
- session._transaction,
- isstates=False,
- update_changed_only=False,
- use_orm_update_stmt=statement,
- enable_check_rowcount=enable_check_rowcount,
- )
- return cls.orm_setup_cursor_result(
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- result,
- )
- else:
- return super().orm_execute_statement(
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- conn,
- )
-
- @classmethod
- def can_use_returning(
- cls,
- dialect: Dialect,
- mapper: Mapper[Any],
- *,
- is_multitable: bool = False,
- is_update_from: bool = False,
- is_delete_using: bool = False,
- is_executemany: bool = False,
- ) -> bool:
- # normal answer for "should we use RETURNING" at all.
- normal_answer = (
- dialect.update_returning and mapper.local_table.implicit_returning
- )
- if not normal_answer:
- return False
-
- if is_executemany:
- return dialect.update_executemany_returning
-
- # these workarounds are currently hypothetical for UPDATE,
- # unlike DELETE where they impact MariaDB
- if is_update_from:
- return dialect.update_returning_multifrom
-
- elif is_multitable and not dialect.update_returning_multifrom:
- raise sa_exc.CompileError(
- f'Dialect "{dialect.name}" does not support RETURNING '
- "with UPDATE..FROM; for synchronize_session='fetch', "
- "please add the additional execution option "
- "'is_update_from=True' to the statement to indicate that "
- "a separate SELECT should be used for this backend."
- )
-
- return True
-
- @classmethod
- def _do_post_synchronize_bulk_evaluate(
- cls, session, params, result, update_options
- ):
- if not params:
- return
-
- mapper = update_options._subject_mapper
- pk_keys = [prop.key for prop in mapper._identity_key_props]
-
- identity_map = session.identity_map
-
- for param in params:
- identity_key = mapper.identity_key_from_primary_key(
- (param[key] for key in pk_keys),
- update_options._identity_token,
- )
- state = identity_map.fast_get_state(identity_key)
- if not state:
- continue
-
- evaluated_keys = set(param).difference(pk_keys)
-
- dict_ = state.dict
- # only evaluate unmodified attributes
- to_evaluate = state.unmodified.intersection(evaluated_keys)
- for key in to_evaluate:
- if key in dict_:
- dict_[key] = param[key]
-
- state.manager.dispatch.refresh(state, None, to_evaluate)
-
- state._commit(dict_, list(to_evaluate))
-
- # attributes that were formerly modified instead get expired.
- # this only gets hit if the session had pending changes
- # and autoflush were set to False.
- to_expire = evaluated_keys.intersection(dict_).difference(
- to_evaluate
- )
- if to_expire:
- state._expire_attributes(dict_, to_expire)
-
- @classmethod
- def _do_post_synchronize_evaluate(
- cls, session, statement, result, update_options
- ):
- matched_objects = cls._get_matched_objects_on_criteria(
- update_options,
- session.identity_map.all_states(),
- )
-
- cls._apply_update_set_values_to_objects(
- session,
- update_options,
- statement,
- [(obj, state, dict_) for obj, state, dict_, _ in matched_objects],
- )
-
- @classmethod
- def _do_post_synchronize_fetch(
- cls, session, statement, result, update_options
- ):
- target_mapper = update_options._subject_mapper
-
- returned_defaults_rows = result.returned_defaults_rows
- if returned_defaults_rows:
- pk_rows = cls._interpret_returning_rows(
- target_mapper, returned_defaults_rows
- )
-
- matched_rows = [
- tuple(row) + (update_options._identity_token,)
- for row in pk_rows
- ]
- else:
- matched_rows = update_options._matched_rows
-
- objs = [
- session.identity_map[identity_key]
- for identity_key in [
- target_mapper.identity_key_from_primary_key(
- list(primary_key),
- identity_token=identity_token,
- )
- for primary_key, identity_token in [
- (row[0:-1], row[-1]) for row in matched_rows
- ]
- if update_options._identity_token is None
- or identity_token == update_options._identity_token
- ]
- if identity_key in session.identity_map
- ]
-
- if not objs:
- return
-
- cls._apply_update_set_values_to_objects(
- session,
- update_options,
- statement,
- [
- (
- obj,
- attributes.instance_state(obj),
- attributes.instance_dict(obj),
- )
- for obj in objs
- ],
- )
-
- @classmethod
- def _apply_update_set_values_to_objects(
- cls, session, update_options, statement, matched_objects
- ):
- """apply values to objects derived from an update statement, e.g.
- UPDATE..SET <values>
-
- """
- mapper = update_options._subject_mapper
- target_cls = mapper.class_
- evaluator_compiler = evaluator._EvaluatorCompiler(target_cls)
- resolved_values = cls._get_resolved_values(mapper, statement)
- resolved_keys_as_propnames = cls._resolved_keys_as_propnames(
- mapper, resolved_values
- )
- value_evaluators = {}
- for key, value in resolved_keys_as_propnames:
- try:
- _evaluator = evaluator_compiler.process(
- coercions.expect(roles.ExpressionElementRole, value)
- )
- except evaluator.UnevaluatableError:
- pass
- else:
- value_evaluators[key] = _evaluator
-
- evaluated_keys = list(value_evaluators.keys())
- attrib = {k for k, v in resolved_keys_as_propnames}
-
- states = set()
- for obj, state, dict_ in matched_objects:
- to_evaluate = state.unmodified.intersection(evaluated_keys)
-
- for key in to_evaluate:
- if key in dict_:
- # only run eval for attributes that are present.
- dict_[key] = value_evaluators[key](obj)
-
- state.manager.dispatch.refresh(state, None, to_evaluate)
-
- state._commit(dict_, list(to_evaluate))
-
- # attributes that were formerly modified instead get expired.
- # this only gets hit if the session had pending changes
- # and autoflush were set to False.
- to_expire = attrib.intersection(dict_).difference(to_evaluate)
- if to_expire:
- state._expire_attributes(dict_, to_expire)
-
- states.add(state)
- session._register_altered(states)
-
-
-@CompileState.plugin_for("orm", "delete")
-class BulkORMDelete(BulkUDCompileState, DeleteDMLState):
- @classmethod
- def create_for_statement(cls, statement, compiler, **kw):
- self = cls.__new__(cls)
-
- dml_strategy = statement._annotations.get(
- "dml_strategy", "unspecified"
- )
-
- if (
- dml_strategy == "core_only"
- or dml_strategy == "unspecified"
- and "parententity" not in statement.table._annotations
- ):
- DeleteDMLState.__init__(self, statement, compiler, **kw)
- return self
-
- toplevel = not compiler.stack
-
- orm_level_statement = statement
-
- ext_info = statement.table._annotations["parententity"]
- self.mapper = mapper = ext_info.mapper
-
- self._init_global_attributes(
- statement,
- compiler,
- toplevel=toplevel,
- process_criteria_for_toplevel=toplevel,
- )
-
- new_stmt = statement._clone()
-
- new_crit = cls._adjust_for_extra_criteria(
- self.global_attributes, mapper
- )
- if new_crit:
- new_stmt = new_stmt.where(*new_crit)
-
- # do this first as we need to determine if there is
- # DELETE..FROM
- DeleteDMLState.__init__(self, new_stmt, compiler, **kw)
-
- use_supplemental_cols = False
-
- if not toplevel:
- synchronize_session = None
- else:
- synchronize_session = compiler._annotations.get(
- "synchronize_session", None
- )
- can_use_returning = compiler._annotations.get(
- "can_use_returning", None
- )
- if can_use_returning is not False:
- # even though pre_exec has determined basic
- # can_use_returning for the dialect, if we are to use
- # RETURNING we need to run can_use_returning() at this level
- # unconditionally because is_delete_using was not known
- # at the pre_exec level
- can_use_returning = (
- synchronize_session == "fetch"
- and self.can_use_returning(
- compiler.dialect,
- mapper,
- is_multitable=self.is_multitable,
- is_delete_using=compiler._annotations.get(
- "is_delete_using", False
- ),
- )
- )
-
- if can_use_returning:
- use_supplemental_cols = True
-
- new_stmt = new_stmt.return_defaults(*new_stmt.table.primary_key)
-
- if toplevel:
- new_stmt = self._setup_orm_returning(
- compiler,
- orm_level_statement,
- new_stmt,
- dml_mapper=mapper,
- use_supplemental_cols=use_supplemental_cols,
- )
-
- self.statement = new_stmt
-
- return self
-
- @classmethod
- def orm_execute_statement(
- cls,
- session: Session,
- statement: dml.Delete,
- params: _CoreAnyExecuteParams,
- execution_options: OrmExecuteOptionsParameter,
- bind_arguments: _BindArguments,
- conn: Connection,
- ) -> _result.Result:
- update_options = execution_options.get(
- "_sa_orm_update_options", cls.default_update_options
- )
-
- if update_options._dml_strategy == "bulk":
- raise sa_exc.InvalidRequestError(
- "Bulk ORM DELETE not supported right now. "
- "Statement may be invoked at the "
- "Core level using "
- "session.connection().execute(stmt, parameters)"
- )
-
- if update_options._dml_strategy not in ("orm", "auto", "core_only"):
- raise sa_exc.ArgumentError(
- "Valid strategies for ORM DELETE strategy are 'orm', 'auto', "
- "'core_only'"
- )
-
- return super().orm_execute_statement(
- session, statement, params, execution_options, bind_arguments, conn
- )
-
- @classmethod
- def can_use_returning(
- cls,
- dialect: Dialect,
- mapper: Mapper[Any],
- *,
- is_multitable: bool = False,
- is_update_from: bool = False,
- is_delete_using: bool = False,
- is_executemany: bool = False,
- ) -> bool:
- # normal answer for "should we use RETURNING" at all.
- normal_answer = (
- dialect.delete_returning and mapper.local_table.implicit_returning
- )
- if not normal_answer:
- return False
-
- # now get into special workarounds because MariaDB supports
- # DELETE...RETURNING but not DELETE...USING...RETURNING.
- if is_delete_using:
- # is_delete_using hint was passed. use
- # additional dialect feature (True for PG, False for MariaDB)
- return dialect.delete_returning_multifrom
-
- elif is_multitable and not dialect.delete_returning_multifrom:
- # is_delete_using hint was not passed, but we determined
- # at compile time that this is in fact a DELETE..USING.
- # it's too late to continue since we did not pre-SELECT.
- # raise that we need that hint up front.
-
- raise sa_exc.CompileError(
- f'Dialect "{dialect.name}" does not support RETURNING '
- "with DELETE..USING; for synchronize_session='fetch', "
- "please add the additional execution option "
- "'is_delete_using=True' to the statement to indicate that "
- "a separate SELECT should be used for this backend."
- )
-
- return True
-
- @classmethod
- def _do_post_synchronize_evaluate(
- cls, session, statement, result, update_options
- ):
- matched_objects = cls._get_matched_objects_on_criteria(
- update_options,
- session.identity_map.all_states(),
- )
-
- to_delete = []
-
- for _, state, dict_, is_partially_expired in matched_objects:
- if is_partially_expired:
- state._expire(dict_, session.identity_map._modified)
- else:
- to_delete.append(state)
-
- if to_delete:
- session._remove_newly_deleted(to_delete)
-
- @classmethod
- def _do_post_synchronize_fetch(
- cls, session, statement, result, update_options
- ):
- target_mapper = update_options._subject_mapper
-
- returned_defaults_rows = result.returned_defaults_rows
-
- if returned_defaults_rows:
- pk_rows = cls._interpret_returning_rows(
- target_mapper, returned_defaults_rows
- )
-
- matched_rows = [
- tuple(row) + (update_options._identity_token,)
- for row in pk_rows
- ]
- else:
- matched_rows = update_options._matched_rows
-
- for row in matched_rows:
- primary_key = row[0:-1]
- identity_token = row[-1]
-
- # TODO: inline this and call remove_newly_deleted
- # once
- identity_key = target_mapper.identity_key_from_primary_key(
- list(primary_key),
- identity_token=identity_token,
- )
- if identity_key in session.identity_map:
- session._remove_newly_deleted(
- [
- attributes.instance_state(
- session.identity_map[identity_key]
- )
- ]
- )
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/clsregistry.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/clsregistry.py
deleted file mode 100644
index 26113d8..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/clsregistry.py
+++ /dev/null
@@ -1,570 +0,0 @@
-# orm/clsregistry.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
-
-"""Routines to handle the string class registry used by declarative.
-
-This system allows specification of classes and expressions used in
-:func:`_orm.relationship` using strings.
-
-"""
-
-from __future__ import annotations
-
-import re
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Dict
-from typing import Generator
-from typing import Iterable
-from typing import List
-from typing import Mapping
-from typing import MutableMapping
-from typing import NoReturn
-from typing import Optional
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-import weakref
-
-from . import attributes
-from . import interfaces
-from .descriptor_props import SynonymProperty
-from .properties import ColumnProperty
-from .util import class_mapper
-from .. import exc
-from .. import inspection
-from .. import util
-from ..sql.schema import _get_table_key
-from ..util.typing import CallableReference
-
-if TYPE_CHECKING:
- from .relationships import RelationshipProperty
- from ..sql.schema import MetaData
- from ..sql.schema import Table
-
-_T = TypeVar("_T", bound=Any)
-
-_ClsRegistryType = MutableMapping[str, Union[type, "ClsRegistryToken"]]
-
-# strong references to registries which we place in
-# the _decl_class_registry, which is usually weak referencing.
-# the internal registries here link to classes with weakrefs and remove
-# themselves when all references to contained classes are removed.
-_registries: Set[ClsRegistryToken] = set()
-
-
-def add_class(
- classname: str, cls: Type[_T], decl_class_registry: _ClsRegistryType
-) -> None:
- """Add a class to the _decl_class_registry associated with the
- given declarative class.
-
- """
- if classname in decl_class_registry:
- # class already exists.
- existing = decl_class_registry[classname]
- if not isinstance(existing, _MultipleClassMarker):
- existing = decl_class_registry[classname] = _MultipleClassMarker(
- [cls, cast("Type[Any]", existing)]
- )
- else:
- decl_class_registry[classname] = cls
-
- try:
- root_module = cast(
- _ModuleMarker, decl_class_registry["_sa_module_registry"]
- )
- except KeyError:
- decl_class_registry["_sa_module_registry"] = root_module = (
- _ModuleMarker("_sa_module_registry", None)
- )
-
- tokens = cls.__module__.split(".")
-
- # build up a tree like this:
- # modulename: myapp.snacks.nuts
- #
- # myapp->snack->nuts->(classes)
- # snack->nuts->(classes)
- # nuts->(classes)
- #
- # this allows partial token paths to be used.
- while tokens:
- token = tokens.pop(0)
- module = root_module.get_module(token)
- for token in tokens:
- module = module.get_module(token)
-
- try:
- module.add_class(classname, cls)
- except AttributeError as ae:
- if not isinstance(module, _ModuleMarker):
- raise exc.InvalidRequestError(
- f'name "{classname}" matches both a '
- "class name and a module name"
- ) from ae
- else:
- raise
-
-
-def remove_class(
- classname: str, cls: Type[Any], decl_class_registry: _ClsRegistryType
-) -> None:
- if classname in decl_class_registry:
- existing = decl_class_registry[classname]
- if isinstance(existing, _MultipleClassMarker):
- existing.remove_item(cls)
- else:
- del decl_class_registry[classname]
-
- try:
- root_module = cast(
- _ModuleMarker, decl_class_registry["_sa_module_registry"]
- )
- except KeyError:
- return
-
- tokens = cls.__module__.split(".")
-
- while tokens:
- token = tokens.pop(0)
- module = root_module.get_module(token)
- for token in tokens:
- module = module.get_module(token)
- try:
- module.remove_class(classname, cls)
- except AttributeError:
- if not isinstance(module, _ModuleMarker):
- pass
- else:
- raise
-
-
-def _key_is_empty(
- key: str,
- decl_class_registry: _ClsRegistryType,
- test: Callable[[Any], bool],
-) -> bool:
- """test if a key is empty of a certain object.
-
- used for unit tests against the registry to see if garbage collection
- is working.
-
- "test" is a callable that will be passed an object should return True
- if the given object is the one we were looking for.
-
- We can't pass the actual object itself b.c. this is for testing garbage
- collection; the caller will have to have removed references to the
- object itself.
-
- """
- if key not in decl_class_registry:
- return True
-
- thing = decl_class_registry[key]
- if isinstance(thing, _MultipleClassMarker):
- for sub_thing in thing.contents:
- if test(sub_thing):
- return False
- else:
- raise NotImplementedError("unknown codepath")
- else:
- return not test(thing)
-
-
-class ClsRegistryToken:
- """an object that can be in the registry._class_registry as a value."""
-
- __slots__ = ()
-
-
-class _MultipleClassMarker(ClsRegistryToken):
- """refers to multiple classes of the same name
- within _decl_class_registry.
-
- """
-
- __slots__ = "on_remove", "contents", "__weakref__"
-
- contents: Set[weakref.ref[Type[Any]]]
- on_remove: CallableReference[Optional[Callable[[], None]]]
-
- def __init__(
- self,
- classes: Iterable[Type[Any]],
- on_remove: Optional[Callable[[], None]] = None,
- ):
- self.on_remove = on_remove
- self.contents = {
- weakref.ref(item, self._remove_item) for item in classes
- }
- _registries.add(self)
-
- def remove_item(self, cls: Type[Any]) -> None:
- self._remove_item(weakref.ref(cls))
-
- def __iter__(self) -> Generator[Optional[Type[Any]], None, None]:
- return (ref() for ref in self.contents)
-
- def attempt_get(self, path: List[str], key: str) -> Type[Any]:
- if len(self.contents) > 1:
- raise exc.InvalidRequestError(
- 'Multiple classes found for path "%s" '
- "in the registry of this declarative "
- "base. Please use a fully module-qualified path."
- % (".".join(path + [key]))
- )
- else:
- ref = list(self.contents)[0]
- cls = ref()
- if cls is None:
- raise NameError(key)
- return cls
-
- def _remove_item(self, ref: weakref.ref[Type[Any]]) -> None:
- self.contents.discard(ref)
- if not self.contents:
- _registries.discard(self)
- if self.on_remove:
- self.on_remove()
-
- def add_item(self, item: Type[Any]) -> None:
- # protect against class registration race condition against
- # asynchronous garbage collection calling _remove_item,
- # [ticket:3208] and [ticket:10782]
- modules = {
- cls.__module__
- for cls in [ref() for ref in list(self.contents)]
- if cls is not None
- }
- if item.__module__ in modules:
- util.warn(
- "This declarative base already contains a class with the "
- "same class name and module name as %s.%s, and will "
- "be replaced in the string-lookup table."
- % (item.__module__, item.__name__)
- )
- self.contents.add(weakref.ref(item, self._remove_item))
-
-
-class _ModuleMarker(ClsRegistryToken):
- """Refers to a module name within
- _decl_class_registry.
-
- """
-
- __slots__ = "parent", "name", "contents", "mod_ns", "path", "__weakref__"
-
- parent: Optional[_ModuleMarker]
- contents: Dict[str, Union[_ModuleMarker, _MultipleClassMarker]]
- mod_ns: _ModNS
- path: List[str]
-
- def __init__(self, name: str, parent: Optional[_ModuleMarker]):
- self.parent = parent
- self.name = name
- self.contents = {}
- self.mod_ns = _ModNS(self)
- if self.parent:
- self.path = self.parent.path + [self.name]
- else:
- self.path = []
- _registries.add(self)
-
- def __contains__(self, name: str) -> bool:
- return name in self.contents
-
- def __getitem__(self, name: str) -> ClsRegistryToken:
- return self.contents[name]
-
- def _remove_item(self, name: str) -> None:
- self.contents.pop(name, None)
- if not self.contents and self.parent is not None:
- self.parent._remove_item(self.name)
- _registries.discard(self)
-
- def resolve_attr(self, key: str) -> Union[_ModNS, Type[Any]]:
- return self.mod_ns.__getattr__(key)
-
- def get_module(self, name: str) -> _ModuleMarker:
- if name not in self.contents:
- marker = _ModuleMarker(name, self)
- self.contents[name] = marker
- else:
- marker = cast(_ModuleMarker, self.contents[name])
- return marker
-
- def add_class(self, name: str, cls: Type[Any]) -> None:
- if name in self.contents:
- existing = cast(_MultipleClassMarker, self.contents[name])
- try:
- existing.add_item(cls)
- except AttributeError as ae:
- if not isinstance(existing, _MultipleClassMarker):
- raise exc.InvalidRequestError(
- f'name "{name}" matches both a '
- "class name and a module name"
- ) from ae
- else:
- raise
- else:
- existing = self.contents[name] = _MultipleClassMarker(
- [cls], on_remove=lambda: self._remove_item(name)
- )
-
- def remove_class(self, name: str, cls: Type[Any]) -> None:
- if name in self.contents:
- existing = cast(_MultipleClassMarker, self.contents[name])
- existing.remove_item(cls)
-
-
-class _ModNS:
- __slots__ = ("__parent",)
-
- __parent: _ModuleMarker
-
- def __init__(self, parent: _ModuleMarker):
- self.__parent = parent
-
- def __getattr__(self, key: str) -> Union[_ModNS, Type[Any]]:
- try:
- value = self.__parent.contents[key]
- except KeyError:
- pass
- else:
- if value is not None:
- if isinstance(value, _ModuleMarker):
- return value.mod_ns
- else:
- assert isinstance(value, _MultipleClassMarker)
- return value.attempt_get(self.__parent.path, key)
- raise NameError(
- "Module %r has no mapped classes "
- "registered under the name %r" % (self.__parent.name, key)
- )
-
-
-class _GetColumns:
- __slots__ = ("cls",)
-
- cls: Type[Any]
-
- def __init__(self, cls: Type[Any]):
- self.cls = cls
-
- def __getattr__(self, key: str) -> Any:
- mp = class_mapper(self.cls, configure=False)
- if mp:
- if key not in mp.all_orm_descriptors:
- raise AttributeError(
- "Class %r does not have a mapped column named %r"
- % (self.cls, key)
- )
-
- desc = mp.all_orm_descriptors[key]
- if desc.extension_type is interfaces.NotExtension.NOT_EXTENSION:
- assert isinstance(desc, attributes.QueryableAttribute)
- prop = desc.property
- if isinstance(prop, SynonymProperty):
- key = prop.name
- elif not isinstance(prop, ColumnProperty):
- raise exc.InvalidRequestError(
- "Property %r is not an instance of"
- " ColumnProperty (i.e. does not correspond"
- " directly to a Column)." % key
- )
- return getattr(self.cls, key)
-
-
-inspection._inspects(_GetColumns)(
- lambda target: inspection.inspect(target.cls)
-)
-
-
-class _GetTable:
- __slots__ = "key", "metadata"
-
- key: str
- metadata: MetaData
-
- def __init__(self, key: str, metadata: MetaData):
- self.key = key
- self.metadata = metadata
-
- def __getattr__(self, key: str) -> Table:
- return self.metadata.tables[_get_table_key(key, self.key)]
-
-
-def _determine_container(key: str, value: Any) -> _GetColumns:
- if isinstance(value, _MultipleClassMarker):
- value = value.attempt_get([], key)
- return _GetColumns(value)
-
-
-class _class_resolver:
- __slots__ = (
- "cls",
- "prop",
- "arg",
- "fallback",
- "_dict",
- "_resolvers",
- "favor_tables",
- )
-
- cls: Type[Any]
- prop: RelationshipProperty[Any]
- fallback: Mapping[str, Any]
- arg: str
- favor_tables: bool
- _resolvers: Tuple[Callable[[str], Any], ...]
-
- def __init__(
- self,
- cls: Type[Any],
- prop: RelationshipProperty[Any],
- fallback: Mapping[str, Any],
- arg: str,
- favor_tables: bool = False,
- ):
- self.cls = cls
- self.prop = prop
- self.arg = arg
- self.fallback = fallback
- self._dict = util.PopulateDict(self._access_cls)
- self._resolvers = ()
- self.favor_tables = favor_tables
-
- def _access_cls(self, key: str) -> Any:
- cls = self.cls
-
- manager = attributes.manager_of_class(cls)
- decl_base = manager.registry
- assert decl_base is not None
- decl_class_registry = decl_base._class_registry
- metadata = decl_base.metadata
-
- if self.favor_tables:
- if key in metadata.tables:
- return metadata.tables[key]
- elif key in metadata._schemas:
- return _GetTable(key, getattr(cls, "metadata", metadata))
-
- if key in decl_class_registry:
- return _determine_container(key, decl_class_registry[key])
-
- if not self.favor_tables:
- if key in metadata.tables:
- return metadata.tables[key]
- elif key in metadata._schemas:
- return _GetTable(key, getattr(cls, "metadata", metadata))
-
- if "_sa_module_registry" in decl_class_registry and key in cast(
- _ModuleMarker, decl_class_registry["_sa_module_registry"]
- ):
- registry = cast(
- _ModuleMarker, decl_class_registry["_sa_module_registry"]
- )
- return registry.resolve_attr(key)
- elif self._resolvers:
- for resolv in self._resolvers:
- value = resolv(key)
- if value is not None:
- return value
-
- return self.fallback[key]
-
- def _raise_for_name(self, name: str, err: Exception) -> NoReturn:
- generic_match = re.match(r"(.+)\[(.+)\]", name)
-
- if generic_match:
- clsarg = generic_match.group(2).strip("'")
- raise exc.InvalidRequestError(
- f"When initializing mapper {self.prop.parent}, "
- f'expression "relationship({self.arg!r})" seems to be '
- "using a generic class as the argument to relationship(); "
- "please state the generic argument "
- "using an annotation, e.g. "
- f'"{self.prop.key}: Mapped[{generic_match.group(1)}'
- f"['{clsarg}']] = relationship()\""
- ) from err
- else:
- raise exc.InvalidRequestError(
- "When initializing mapper %s, expression %r failed to "
- "locate a name (%r). If this is a class name, consider "
- "adding this relationship() to the %r class after "
- "both dependent classes have been defined."
- % (self.prop.parent, self.arg, name, self.cls)
- ) from err
-
- def _resolve_name(self) -> Union[Table, Type[Any], _ModNS]:
- name = self.arg
- d = self._dict
- rval = None
- try:
- for token in name.split("."):
- if rval is None:
- rval = d[token]
- else:
- rval = getattr(rval, token)
- except KeyError as err:
- self._raise_for_name(name, err)
- except NameError as n:
- self._raise_for_name(n.args[0], n)
- else:
- if isinstance(rval, _GetColumns):
- return rval.cls
- else:
- if TYPE_CHECKING:
- assert isinstance(rval, (type, Table, _ModNS))
- return rval
-
- def __call__(self) -> Any:
- try:
- x = eval(self.arg, globals(), self._dict)
-
- if isinstance(x, _GetColumns):
- return x.cls
- else:
- return x
- except NameError as n:
- self._raise_for_name(n.args[0], n)
-
-
-_fallback_dict: Mapping[str, Any] = None # type: ignore
-
-
-def _resolver(cls: Type[Any], prop: RelationshipProperty[Any]) -> Tuple[
- Callable[[str], Callable[[], Union[Type[Any], Table, _ModNS]]],
- Callable[[str, bool], _class_resolver],
-]:
- global _fallback_dict
-
- if _fallback_dict is None:
- import sqlalchemy
- from . import foreign
- from . import remote
-
- _fallback_dict = util.immutabledict(sqlalchemy.__dict__).union(
- {"foreign": foreign, "remote": remote}
- )
-
- def resolve_arg(arg: str, favor_tables: bool = False) -> _class_resolver:
- return _class_resolver(
- cls, prop, _fallback_dict, arg, favor_tables=favor_tables
- )
-
- def resolve_name(
- arg: str,
- ) -> Callable[[], Union[Type[Any], Table, _ModNS]]:
- return _class_resolver(cls, prop, _fallback_dict, arg)._resolve_name
-
- return resolve_name, resolve_arg
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/collections.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/collections.py
deleted file mode 100644
index 6fefd78..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/collections.py
+++ /dev/null
@@ -1,1618 +0,0 @@
-# orm/collections.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
-# mypy: allow-untyped-defs, allow-untyped-calls
-
-"""Support for collections of mapped entities.
-
-The collections package supplies the machinery used to inform the ORM of
-collection membership changes. An instrumentation via decoration approach is
-used, allowing arbitrary types (including built-ins) to be used as entity
-collections without requiring inheritance from a base class.
-
-Instrumentation decoration relays membership change events to the
-:class:`.CollectionAttributeImpl` that is currently managing the collection.
-The decorators observe function call arguments and return values, tracking
-entities entering or leaving the collection. Two decorator approaches are
-provided. One is a bundle of generic decorators that map function arguments
-and return values to events::
-
- from sqlalchemy.orm.collections import collection
- class MyClass:
- # ...
-
- @collection.adds(1)
- def store(self, item):
- self.data.append(item)
-
- @collection.removes_return()
- def pop(self):
- return self.data.pop()
-
-
-The second approach is a bundle of targeted decorators that wrap appropriate
-append and remove notifiers around the mutation methods present in the
-standard Python ``list``, ``set`` and ``dict`` interfaces. These could be
-specified in terms of generic decorator recipes, but are instead hand-tooled
-for increased efficiency. The targeted decorators occasionally implement
-adapter-like behavior, such as mapping bulk-set methods (``extend``,
-``update``, ``__setslice__``, etc.) into the series of atomic mutation events
-that the ORM requires.
-
-The targeted decorators are used internally for automatic instrumentation of
-entity collection classes. Every collection class goes through a
-transformation process roughly like so:
-
-1. If the class is a built-in, substitute a trivial sub-class
-2. Is this class already instrumented?
-3. Add in generic decorators
-4. Sniff out the collection interface through duck-typing
-5. Add targeted decoration to any undecorated interface method
-
-This process modifies the class at runtime, decorating methods and adding some
-bookkeeping properties. This isn't possible (or desirable) for built-in
-classes like ``list``, so trivial sub-classes are substituted to hold
-decoration::
-
- class InstrumentedList(list):
- pass
-
-Collection classes can be specified in ``relationship(collection_class=)`` as
-types or a function that returns an instance. Collection classes are
-inspected and instrumented during the mapper compilation phase. The
-collection_class callable will be executed once to produce a specimen
-instance, and the type of that specimen will be instrumented. Functions that
-return built-in types like ``lists`` will be adapted to produce instrumented
-instances.
-
-When extending a known type like ``list``, additional decorations are not
-generally not needed. Odds are, the extension method will delegate to a
-method that's already instrumented. For example::
-
- class QueueIsh(list):
- def push(self, item):
- self.append(item)
- def shift(self):
- return self.pop(0)
-
-There's no need to decorate these methods. ``append`` and ``pop`` are already
-instrumented as part of the ``list`` interface. Decorating them would fire
-duplicate events, which should be avoided.
-
-The targeted decoration tries not to rely on other methods in the underlying
-collection class, but some are unavoidable. Many depend on 'read' methods
-being present to properly instrument a 'write', for example, ``__setitem__``
-needs ``__getitem__``. "Bulk" methods like ``update`` and ``extend`` may also
-reimplemented in terms of atomic appends and removes, so the ``extend``
-decoration will actually perform many ``append`` operations and not call the
-underlying method at all.
-
-Tight control over bulk operation and the firing of events is also possible by
-implementing the instrumentation internally in your methods. The basic
-instrumentation package works under the general assumption that collection
-mutation will not raise unusual exceptions. If you want to closely
-orchestrate append and remove events with exception management, internal
-instrumentation may be the answer. Within your method,
-``collection_adapter(self)`` will retrieve an object that you can use for
-explicit control over triggering append and remove events.
-
-The owning object and :class:`.CollectionAttributeImpl` are also reachable
-through the adapter, allowing for some very sophisticated behavior.
-
-"""
-from __future__ import annotations
-
-import operator
-import threading
-import typing
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Collection
-from typing import Dict
-from typing import Iterable
-from typing import List
-from typing import NoReturn
-from typing import Optional
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-import weakref
-
-from .base import NO_KEY
-from .. import exc as sa_exc
-from .. import util
-from ..sql.base import NO_ARG
-from ..util.compat import inspect_getfullargspec
-from ..util.typing import Protocol
-
-if typing.TYPE_CHECKING:
- from .attributes import AttributeEventToken
- from .attributes import CollectionAttributeImpl
- from .mapped_collection import attribute_keyed_dict
- from .mapped_collection import column_keyed_dict
- from .mapped_collection import keyfunc_mapping
- from .mapped_collection import KeyFuncDict # noqa: F401
- from .state import InstanceState
-
-
-__all__ = [
- "collection",
- "collection_adapter",
- "keyfunc_mapping",
- "column_keyed_dict",
- "attribute_keyed_dict",
- "column_keyed_dict",
- "attribute_keyed_dict",
- "MappedCollection",
- "KeyFuncDict",
-]
-
-__instrumentation_mutex = threading.Lock()
-
-
-_CollectionFactoryType = Callable[[], "_AdaptedCollectionProtocol"]
-
-_T = TypeVar("_T", bound=Any)
-_KT = TypeVar("_KT", bound=Any)
-_VT = TypeVar("_VT", bound=Any)
-_COL = TypeVar("_COL", bound="Collection[Any]")
-_FN = TypeVar("_FN", bound="Callable[..., Any]")
-
-
-class _CollectionConverterProtocol(Protocol):
- def __call__(self, collection: _COL) -> _COL: ...
-
-
-class _AdaptedCollectionProtocol(Protocol):
- _sa_adapter: CollectionAdapter
- _sa_appender: Callable[..., Any]
- _sa_remover: Callable[..., Any]
- _sa_iterator: Callable[..., Iterable[Any]]
- _sa_converter: _CollectionConverterProtocol
-
-
-class collection:
- """Decorators for entity collection classes.
-
- The decorators fall into two groups: annotations and interception recipes.
-
- The annotating decorators (appender, remover, iterator, converter,
- internally_instrumented) indicate the method's purpose and take no
- arguments. They are not written with parens::
-
- @collection.appender
- def append(self, append): ...
-
- The recipe decorators all require parens, even those that take no
- arguments::
-
- @collection.adds('entity')
- def insert(self, position, entity): ...
-
- @collection.removes_return()
- def popitem(self): ...
-
- """
-
- # Bundled as a class solely for ease of use: packaging, doc strings,
- # importability.
-
- @staticmethod
- def appender(fn):
- """Tag the method as the collection appender.
-
- The appender method is called with one positional argument: the value
- to append. The method will be automatically decorated with 'adds(1)'
- if not already decorated::
-
- @collection.appender
- def add(self, append): ...
-
- # or, equivalently
- @collection.appender
- @collection.adds(1)
- def add(self, append): ...
-
- # for mapping type, an 'append' may kick out a previous value
- # that occupies that slot. consider d['a'] = 'foo'- any previous
- # value in d['a'] is discarded.
- @collection.appender
- @collection.replaces(1)
- def add(self, entity):
- key = some_key_func(entity)
- previous = None
- if key in self:
- previous = self[key]
- self[key] = entity
- return previous
-
- If the value to append is not allowed in the collection, you may
- raise an exception. Something to remember is that the appender
- will be called for each object mapped by a database query. If the
- database contains rows that violate your collection semantics, you
- will need to get creative to fix the problem, as access via the
- collection will not work.
-
- If the appender method is internally instrumented, you must also
- receive the keyword argument '_sa_initiator' and ensure its
- promulgation to collection events.
-
- """
- fn._sa_instrument_role = "appender"
- return fn
-
- @staticmethod
- def remover(fn):
- """Tag the method as the collection remover.
-
- The remover method is called with one positional argument: the value
- to remove. The method will be automatically decorated with
- :meth:`removes_return` if not already decorated::
-
- @collection.remover
- def zap(self, entity): ...
-
- # or, equivalently
- @collection.remover
- @collection.removes_return()
- def zap(self, ): ...
-
- If the value to remove is not present in the collection, you may
- raise an exception or return None to ignore the error.
-
- If the remove method is internally instrumented, you must also
- receive the keyword argument '_sa_initiator' and ensure its
- promulgation to collection events.
-
- """
- fn._sa_instrument_role = "remover"
- return fn
-
- @staticmethod
- def iterator(fn):
- """Tag the method as the collection remover.
-
- The iterator method is called with no arguments. It is expected to
- return an iterator over all collection members::
-
- @collection.iterator
- def __iter__(self): ...
-
- """
- fn._sa_instrument_role = "iterator"
- return fn
-
- @staticmethod
- def internally_instrumented(fn):
- """Tag the method as instrumented.
-
- This tag will prevent any decoration from being applied to the
- method. Use this if you are orchestrating your own calls to
- :func:`.collection_adapter` in one of the basic SQLAlchemy
- interface methods, or to prevent an automatic ABC method
- decoration from wrapping your implementation::
-
- # normally an 'extend' method on a list-like class would be
- # automatically intercepted and re-implemented in terms of
- # SQLAlchemy events and append(). your implementation will
- # never be called, unless:
- @collection.internally_instrumented
- def extend(self, items): ...
-
- """
- fn._sa_instrumented = True
- return fn
-
- @staticmethod
- @util.deprecated(
- "1.3",
- "The :meth:`.collection.converter` handler is deprecated and will "
- "be removed in a future release. Please refer to the "
- ":class:`.AttributeEvents.bulk_replace` listener interface in "
- "conjunction with the :func:`.event.listen` function.",
- )
- def converter(fn):
- """Tag the method as the collection converter.
-
- This optional method will be called when a collection is being
- replaced entirely, as in::
-
- myobj.acollection = [newvalue1, newvalue2]
-
- The converter method will receive the object being assigned and should
- return an iterable of values suitable for use by the ``appender``
- method. A converter must not assign values or mutate the collection,
- its sole job is to adapt the value the user provides into an iterable
- of values for the ORM's use.
-
- The default converter implementation will use duck-typing to do the
- conversion. A dict-like collection will be convert into an iterable
- of dictionary values, and other types will simply be iterated::
-
- @collection.converter
- def convert(self, other): ...
-
- If the duck-typing of the object does not match the type of this
- collection, a TypeError is raised.
-
- Supply an implementation of this method if you want to expand the
- range of possible types that can be assigned in bulk or perform
- validation on the values about to be assigned.
-
- """
- fn._sa_instrument_role = "converter"
- return fn
-
- @staticmethod
- def adds(arg):
- """Mark the method as adding an entity to the collection.
-
- Adds "add to collection" handling to the method. The decorator
- argument indicates which method argument holds the SQLAlchemy-relevant
- value. Arguments can be specified positionally (i.e. integer) or by
- name::
-
- @collection.adds(1)
- def push(self, item): ...
-
- @collection.adds('entity')
- def do_stuff(self, thing, entity=None): ...
-
- """
-
- def decorator(fn):
- fn._sa_instrument_before = ("fire_append_event", arg)
- return fn
-
- return decorator
-
- @staticmethod
- def replaces(arg):
- """Mark the method as replacing an entity in the collection.
-
- Adds "add to collection" and "remove from collection" handling to
- the method. The decorator argument indicates which method argument
- holds the SQLAlchemy-relevant value to be added, and return value, if
- any will be considered the value to remove.
-
- Arguments can be specified positionally (i.e. integer) or by name::
-
- @collection.replaces(2)
- def __setitem__(self, index, item): ...
-
- """
-
- def decorator(fn):
- fn._sa_instrument_before = ("fire_append_event", arg)
- fn._sa_instrument_after = "fire_remove_event"
- return fn
-
- return decorator
-
- @staticmethod
- def removes(arg):
- """Mark the method as removing an entity in the collection.
-
- Adds "remove from collection" handling to the method. The decorator
- argument indicates which method argument holds the SQLAlchemy-relevant
- value to be removed. Arguments can be specified positionally (i.e.
- integer) or by name::
-
- @collection.removes(1)
- def zap(self, item): ...
-
- For methods where the value to remove is not known at call-time, use
- collection.removes_return.
-
- """
-
- def decorator(fn):
- fn._sa_instrument_before = ("fire_remove_event", arg)
- return fn
-
- return decorator
-
- @staticmethod
- def removes_return():
- """Mark the method as removing an entity in the collection.
-
- Adds "remove from collection" handling to the method. The return
- value of the method, if any, is considered the value to remove. The
- method arguments are not inspected::
-
- @collection.removes_return()
- def pop(self): ...
-
- For methods where the value to remove is known at call-time, use
- collection.remove.
-
- """
-
- def decorator(fn):
- fn._sa_instrument_after = "fire_remove_event"
- return fn
-
- return decorator
-
-
-if TYPE_CHECKING:
-
- def collection_adapter(collection: Collection[Any]) -> CollectionAdapter:
- """Fetch the :class:`.CollectionAdapter` for a collection."""
-
-else:
- collection_adapter = operator.attrgetter("_sa_adapter")
-
-
-class CollectionAdapter:
- """Bridges between the ORM and arbitrary Python collections.
-
- Proxies base-level collection operations (append, remove, iterate)
- to the underlying Python collection, and emits add/remove events for
- entities entering or leaving the collection.
-
- The ORM uses :class:`.CollectionAdapter` exclusively for interaction with
- entity collections.
-
-
- """
-
- __slots__ = (
- "attr",
- "_key",
- "_data",
- "owner_state",
- "_converter",
- "invalidated",
- "empty",
- )
-
- attr: CollectionAttributeImpl
- _key: str
-
- # this is actually a weakref; see note in constructor
- _data: Callable[..., _AdaptedCollectionProtocol]
-
- owner_state: InstanceState[Any]
- _converter: _CollectionConverterProtocol
- invalidated: bool
- empty: bool
-
- def __init__(
- self,
- attr: CollectionAttributeImpl,
- owner_state: InstanceState[Any],
- data: _AdaptedCollectionProtocol,
- ):
- self.attr = attr
- self._key = attr.key
-
- # this weakref stays referenced throughout the lifespan of
- # CollectionAdapter. so while the weakref can return None, this
- # is realistically only during garbage collection of this object, so
- # we type this as a callable that returns _AdaptedCollectionProtocol
- # in all cases.
- self._data = weakref.ref(data) # type: ignore
-
- self.owner_state = owner_state
- data._sa_adapter = self
- self._converter = data._sa_converter
- self.invalidated = False
- self.empty = False
-
- def _warn_invalidated(self) -> None:
- util.warn("This collection has been invalidated.")
-
- @property
- def data(self) -> _AdaptedCollectionProtocol:
- "The entity collection being adapted."
- return self._data()
-
- @property
- def _referenced_by_owner(self) -> bool:
- """return True if the owner state still refers to this collection.
-
- This will return False within a bulk replace operation,
- where this collection is the one being replaced.
-
- """
- return self.owner_state.dict[self._key] is self._data()
-
- def bulk_appender(self):
- return self._data()._sa_appender
-
- def append_with_event(
- self, item: Any, initiator: Optional[AttributeEventToken] = None
- ) -> None:
- """Add an entity to the collection, firing mutation events."""
-
- self._data()._sa_appender(item, _sa_initiator=initiator)
-
- def _set_empty(self, user_data):
- assert (
- not self.empty
- ), "This collection adapter is already in the 'empty' state"
- self.empty = True
- self.owner_state._empty_collections[self._key] = user_data
-
- def _reset_empty(self) -> None:
- assert (
- self.empty
- ), "This collection adapter is not in the 'empty' state"
- self.empty = False
- self.owner_state.dict[self._key] = (
- self.owner_state._empty_collections.pop(self._key)
- )
-
- def _refuse_empty(self) -> NoReturn:
- raise sa_exc.InvalidRequestError(
- "This is a special 'empty' collection which cannot accommodate "
- "internal mutation operations"
- )
-
- def append_without_event(self, item: Any) -> None:
- """Add or restore an entity to the collection, firing no events."""
-
- if self.empty:
- self._refuse_empty()
- self._data()._sa_appender(item, _sa_initiator=False)
-
- def append_multiple_without_event(self, items: Iterable[Any]) -> None:
- """Add or restore an entity to the collection, firing no events."""
- if self.empty:
- self._refuse_empty()
- appender = self._data()._sa_appender
- for item in items:
- appender(item, _sa_initiator=False)
-
- def bulk_remover(self):
- return self._data()._sa_remover
-
- def remove_with_event(
- self, item: Any, initiator: Optional[AttributeEventToken] = None
- ) -> None:
- """Remove an entity from the collection, firing mutation events."""
- self._data()._sa_remover(item, _sa_initiator=initiator)
-
- def remove_without_event(self, item: Any) -> None:
- """Remove an entity from the collection, firing no events."""
- if self.empty:
- self._refuse_empty()
- self._data()._sa_remover(item, _sa_initiator=False)
-
- def clear_with_event(
- self, initiator: Optional[AttributeEventToken] = None
- ) -> None:
- """Empty the collection, firing a mutation event for each entity."""
-
- if self.empty:
- self._refuse_empty()
- remover = self._data()._sa_remover
- for item in list(self):
- remover(item, _sa_initiator=initiator)
-
- def clear_without_event(self) -> None:
- """Empty the collection, firing no events."""
-
- if self.empty:
- self._refuse_empty()
- remover = self._data()._sa_remover
- for item in list(self):
- remover(item, _sa_initiator=False)
-
- def __iter__(self):
- """Iterate over entities in the collection."""
-
- return iter(self._data()._sa_iterator())
-
- def __len__(self):
- """Count entities in the collection."""
- return len(list(self._data()._sa_iterator()))
-
- def __bool__(self):
- return True
-
- def _fire_append_wo_mutation_event_bulk(
- self, items, initiator=None, key=NO_KEY
- ):
- if not items:
- return
-
- if initiator is not False:
- if self.invalidated:
- self._warn_invalidated()
-
- if self.empty:
- self._reset_empty()
-
- for item in items:
- self.attr.fire_append_wo_mutation_event(
- self.owner_state,
- self.owner_state.dict,
- item,
- initiator,
- key,
- )
-
- def fire_append_wo_mutation_event(self, item, initiator=None, key=NO_KEY):
- """Notify that a entity is entering the collection but is already
- present.
-
-
- Initiator is a token owned by the InstrumentedAttribute that
- initiated the membership mutation, and should be left as None
- unless you are passing along an initiator value from a chained
- operation.
-
- .. versionadded:: 1.4.15
-
- """
- if initiator is not False:
- if self.invalidated:
- self._warn_invalidated()
-
- if self.empty:
- self._reset_empty()
-
- return self.attr.fire_append_wo_mutation_event(
- self.owner_state, self.owner_state.dict, item, initiator, key
- )
- else:
- return item
-
- def fire_append_event(self, item, initiator=None, key=NO_KEY):
- """Notify that a entity has entered the collection.
-
- Initiator is a token owned by the InstrumentedAttribute that
- initiated the membership mutation, and should be left as None
- unless you are passing along an initiator value from a chained
- operation.
-
- """
- if initiator is not False:
- if self.invalidated:
- self._warn_invalidated()
-
- if self.empty:
- self._reset_empty()
-
- return self.attr.fire_append_event(
- self.owner_state, self.owner_state.dict, item, initiator, key
- )
- else:
- return item
-
- def _fire_remove_event_bulk(self, items, initiator=None, key=NO_KEY):
- if not items:
- return
-
- if initiator is not False:
- if self.invalidated:
- self._warn_invalidated()
-
- if self.empty:
- self._reset_empty()
-
- for item in items:
- self.attr.fire_remove_event(
- self.owner_state,
- self.owner_state.dict,
- item,
- initiator,
- key,
- )
-
- def fire_remove_event(self, item, initiator=None, key=NO_KEY):
- """Notify that a entity has been removed from the collection.
-
- Initiator is the InstrumentedAttribute that initiated the membership
- mutation, and should be left as None unless you are passing along
- an initiator value from a chained operation.
-
- """
- if initiator is not False:
- if self.invalidated:
- self._warn_invalidated()
-
- if self.empty:
- self._reset_empty()
-
- self.attr.fire_remove_event(
- self.owner_state, self.owner_state.dict, item, initiator, key
- )
-
- def fire_pre_remove_event(self, initiator=None, key=NO_KEY):
- """Notify that an entity is about to be removed from the collection.
-
- Only called if the entity cannot be removed after calling
- fire_remove_event().
-
- """
- if self.invalidated:
- self._warn_invalidated()
- self.attr.fire_pre_remove_event(
- self.owner_state,
- self.owner_state.dict,
- initiator=initiator,
- key=key,
- )
-
- def __getstate__(self):
- return {
- "key": self._key,
- "owner_state": self.owner_state,
- "owner_cls": self.owner_state.class_,
- "data": self.data,
- "invalidated": self.invalidated,
- "empty": self.empty,
- }
-
- def __setstate__(self, d):
- self._key = d["key"]
- self.owner_state = d["owner_state"]
-
- # see note in constructor regarding this type: ignore
- self._data = weakref.ref(d["data"]) # type: ignore
-
- self._converter = d["data"]._sa_converter
- d["data"]._sa_adapter = self
- self.invalidated = d["invalidated"]
- self.attr = getattr(d["owner_cls"], self._key).impl
- self.empty = d.get("empty", False)
-
-
-def bulk_replace(values, existing_adapter, new_adapter, initiator=None):
- """Load a new collection, firing events based on prior like membership.
-
- Appends instances in ``values`` onto the ``new_adapter``. Events will be
- fired for any instance not present in the ``existing_adapter``. Any
- instances in ``existing_adapter`` not present in ``values`` will have
- remove events fired upon them.
-
- :param values: An iterable of collection member instances
-
- :param existing_adapter: A :class:`.CollectionAdapter` of
- instances to be replaced
-
- :param new_adapter: An empty :class:`.CollectionAdapter`
- to load with ``values``
-
-
- """
-
- assert isinstance(values, list)
-
- idset = util.IdentitySet
- existing_idset = idset(existing_adapter or ())
- constants = existing_idset.intersection(values or ())
- additions = idset(values or ()).difference(constants)
- removals = existing_idset.difference(constants)
-
- appender = new_adapter.bulk_appender()
-
- for member in values or ():
- if member in additions:
- appender(member, _sa_initiator=initiator)
- elif member in constants:
- appender(member, _sa_initiator=False)
-
- if existing_adapter:
- existing_adapter._fire_append_wo_mutation_event_bulk(
- constants, initiator=initiator
- )
- existing_adapter._fire_remove_event_bulk(removals, initiator=initiator)
-
-
-def prepare_instrumentation(
- factory: Union[Type[Collection[Any]], _CollectionFactoryType],
-) -> _CollectionFactoryType:
- """Prepare a callable for future use as a collection class factory.
-
- Given a collection class factory (either a type or no-arg callable),
- return another factory that will produce compatible instances when
- called.
-
- This function is responsible for converting collection_class=list
- into the run-time behavior of collection_class=InstrumentedList.
-
- """
-
- impl_factory: _CollectionFactoryType
-
- # Convert a builtin to 'Instrumented*'
- if factory in __canned_instrumentation:
- impl_factory = __canned_instrumentation[factory]
- else:
- impl_factory = cast(_CollectionFactoryType, factory)
-
- cls: Union[_CollectionFactoryType, Type[Collection[Any]]]
-
- # Create a specimen
- cls = type(impl_factory())
-
- # Did factory callable return a builtin?
- if cls in __canned_instrumentation:
- # if so, just convert.
- # in previous major releases, this codepath wasn't working and was
- # not covered by tests. prior to that it supplied a "wrapper"
- # function that would return the class, though the rationale for this
- # case is not known
- impl_factory = __canned_instrumentation[cls]
- cls = type(impl_factory())
-
- # Instrument the class if needed.
- if __instrumentation_mutex.acquire():
- try:
- if getattr(cls, "_sa_instrumented", None) != id(cls):
- _instrument_class(cls)
- finally:
- __instrumentation_mutex.release()
-
- return impl_factory
-
-
-def _instrument_class(cls):
- """Modify methods in a class and install instrumentation."""
-
- # In the normal call flow, a request for any of the 3 basic collection
- # types is transformed into one of our trivial subclasses
- # (e.g. InstrumentedList). Catch anything else that sneaks in here...
- if cls.__module__ == "__builtin__":
- raise sa_exc.ArgumentError(
- "Can not instrument a built-in type. Use a "
- "subclass, even a trivial one."
- )
-
- roles, methods = _locate_roles_and_methods(cls)
-
- _setup_canned_roles(cls, roles, methods)
-
- _assert_required_roles(cls, roles, methods)
-
- _set_collection_attributes(cls, roles, methods)
-
-
-def _locate_roles_and_methods(cls):
- """search for _sa_instrument_role-decorated methods in
- method resolution order, assign to roles.
-
- """
-
- roles: Dict[str, str] = {}
- methods: Dict[str, Tuple[Optional[str], Optional[int], Optional[str]]] = {}
-
- for supercls in cls.__mro__:
- for name, method in vars(supercls).items():
- if not callable(method):
- continue
-
- # note role declarations
- if hasattr(method, "_sa_instrument_role"):
- role = method._sa_instrument_role
- assert role in (
- "appender",
- "remover",
- "iterator",
- "converter",
- )
- roles.setdefault(role, name)
-
- # transfer instrumentation requests from decorated function
- # to the combined queue
- before: Optional[Tuple[str, int]] = None
- after: Optional[str] = None
-
- if hasattr(method, "_sa_instrument_before"):
- op, argument = method._sa_instrument_before
- assert op in ("fire_append_event", "fire_remove_event")
- before = op, argument
- if hasattr(method, "_sa_instrument_after"):
- op = method._sa_instrument_after
- assert op in ("fire_append_event", "fire_remove_event")
- after = op
- if before:
- methods[name] = before + (after,)
- elif after:
- methods[name] = None, None, after
- return roles, methods
-
-
-def _setup_canned_roles(cls, roles, methods):
- """see if this class has "canned" roles based on a known
- collection type (dict, set, list). Apply those roles
- as needed to the "roles" dictionary, and also
- prepare "decorator" methods
-
- """
- collection_type = util.duck_type_collection(cls)
- if collection_type in __interfaces:
- assert collection_type is not None
- canned_roles, decorators = __interfaces[collection_type]
- for role, name in canned_roles.items():
- roles.setdefault(role, name)
-
- # apply ABC auto-decoration to methods that need it
- for method, decorator in decorators.items():
- fn = getattr(cls, method, None)
- if (
- fn
- and method not in methods
- and not hasattr(fn, "_sa_instrumented")
- ):
- setattr(cls, method, decorator(fn))
-
-
-def _assert_required_roles(cls, roles, methods):
- """ensure all roles are present, and apply implicit instrumentation if
- needed
-
- """
- if "appender" not in roles or not hasattr(cls, roles["appender"]):
- raise sa_exc.ArgumentError(
- "Type %s must elect an appender method to be "
- "a collection class" % cls.__name__
- )
- elif roles["appender"] not in methods and not hasattr(
- getattr(cls, roles["appender"]), "_sa_instrumented"
- ):
- methods[roles["appender"]] = ("fire_append_event", 1, None)
-
- if "remover" not in roles or not hasattr(cls, roles["remover"]):
- raise sa_exc.ArgumentError(
- "Type %s must elect a remover method to be "
- "a collection class" % cls.__name__
- )
- elif roles["remover"] not in methods and not hasattr(
- getattr(cls, roles["remover"]), "_sa_instrumented"
- ):
- methods[roles["remover"]] = ("fire_remove_event", 1, None)
-
- if "iterator" not in roles or not hasattr(cls, roles["iterator"]):
- raise sa_exc.ArgumentError(
- "Type %s must elect an iterator method to be "
- "a collection class" % cls.__name__
- )
-
-
-def _set_collection_attributes(cls, roles, methods):
- """apply ad-hoc instrumentation from decorators, class-level defaults
- and implicit role declarations
-
- """
- for method_name, (before, argument, after) in methods.items():
- setattr(
- cls,
- method_name,
- _instrument_membership_mutator(
- getattr(cls, method_name), before, argument, after
- ),
- )
- # intern the role map
- for role, method_name in roles.items():
- setattr(cls, "_sa_%s" % role, getattr(cls, method_name))
-
- cls._sa_adapter = None
-
- if not hasattr(cls, "_sa_converter"):
- cls._sa_converter = None
- cls._sa_instrumented = id(cls)
-
-
-def _instrument_membership_mutator(method, before, argument, after):
- """Route method args and/or return value through the collection
- adapter."""
- # This isn't smart enough to handle @adds(1) for 'def fn(self, (a, b))'
- if before:
- fn_args = list(
- util.flatten_iterator(inspect_getfullargspec(method)[0])
- )
- if isinstance(argument, int):
- pos_arg = argument
- named_arg = len(fn_args) > argument and fn_args[argument] or None
- else:
- if argument in fn_args:
- pos_arg = fn_args.index(argument)
- else:
- pos_arg = None
- named_arg = argument
- del fn_args
-
- def wrapper(*args, **kw):
- if before:
- if pos_arg is None:
- if named_arg not in kw:
- raise sa_exc.ArgumentError(
- "Missing argument %s" % argument
- )
- value = kw[named_arg]
- else:
- if len(args) > pos_arg:
- value = args[pos_arg]
- elif named_arg in kw:
- value = kw[named_arg]
- else:
- raise sa_exc.ArgumentError(
- "Missing argument %s" % argument
- )
-
- initiator = kw.pop("_sa_initiator", None)
- if initiator is False:
- executor = None
- else:
- executor = args[0]._sa_adapter
-
- if before and executor:
- getattr(executor, before)(value, initiator)
-
- if not after or not executor:
- return method(*args, **kw)
- else:
- res = method(*args, **kw)
- if res is not None:
- getattr(executor, after)(res, initiator)
- return res
-
- wrapper._sa_instrumented = True # type: ignore[attr-defined]
- if hasattr(method, "_sa_instrument_role"):
- wrapper._sa_instrument_role = method._sa_instrument_role # type: ignore[attr-defined] # noqa: E501
- wrapper.__name__ = method.__name__
- wrapper.__doc__ = method.__doc__
- return wrapper
-
-
-def __set_wo_mutation(collection, item, _sa_initiator=None):
- """Run set wo mutation events.
-
- The collection is not mutated.
-
- """
- if _sa_initiator is not False:
- executor = collection._sa_adapter
- if executor:
- executor.fire_append_wo_mutation_event(
- item, _sa_initiator, key=None
- )
-
-
-def __set(collection, item, _sa_initiator, key):
- """Run set events.
-
- This event always occurs before the collection is actually mutated.
-
- """
-
- if _sa_initiator is not False:
- executor = collection._sa_adapter
- if executor:
- item = executor.fire_append_event(item, _sa_initiator, key=key)
- return item
-
-
-def __del(collection, item, _sa_initiator, key):
- """Run del events.
-
- This event occurs before the collection is actually mutated, *except*
- in the case of a pop operation, in which case it occurs afterwards.
- For pop operations, the __before_pop hook is called before the
- operation occurs.
-
- """
- if _sa_initiator is not False:
- executor = collection._sa_adapter
- if executor:
- executor.fire_remove_event(item, _sa_initiator, key=key)
-
-
-def __before_pop(collection, _sa_initiator=None):
- """An event which occurs on a before a pop() operation occurs."""
- executor = collection._sa_adapter
- if executor:
- executor.fire_pre_remove_event(_sa_initiator)
-
-
-def _list_decorators() -> Dict[str, Callable[[_FN], _FN]]:
- """Tailored instrumentation wrappers for any list-like class."""
-
- def _tidy(fn):
- fn._sa_instrumented = True
- fn.__doc__ = getattr(list, fn.__name__).__doc__
-
- def append(fn):
- def append(self, item, _sa_initiator=None):
- item = __set(self, item, _sa_initiator, NO_KEY)
- fn(self, item)
-
- _tidy(append)
- return append
-
- def remove(fn):
- def remove(self, value, _sa_initiator=None):
- __del(self, value, _sa_initiator, NO_KEY)
- # testlib.pragma exempt:__eq__
- fn(self, value)
-
- _tidy(remove)
- return remove
-
- def insert(fn):
- def insert(self, index, value):
- value = __set(self, value, None, index)
- fn(self, index, value)
-
- _tidy(insert)
- return insert
-
- def __setitem__(fn):
- def __setitem__(self, index, value):
- if not isinstance(index, slice):
- existing = self[index]
- if existing is not None:
- __del(self, existing, None, index)
- value = __set(self, value, None, index)
- fn(self, index, value)
- else:
- # slice assignment requires __delitem__, insert, __len__
- step = index.step or 1
- start = index.start or 0
- if start < 0:
- start += len(self)
- if index.stop is not None:
- stop = index.stop
- else:
- stop = len(self)
- if stop < 0:
- stop += len(self)
-
- if step == 1:
- if value is self:
- return
- for i in range(start, stop, step):
- if len(self) > start:
- del self[start]
-
- for i, item in enumerate(value):
- self.insert(i + start, item)
- else:
- rng = list(range(start, stop, step))
- if len(value) != len(rng):
- raise ValueError(
- "attempt to assign sequence of size %s to "
- "extended slice of size %s"
- % (len(value), len(rng))
- )
- for i, item in zip(rng, value):
- self.__setitem__(i, item)
-
- _tidy(__setitem__)
- return __setitem__
-
- def __delitem__(fn):
- def __delitem__(self, index):
- if not isinstance(index, slice):
- item = self[index]
- __del(self, item, None, index)
- fn(self, index)
- else:
- # slice deletion requires __getslice__ and a slice-groking
- # __getitem__ for stepped deletion
- # note: not breaking this into atomic dels
- for item in self[index]:
- __del(self, item, None, index)
- fn(self, index)
-
- _tidy(__delitem__)
- return __delitem__
-
- def extend(fn):
- def extend(self, iterable):
- for value in list(iterable):
- self.append(value)
-
- _tidy(extend)
- return extend
-
- def __iadd__(fn):
- def __iadd__(self, iterable):
- # list.__iadd__ takes any iterable and seems to let TypeError
- # raise as-is instead of returning NotImplemented
- for value in list(iterable):
- self.append(value)
- return self
-
- _tidy(__iadd__)
- return __iadd__
-
- def pop(fn):
- def pop(self, index=-1):
- __before_pop(self)
- item = fn(self, index)
- __del(self, item, None, index)
- return item
-
- _tidy(pop)
- return pop
-
- def clear(fn):
- def clear(self, index=-1):
- for item in self:
- __del(self, item, None, index)
- fn(self)
-
- _tidy(clear)
- return clear
-
- # __imul__ : not wrapping this. all members of the collection are already
- # present, so no need to fire appends... wrapping it with an explicit
- # decorator is still possible, so events on *= can be had if they're
- # desired. hard to imagine a use case for __imul__, though.
-
- l = locals().copy()
- l.pop("_tidy")
- return l
-
-
-def _dict_decorators() -> Dict[str, Callable[[_FN], _FN]]:
- """Tailored instrumentation wrappers for any dict-like mapping class."""
-
- def _tidy(fn):
- fn._sa_instrumented = True
- fn.__doc__ = getattr(dict, fn.__name__).__doc__
-
- def __setitem__(fn):
- def __setitem__(self, key, value, _sa_initiator=None):
- if key in self:
- __del(self, self[key], _sa_initiator, key)
- value = __set(self, value, _sa_initiator, key)
- fn(self, key, value)
-
- _tidy(__setitem__)
- return __setitem__
-
- def __delitem__(fn):
- def __delitem__(self, key, _sa_initiator=None):
- if key in self:
- __del(self, self[key], _sa_initiator, key)
- fn(self, key)
-
- _tidy(__delitem__)
- return __delitem__
-
- def clear(fn):
- def clear(self):
- for key in self:
- __del(self, self[key], None, key)
- fn(self)
-
- _tidy(clear)
- return clear
-
- def pop(fn):
- def pop(self, key, default=NO_ARG):
- __before_pop(self)
- _to_del = key in self
- if default is NO_ARG:
- item = fn(self, key)
- else:
- item = fn(self, key, default)
- if _to_del:
- __del(self, item, None, key)
- return item
-
- _tidy(pop)
- return pop
-
- def popitem(fn):
- def popitem(self):
- __before_pop(self)
- item = fn(self)
- __del(self, item[1], None, 1)
- return item
-
- _tidy(popitem)
- return popitem
-
- def setdefault(fn):
- def setdefault(self, key, default=None):
- if key not in self:
- self.__setitem__(key, default)
- return default
- else:
- value = self.__getitem__(key)
- if value is default:
- __set_wo_mutation(self, value, None)
-
- return value
-
- _tidy(setdefault)
- return setdefault
-
- def update(fn):
- def update(self, __other=NO_ARG, **kw):
- if __other is not NO_ARG:
- if hasattr(__other, "keys"):
- for key in list(__other):
- if key not in self or self[key] is not __other[key]:
- self[key] = __other[key]
- else:
- __set_wo_mutation(self, __other[key], None)
- else:
- for key, value in __other:
- if key not in self or self[key] is not value:
- self[key] = value
- else:
- __set_wo_mutation(self, value, None)
- for key in kw:
- if key not in self or self[key] is not kw[key]:
- self[key] = kw[key]
- else:
- __set_wo_mutation(self, kw[key], None)
-
- _tidy(update)
- return update
-
- l = locals().copy()
- l.pop("_tidy")
- return l
-
-
-_set_binop_bases = (set, frozenset)
-
-
-def _set_binops_check_strict(self: Any, obj: Any) -> bool:
- """Allow only set, frozenset and self.__class__-derived
- objects in binops."""
- return isinstance(obj, _set_binop_bases + (self.__class__,))
-
-
-def _set_binops_check_loose(self: Any, obj: Any) -> bool:
- """Allow anything set-like to participate in set binops."""
- return (
- isinstance(obj, _set_binop_bases + (self.__class__,))
- or util.duck_type_collection(obj) == set
- )
-
-
-def _set_decorators() -> Dict[str, Callable[[_FN], _FN]]:
- """Tailored instrumentation wrappers for any set-like class."""
-
- def _tidy(fn):
- fn._sa_instrumented = True
- fn.__doc__ = getattr(set, fn.__name__).__doc__
-
- def add(fn):
- def add(self, value, _sa_initiator=None):
- if value not in self:
- value = __set(self, value, _sa_initiator, NO_KEY)
- else:
- __set_wo_mutation(self, value, _sa_initiator)
- # testlib.pragma exempt:__hash__
- fn(self, value)
-
- _tidy(add)
- return add
-
- def discard(fn):
- def discard(self, value, _sa_initiator=None):
- # testlib.pragma exempt:__hash__
- if value in self:
- __del(self, value, _sa_initiator, NO_KEY)
- # testlib.pragma exempt:__hash__
- fn(self, value)
-
- _tidy(discard)
- return discard
-
- def remove(fn):
- def remove(self, value, _sa_initiator=None):
- # testlib.pragma exempt:__hash__
- if value in self:
- __del(self, value, _sa_initiator, NO_KEY)
- # testlib.pragma exempt:__hash__
- fn(self, value)
-
- _tidy(remove)
- return remove
-
- def pop(fn):
- def pop(self):
- __before_pop(self)
- item = fn(self)
- # for set in particular, we have no way to access the item
- # that will be popped before pop is called.
- __del(self, item, None, NO_KEY)
- return item
-
- _tidy(pop)
- return pop
-
- def clear(fn):
- def clear(self):
- for item in list(self):
- self.remove(item)
-
- _tidy(clear)
- return clear
-
- def update(fn):
- def update(self, value):
- for item in value:
- self.add(item)
-
- _tidy(update)
- return update
-
- def __ior__(fn):
- def __ior__(self, value):
- if not _set_binops_check_strict(self, value):
- return NotImplemented
- for item in value:
- self.add(item)
- return self
-
- _tidy(__ior__)
- return __ior__
-
- def difference_update(fn):
- def difference_update(self, value):
- for item in value:
- self.discard(item)
-
- _tidy(difference_update)
- return difference_update
-
- def __isub__(fn):
- def __isub__(self, value):
- if not _set_binops_check_strict(self, value):
- return NotImplemented
- for item in value:
- self.discard(item)
- return self
-
- _tidy(__isub__)
- return __isub__
-
- def intersection_update(fn):
- def intersection_update(self, other):
- want, have = self.intersection(other), set(self)
- remove, add = have - want, want - have
-
- for item in remove:
- self.remove(item)
- for item in add:
- self.add(item)
-
- _tidy(intersection_update)
- return intersection_update
-
- def __iand__(fn):
- def __iand__(self, other):
- if not _set_binops_check_strict(self, other):
- return NotImplemented
- want, have = self.intersection(other), set(self)
- remove, add = have - want, want - have
-
- for item in remove:
- self.remove(item)
- for item in add:
- self.add(item)
- return self
-
- _tidy(__iand__)
- return __iand__
-
- def symmetric_difference_update(fn):
- def symmetric_difference_update(self, other):
- want, have = self.symmetric_difference(other), set(self)
- remove, add = have - want, want - have
-
- for item in remove:
- self.remove(item)
- for item in add:
- self.add(item)
-
- _tidy(symmetric_difference_update)
- return symmetric_difference_update
-
- def __ixor__(fn):
- def __ixor__(self, other):
- if not _set_binops_check_strict(self, other):
- return NotImplemented
- want, have = self.symmetric_difference(other), set(self)
- remove, add = have - want, want - have
-
- for item in remove:
- self.remove(item)
- for item in add:
- self.add(item)
- return self
-
- _tidy(__ixor__)
- return __ixor__
-
- l = locals().copy()
- l.pop("_tidy")
- return l
-
-
-class InstrumentedList(List[_T]):
- """An instrumented version of the built-in list."""
-
-
-class InstrumentedSet(Set[_T]):
- """An instrumented version of the built-in set."""
-
-
-class InstrumentedDict(Dict[_KT, _VT]):
- """An instrumented version of the built-in dict."""
-
-
-__canned_instrumentation: util.immutabledict[Any, _CollectionFactoryType] = (
- util.immutabledict(
- {
- list: InstrumentedList,
- set: InstrumentedSet,
- dict: InstrumentedDict,
- }
- )
-)
-
-__interfaces: util.immutabledict[
- Any,
- Tuple[
- Dict[str, str],
- Dict[str, Callable[..., Any]],
- ],
-] = util.immutabledict(
- {
- list: (
- {
- "appender": "append",
- "remover": "remove",
- "iterator": "__iter__",
- },
- _list_decorators(),
- ),
- set: (
- {"appender": "add", "remover": "remove", "iterator": "__iter__"},
- _set_decorators(),
- ),
- # decorators are required for dicts and object collections.
- dict: ({"iterator": "values"}, _dict_decorators()),
- }
-)
-
-
-def __go(lcls):
- global keyfunc_mapping, mapped_collection
- global column_keyed_dict, column_mapped_collection
- global MappedCollection, KeyFuncDict
- global attribute_keyed_dict, attribute_mapped_collection
-
- from .mapped_collection import keyfunc_mapping
- from .mapped_collection import column_keyed_dict
- from .mapped_collection import attribute_keyed_dict
- from .mapped_collection import KeyFuncDict
-
- from .mapped_collection import mapped_collection
- from .mapped_collection import column_mapped_collection
- from .mapped_collection import attribute_mapped_collection
- from .mapped_collection import MappedCollection
-
- # ensure instrumentation is associated with
- # these built-in classes; if a user-defined class
- # subclasses these and uses @internally_instrumented,
- # the superclass is otherwise not instrumented.
- # see [ticket:2406].
- _instrument_class(InstrumentedList)
- _instrument_class(InstrumentedSet)
- _instrument_class(KeyFuncDict)
-
-
-__go(locals())
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/context.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/context.py
deleted file mode 100644
index 3056016..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/context.py
+++ /dev/null
@@ -1,3243 +0,0 @@
-# orm/context.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
-# mypy: ignore-errors
-
-from __future__ import annotations
-
-import itertools
-from typing import Any
-from typing import cast
-from typing import Dict
-from typing import Iterable
-from typing import List
-from typing import Optional
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from . import attributes
-from . import interfaces
-from . import loading
-from .base import _is_aliased_class
-from .interfaces import ORMColumnDescription
-from .interfaces import ORMColumnsClauseRole
-from .path_registry import PathRegistry
-from .util import _entity_corresponds_to
-from .util import _ORMJoin
-from .util import _TraceAdaptRole
-from .util import AliasedClass
-from .util import Bundle
-from .util import ORMAdapter
-from .util import ORMStatementAdapter
-from .. import exc as sa_exc
-from .. import future
-from .. import inspect
-from .. import sql
-from .. import util
-from ..sql import coercions
-from ..sql import expression
-from ..sql import roles
-from ..sql import util as sql_util
-from ..sql import visitors
-from ..sql._typing import _TP
-from ..sql._typing import is_dml
-from ..sql._typing import is_insert_update
-from ..sql._typing import is_select_base
-from ..sql.base import _select_iterables
-from ..sql.base import CacheableOptions
-from ..sql.base import CompileState
-from ..sql.base import Executable
-from ..sql.base import Generative
-from ..sql.base import Options
-from ..sql.dml import UpdateBase
-from ..sql.elements import GroupedElement
-from ..sql.elements import TextClause
-from ..sql.selectable import CompoundSelectState
-from ..sql.selectable import LABEL_STYLE_DISAMBIGUATE_ONLY
-from ..sql.selectable import LABEL_STYLE_NONE
-from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
-from ..sql.selectable import Select
-from ..sql.selectable import SelectLabelStyle
-from ..sql.selectable import SelectState
-from ..sql.selectable import TypedReturnsRows
-from ..sql.visitors import InternalTraversal
-
-if TYPE_CHECKING:
- from ._typing import _InternalEntityType
- from ._typing import OrmExecuteOptionsParameter
- from .loading import PostLoad
- from .mapper import Mapper
- from .query import Query
- from .session import _BindArguments
- from .session import Session
- from ..engine import Result
- from ..engine.interfaces import _CoreSingleExecuteParams
- from ..sql._typing import _ColumnsClauseArgument
- from ..sql.compiler import SQLCompiler
- from ..sql.dml import _DMLTableElement
- from ..sql.elements import ColumnElement
- from ..sql.selectable import _JoinTargetElement
- from ..sql.selectable import _LabelConventionCallable
- from ..sql.selectable import _SetupJoinsElement
- from ..sql.selectable import ExecutableReturnsRows
- from ..sql.selectable import SelectBase
- from ..sql.type_api import TypeEngine
-
-_T = TypeVar("_T", bound=Any)
-_path_registry = PathRegistry.root
-
-_EMPTY_DICT = util.immutabledict()
-
-
-LABEL_STYLE_LEGACY_ORM = SelectLabelStyle.LABEL_STYLE_LEGACY_ORM
-
-
-class QueryContext:
- __slots__ = (
- "top_level_context",
- "compile_state",
- "query",
- "params",
- "load_options",
- "bind_arguments",
- "execution_options",
- "session",
- "autoflush",
- "populate_existing",
- "invoke_all_eagers",
- "version_check",
- "refresh_state",
- "create_eager_joins",
- "propagated_loader_options",
- "attributes",
- "runid",
- "partials",
- "post_load_paths",
- "identity_token",
- "yield_per",
- "loaders_require_buffering",
- "loaders_require_uniquing",
- )
-
- runid: int
- post_load_paths: Dict[PathRegistry, PostLoad]
- compile_state: ORMCompileState
-
- class default_load_options(Options):
- _only_return_tuples = False
- _populate_existing = False
- _version_check = False
- _invoke_all_eagers = True
- _autoflush = True
- _identity_token = None
- _yield_per = None
- _refresh_state = None
- _lazy_loaded_from = None
- _legacy_uniquing = False
- _sa_top_level_orm_context = None
- _is_user_refresh = False
-
- def __init__(
- self,
- compile_state: CompileState,
- statement: Union[Select[Any], FromStatement[Any]],
- params: _CoreSingleExecuteParams,
- session: Session,
- load_options: Union[
- Type[QueryContext.default_load_options],
- QueryContext.default_load_options,
- ],
- execution_options: Optional[OrmExecuteOptionsParameter] = None,
- bind_arguments: Optional[_BindArguments] = None,
- ):
- self.load_options = load_options
- self.execution_options = execution_options or _EMPTY_DICT
- self.bind_arguments = bind_arguments or _EMPTY_DICT
- self.compile_state = compile_state
- self.query = statement
- self.session = session
- self.loaders_require_buffering = False
- self.loaders_require_uniquing = False
- self.params = params
- self.top_level_context = load_options._sa_top_level_orm_context
-
- cached_options = compile_state.select_statement._with_options
- uncached_options = statement._with_options
-
- # see issue #7447 , #8399 for some background
- # propagated loader options will be present on loaded InstanceState
- # objects under state.load_options and are typically used by
- # LazyLoader to apply options to the SELECT statement it emits.
- # For compile state options (i.e. loader strategy options), these
- # need to line up with the ".load_path" attribute which in
- # loader.py is pulled from context.compile_state.current_path.
- # so, this means these options have to be the ones from the
- # *cached* statement that's travelling with compile_state, not the
- # *current* statement which won't match up for an ad-hoc
- # AliasedClass
- self.propagated_loader_options = tuple(
- opt._adapt_cached_option_to_uncached_option(self, uncached_opt)
- for opt, uncached_opt in zip(cached_options, uncached_options)
- if opt.propagate_to_loaders
- )
-
- self.attributes = dict(compile_state.attributes)
-
- self.autoflush = load_options._autoflush
- self.populate_existing = load_options._populate_existing
- self.invoke_all_eagers = load_options._invoke_all_eagers
- self.version_check = load_options._version_check
- self.refresh_state = load_options._refresh_state
- self.yield_per = load_options._yield_per
- self.identity_token = load_options._identity_token
-
- def _get_top_level_context(self) -> QueryContext:
- return self.top_level_context or self
-
-
-_orm_load_exec_options = util.immutabledict(
- {"_result_disable_adapt_to_context": True}
-)
-
-
-class AbstractORMCompileState(CompileState):
- is_dml_returning = False
-
- def _init_global_attributes(
- self, statement, compiler, *, toplevel, process_criteria_for_toplevel
- ):
- self.attributes = {}
-
- if compiler is None:
- # this is the legacy / testing only ORM _compile_state() use case.
- # there is no need to apply criteria options for this.
- self.global_attributes = ga = {}
- assert toplevel
- return
- else:
- self.global_attributes = ga = compiler._global_attributes
-
- if toplevel:
- ga["toplevel_orm"] = True
-
- if process_criteria_for_toplevel:
- for opt in statement._with_options:
- if opt._is_criteria_option:
- opt.process_compile_state(self)
-
- return
- elif ga.get("toplevel_orm", False):
- return
-
- stack_0 = compiler.stack[0]
-
- try:
- toplevel_stmt = stack_0["selectable"]
- except KeyError:
- pass
- else:
- for opt in toplevel_stmt._with_options:
- if opt._is_compile_state and opt._is_criteria_option:
- opt.process_compile_state(self)
-
- ga["toplevel_orm"] = True
-
- @classmethod
- def create_for_statement(
- cls,
- statement: Union[Select, FromStatement],
- compiler: Optional[SQLCompiler],
- **kw: Any,
- ) -> AbstractORMCompileState:
- """Create a context for a statement given a :class:`.Compiler`.
-
- This method is always invoked in the context of SQLCompiler.process().
-
- For a Select object, this would be invoked from
- SQLCompiler.visit_select(). For the special FromStatement object used
- by Query to indicate "Query.from_statement()", this is called by
- FromStatement._compiler_dispatch() that would be called by
- SQLCompiler.process().
- """
- return super().create_for_statement(statement, compiler, **kw)
-
- @classmethod
- def orm_pre_session_exec(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- is_pre_event,
- ):
- raise NotImplementedError()
-
- @classmethod
- def orm_execute_statement(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- conn,
- ) -> Result:
- result = conn.execute(
- statement, params or {}, execution_options=execution_options
- )
- return cls.orm_setup_cursor_result(
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- result,
- )
-
- @classmethod
- def orm_setup_cursor_result(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- result,
- ):
- raise NotImplementedError()
-
-
-class AutoflushOnlyORMCompileState(AbstractORMCompileState):
- """ORM compile state that is a passthrough, except for autoflush."""
-
- @classmethod
- def orm_pre_session_exec(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- is_pre_event,
- ):
- # consume result-level load_options. These may have been set up
- # in an ORMExecuteState hook
- (
- load_options,
- execution_options,
- ) = QueryContext.default_load_options.from_execution_options(
- "_sa_orm_load_options",
- {
- "autoflush",
- },
- execution_options,
- statement._execution_options,
- )
-
- if not is_pre_event and load_options._autoflush:
- session._autoflush()
-
- return statement, execution_options
-
- @classmethod
- def orm_setup_cursor_result(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- result,
- ):
- return result
-
-
-class ORMCompileState(AbstractORMCompileState):
- class default_compile_options(CacheableOptions):
- _cache_key_traversal = [
- ("_use_legacy_query_style", InternalTraversal.dp_boolean),
- ("_for_statement", InternalTraversal.dp_boolean),
- ("_bake_ok", InternalTraversal.dp_boolean),
- ("_current_path", InternalTraversal.dp_has_cache_key),
- ("_enable_single_crit", InternalTraversal.dp_boolean),
- ("_enable_eagerloads", InternalTraversal.dp_boolean),
- ("_only_load_props", InternalTraversal.dp_plain_obj),
- ("_set_base_alias", InternalTraversal.dp_boolean),
- ("_for_refresh_state", InternalTraversal.dp_boolean),
- ("_render_for_subquery", InternalTraversal.dp_boolean),
- ("_is_star", InternalTraversal.dp_boolean),
- ]
-
- # set to True by default from Query._statement_20(), to indicate
- # the rendered query should look like a legacy ORM query. right
- # now this basically indicates we should use tablename_columnname
- # style labels. Generally indicates the statement originated
- # from a Query object.
- _use_legacy_query_style = False
-
- # set *only* when we are coming from the Query.statement
- # accessor, or a Query-level equivalent such as
- # query.subquery(). this supersedes "toplevel".
- _for_statement = False
-
- _bake_ok = True
- _current_path = _path_registry
- _enable_single_crit = True
- _enable_eagerloads = True
- _only_load_props = None
- _set_base_alias = False
- _for_refresh_state = False
- _render_for_subquery = False
- _is_star = False
-
- attributes: Dict[Any, Any]
- global_attributes: Dict[Any, Any]
-
- statement: Union[Select[Any], FromStatement[Any]]
- select_statement: Union[Select[Any], FromStatement[Any]]
- _entities: List[_QueryEntity]
- _polymorphic_adapters: Dict[_InternalEntityType, ORMAdapter]
- compile_options: Union[
- Type[default_compile_options], default_compile_options
- ]
- _primary_entity: Optional[_QueryEntity]
- use_legacy_query_style: bool
- _label_convention: _LabelConventionCallable
- primary_columns: List[ColumnElement[Any]]
- secondary_columns: List[ColumnElement[Any]]
- dedupe_columns: Set[ColumnElement[Any]]
- create_eager_joins: List[
- # TODO: this structure is set up by JoinedLoader
- Tuple[Any, ...]
- ]
- current_path: PathRegistry = _path_registry
- _has_mapper_entities = False
-
- def __init__(self, *arg, **kw):
- raise NotImplementedError()
-
- if TYPE_CHECKING:
-
- @classmethod
- def create_for_statement(
- cls,
- statement: Union[Select, FromStatement],
- compiler: Optional[SQLCompiler],
- **kw: Any,
- ) -> ORMCompileState: ...
-
- def _append_dedupe_col_collection(self, obj, col_collection):
- dedupe = self.dedupe_columns
- if obj not in dedupe:
- dedupe.add(obj)
- col_collection.append(obj)
-
- @classmethod
- def _column_naming_convention(
- cls, label_style: SelectLabelStyle, legacy: bool
- ) -> _LabelConventionCallable:
- if legacy:
-
- def name(col, col_name=None):
- if col_name:
- return col_name
- else:
- return getattr(col, "key")
-
- return name
- else:
- return SelectState._column_naming_convention(label_style)
-
- @classmethod
- def get_column_descriptions(cls, statement):
- return _column_descriptions(statement)
-
- @classmethod
- def orm_pre_session_exec(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- is_pre_event,
- ):
- # consume result-level load_options. These may have been set up
- # in an ORMExecuteState hook
- (
- load_options,
- execution_options,
- ) = QueryContext.default_load_options.from_execution_options(
- "_sa_orm_load_options",
- {
- "populate_existing",
- "autoflush",
- "yield_per",
- "identity_token",
- "sa_top_level_orm_context",
- },
- execution_options,
- statement._execution_options,
- )
-
- # default execution options for ORM results:
- # 1. _result_disable_adapt_to_context=True
- # this will disable the ResultSetMetadata._adapt_to_context()
- # step which we don't need, as we have result processors cached
- # against the original SELECT statement before caching.
-
- if "sa_top_level_orm_context" in execution_options:
- ctx = execution_options["sa_top_level_orm_context"]
- execution_options = ctx.query._execution_options.merge_with(
- ctx.execution_options, execution_options
- )
-
- if not execution_options:
- execution_options = _orm_load_exec_options
- else:
- execution_options = execution_options.union(_orm_load_exec_options)
-
- # would have been placed here by legacy Query only
- if load_options._yield_per:
- execution_options = execution_options.union(
- {"yield_per": load_options._yield_per}
- )
-
- if (
- getattr(statement._compile_options, "_current_path", None)
- and len(statement._compile_options._current_path) > 10
- and execution_options.get("compiled_cache", True) is not None
- ):
- execution_options: util.immutabledict[str, Any] = (
- execution_options.union(
- {
- "compiled_cache": None,
- "_cache_disable_reason": "excess depth for "
- "ORM loader options",
- }
- )
- )
-
- bind_arguments["clause"] = statement
-
- # new in 1.4 - the coercions system is leveraged to allow the
- # "subject" mapper of a statement be propagated to the top
- # as the statement is built. "subject" mapper is the generally
- # standard object used as an identifier for multi-database schemes.
-
- # we are here based on the fact that _propagate_attrs contains
- # "compile_state_plugin": "orm". The "plugin_subject"
- # needs to be present as well.
-
- try:
- plugin_subject = statement._propagate_attrs["plugin_subject"]
- except KeyError:
- assert False, "statement had 'orm' plugin but no plugin_subject"
- else:
- if plugin_subject:
- bind_arguments["mapper"] = plugin_subject.mapper
-
- if not is_pre_event and load_options._autoflush:
- session._autoflush()
-
- return statement, execution_options
-
- @classmethod
- def orm_setup_cursor_result(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- result,
- ):
- execution_context = result.context
- compile_state = execution_context.compiled.compile_state
-
- # cover edge case where ORM entities used in legacy select
- # were passed to session.execute:
- # session.execute(legacy_select([User.id, User.name]))
- # see test_query->test_legacy_tuple_old_select
-
- load_options = execution_options.get(
- "_sa_orm_load_options", QueryContext.default_load_options
- )
-
- if compile_state.compile_options._is_star:
- return result
-
- querycontext = QueryContext(
- compile_state,
- statement,
- params,
- session,
- load_options,
- execution_options,
- bind_arguments,
- )
- return loading.instances(result, querycontext)
-
- @property
- def _lead_mapper_entities(self):
- """return all _MapperEntity objects in the lead entities collection.
-
- Does **not** include entities that have been replaced by
- with_entities(), with_only_columns()
-
- """
- return [
- ent for ent in self._entities if isinstance(ent, _MapperEntity)
- ]
-
- def _create_with_polymorphic_adapter(self, ext_info, selectable):
- """given MapperEntity or ORMColumnEntity, setup polymorphic loading
- if called for by the Mapper.
-
- As of #8168 in 2.0.0rc1, polymorphic adapters, which greatly increase
- the complexity of the query creation process, are not used at all
- except in the quasi-legacy cases of with_polymorphic referring to an
- alias and/or subquery. This would apply to concrete polymorphic
- loading, and joined inheritance where a subquery is
- passed to with_polymorphic (which is completely unnecessary in modern
- use).
-
- """
- if (
- not ext_info.is_aliased_class
- and ext_info.mapper.persist_selectable
- not in self._polymorphic_adapters
- ):
- for mp in ext_info.mapper.iterate_to_root():
- self._mapper_loads_polymorphically_with(
- mp,
- ORMAdapter(
- _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER,
- mp,
- equivalents=mp._equivalent_columns,
- selectable=selectable,
- ),
- )
-
- def _mapper_loads_polymorphically_with(self, mapper, adapter):
- for m2 in mapper._with_polymorphic_mappers or [mapper]:
- self._polymorphic_adapters[m2] = adapter
-
- for m in m2.iterate_to_root():
- self._polymorphic_adapters[m.local_table] = adapter
-
- @classmethod
- def _create_entities_collection(cls, query, legacy):
- raise NotImplementedError(
- "this method only works for ORMSelectCompileState"
- )
-
-
-class DMLReturningColFilter:
- """an adapter used for the DML RETURNING case.
-
- Has a subset of the interface used by
- :class:`.ORMAdapter` and is used for :class:`._QueryEntity`
- instances to set up their columns as used in RETURNING for a
- DML statement.
-
- """
-
- __slots__ = ("mapper", "columns", "__weakref__")
-
- def __init__(self, target_mapper, immediate_dml_mapper):
- if (
- immediate_dml_mapper is not None
- and target_mapper.local_table
- is not immediate_dml_mapper.local_table
- ):
- # joined inh, or in theory other kinds of multi-table mappings
- self.mapper = immediate_dml_mapper
- else:
- # single inh, normal mappings, etc.
- self.mapper = target_mapper
- self.columns = self.columns = util.WeakPopulateDict(
- self.adapt_check_present # type: ignore
- )
-
- def __call__(self, col, as_filter):
- for cc in sql_util._find_columns(col):
- c2 = self.adapt_check_present(cc)
- if c2 is not None:
- return col
- else:
- return None
-
- def adapt_check_present(self, col):
- mapper = self.mapper
- prop = mapper._columntoproperty.get(col, None)
- if prop is None:
- return None
- return mapper.local_table.c.corresponding_column(col)
-
-
-@sql.base.CompileState.plugin_for("orm", "orm_from_statement")
-class ORMFromStatementCompileState(ORMCompileState):
- _from_obj_alias = None
- _has_mapper_entities = False
-
- statement_container: FromStatement
- requested_statement: Union[SelectBase, TextClause, UpdateBase]
- dml_table: Optional[_DMLTableElement] = None
-
- _has_orm_entities = False
- multi_row_eager_loaders = False
- eager_adding_joins = False
- compound_eager_adapter = None
-
- extra_criteria_entities = _EMPTY_DICT
- eager_joins = _EMPTY_DICT
-
- @classmethod
- def create_for_statement(
- cls,
- statement_container: Union[Select, FromStatement],
- compiler: Optional[SQLCompiler],
- **kw: Any,
- ) -> ORMFromStatementCompileState:
- assert isinstance(statement_container, FromStatement)
-
- if compiler is not None and compiler.stack:
- raise sa_exc.CompileError(
- "The ORM FromStatement construct only supports being "
- "invoked as the topmost statement, as it is only intended to "
- "define how result rows should be returned."
- )
-
- self = cls.__new__(cls)
- self._primary_entity = None
-
- self.use_legacy_query_style = (
- statement_container._compile_options._use_legacy_query_style
- )
- self.statement_container = self.select_statement = statement_container
- self.requested_statement = statement = statement_container.element
-
- if statement.is_dml:
- self.dml_table = statement.table
- self.is_dml_returning = True
-
- self._entities = []
- self._polymorphic_adapters = {}
-
- self.compile_options = statement_container._compile_options
-
- if (
- self.use_legacy_query_style
- and isinstance(statement, expression.SelectBase)
- and not statement._is_textual
- and not statement.is_dml
- and statement._label_style is LABEL_STYLE_NONE
- ):
- self.statement = statement.set_label_style(
- LABEL_STYLE_TABLENAME_PLUS_COL
- )
- else:
- self.statement = statement
-
- self._label_convention = self._column_naming_convention(
- (
- statement._label_style
- if not statement._is_textual and not statement.is_dml
- else LABEL_STYLE_NONE
- ),
- self.use_legacy_query_style,
- )
-
- _QueryEntity.to_compile_state(
- self,
- statement_container._raw_columns,
- self._entities,
- is_current_entities=True,
- )
-
- self.current_path = statement_container._compile_options._current_path
-
- self._init_global_attributes(
- statement_container,
- compiler,
- process_criteria_for_toplevel=False,
- toplevel=True,
- )
-
- if statement_container._with_options:
- for opt in statement_container._with_options:
- if opt._is_compile_state:
- opt.process_compile_state(self)
-
- if statement_container._with_context_options:
- for fn, key in statement_container._with_context_options:
- fn(self)
-
- self.primary_columns = []
- self.secondary_columns = []
- self.dedupe_columns = set()
- self.create_eager_joins = []
- self._fallback_from_clauses = []
-
- self.order_by = None
-
- if isinstance(self.statement, expression.TextClause):
- # TextClause has no "column" objects at all. for this case,
- # we generate columns from our _QueryEntity objects, then
- # flip on all the "please match no matter what" parameters.
- self.extra_criteria_entities = {}
-
- for entity in self._entities:
- entity.setup_compile_state(self)
-
- compiler._ordered_columns = compiler._textual_ordered_columns = (
- False
- )
-
- # enable looser result column matching. this is shown to be
- # needed by test_query.py::TextTest
- compiler._loose_column_name_matching = True
-
- for c in self.primary_columns:
- compiler.process(
- c,
- within_columns_clause=True,
- add_to_result_map=compiler._add_to_result_map,
- )
- else:
- # for everyone else, Select, Insert, Update, TextualSelect, they
- # have column objects already. After much
- # experimentation here, the best approach seems to be, use
- # those columns completely, don't interfere with the compiler
- # at all; just in ORM land, use an adapter to convert from
- # our ORM columns to whatever columns are in the statement,
- # before we look in the result row. Adapt on names
- # to accept cases such as issue #9217, however also allow
- # this to be overridden for cases such as #9273.
- self._from_obj_alias = ORMStatementAdapter(
- _TraceAdaptRole.ADAPT_FROM_STATEMENT,
- self.statement,
- adapt_on_names=statement_container._adapt_on_names,
- )
-
- return self
-
- def _adapt_col_list(self, cols, current_adapter):
- return cols
-
- def _get_current_adapter(self):
- return None
-
- def setup_dml_returning_compile_state(self, dml_mapper):
- """used by BulkORMInsert (and Update / Delete?) to set up a handler
- for RETURNING to return ORM objects and expressions
-
- """
- target_mapper = self.statement._propagate_attrs.get(
- "plugin_subject", None
- )
- adapter = DMLReturningColFilter(target_mapper, dml_mapper)
-
- if self.compile_options._is_star and (len(self._entities) != 1):
- raise sa_exc.CompileError(
- "Can't generate ORM query that includes multiple expressions "
- "at the same time as '*'; query for '*' alone if present"
- )
-
- for entity in self._entities:
- entity.setup_dml_returning_compile_state(self, adapter)
-
-
-class FromStatement(GroupedElement, Generative, TypedReturnsRows[_TP]):
- """Core construct that represents a load of ORM objects from various
- :class:`.ReturnsRows` and other classes including:
-
- :class:`.Select`, :class:`.TextClause`, :class:`.TextualSelect`,
- :class:`.CompoundSelect`, :class`.Insert`, :class:`.Update`,
- and in theory, :class:`.Delete`.
-
- """
-
- __visit_name__ = "orm_from_statement"
-
- _compile_options = ORMFromStatementCompileState.default_compile_options
-
- _compile_state_factory = ORMFromStatementCompileState.create_for_statement
-
- _for_update_arg = None
-
- element: Union[ExecutableReturnsRows, TextClause]
-
- _adapt_on_names: bool
-
- _traverse_internals = [
- ("_raw_columns", InternalTraversal.dp_clauseelement_list),
- ("element", InternalTraversal.dp_clauseelement),
- ] + Executable._executable_traverse_internals
-
- _cache_key_traversal = _traverse_internals + [
- ("_compile_options", InternalTraversal.dp_has_cache_key)
- ]
-
- def __init__(
- self,
- entities: Iterable[_ColumnsClauseArgument[Any]],
- element: Union[ExecutableReturnsRows, TextClause],
- _adapt_on_names: bool = True,
- ):
- self._raw_columns = [
- coercions.expect(
- roles.ColumnsClauseRole,
- ent,
- apply_propagate_attrs=self,
- post_inspect=True,
- )
- for ent in util.to_list(entities)
- ]
- self.element = element
- self.is_dml = element.is_dml
- self._label_style = (
- element._label_style if is_select_base(element) else None
- )
- self._adapt_on_names = _adapt_on_names
-
- def _compiler_dispatch(self, compiler, **kw):
- """provide a fixed _compiler_dispatch method.
-
- This is roughly similar to using the sqlalchemy.ext.compiler
- ``@compiles`` extension.
-
- """
-
- compile_state = self._compile_state_factory(self, compiler, **kw)
-
- toplevel = not compiler.stack
-
- if toplevel:
- compiler.compile_state = compile_state
-
- return compiler.process(compile_state.statement, **kw)
-
- @property
- def column_descriptions(self):
- """Return a :term:`plugin-enabled` 'column descriptions' structure
- referring to the columns which are SELECTed by this statement.
-
- See the section :ref:`queryguide_inspection` for an overview
- of this feature.
-
- .. seealso::
-
- :ref:`queryguide_inspection` - ORM background
-
- """
- meth = cast(
- ORMSelectCompileState, SelectState.get_plugin_class(self)
- ).get_column_descriptions
- return meth(self)
-
- def _ensure_disambiguated_names(self):
- return self
-
- def get_children(self, **kw):
- yield from itertools.chain.from_iterable(
- element._from_objects for element in self._raw_columns
- )
- yield from super().get_children(**kw)
-
- @property
- def _all_selected_columns(self):
- return self.element._all_selected_columns
-
- @property
- def _return_defaults(self):
- return self.element._return_defaults if is_dml(self.element) else None
-
- @property
- def _returning(self):
- return self.element._returning if is_dml(self.element) else None
-
- @property
- def _inline(self):
- return self.element._inline if is_insert_update(self.element) else None
-
-
-@sql.base.CompileState.plugin_for("orm", "compound_select")
-class CompoundSelectCompileState(
- AutoflushOnlyORMCompileState, CompoundSelectState
-):
- pass
-
-
-@sql.base.CompileState.plugin_for("orm", "select")
-class ORMSelectCompileState(ORMCompileState, SelectState):
- _already_joined_edges = ()
-
- _memoized_entities = _EMPTY_DICT
-
- _from_obj_alias = None
- _has_mapper_entities = False
-
- _has_orm_entities = False
- multi_row_eager_loaders = False
- eager_adding_joins = False
- compound_eager_adapter = None
-
- correlate = None
- correlate_except = None
- _where_criteria = ()
- _having_criteria = ()
-
- @classmethod
- def create_for_statement(
- cls,
- statement: Union[Select, FromStatement],
- compiler: Optional[SQLCompiler],
- **kw: Any,
- ) -> ORMSelectCompileState:
- """compiler hook, we arrive here from compiler.visit_select() only."""
-
- self = cls.__new__(cls)
-
- if compiler is not None:
- toplevel = not compiler.stack
- else:
- toplevel = True
-
- select_statement = statement
-
- # if we are a select() that was never a legacy Query, we won't
- # have ORM level compile options.
- statement._compile_options = cls.default_compile_options.safe_merge(
- statement._compile_options
- )
-
- if select_statement._execution_options:
- # execution options should not impact the compilation of a
- # query, and at the moment subqueryloader is putting some things
- # in here that we explicitly don't want stuck in a cache.
- self.select_statement = select_statement._clone()
- self.select_statement._execution_options = util.immutabledict()
- else:
- self.select_statement = select_statement
-
- # indicates this select() came from Query.statement
- self.for_statement = select_statement._compile_options._for_statement
-
- # generally if we are from Query or directly from a select()
- self.use_legacy_query_style = (
- select_statement._compile_options._use_legacy_query_style
- )
-
- self._entities = []
- self._primary_entity = None
- self._polymorphic_adapters = {}
-
- self.compile_options = select_statement._compile_options
-
- if not toplevel:
- # for subqueries, turn off eagerloads and set
- # "render_for_subquery".
- self.compile_options += {
- "_enable_eagerloads": False,
- "_render_for_subquery": True,
- }
-
- # determine label style. we can make different decisions here.
- # at the moment, trying to see if we can always use DISAMBIGUATE_ONLY
- # rather than LABEL_STYLE_NONE, and if we can use disambiguate style
- # for new style ORM selects too.
- if (
- self.use_legacy_query_style
- and self.select_statement._label_style is LABEL_STYLE_LEGACY_ORM
- ):
- if not self.for_statement:
- self.label_style = LABEL_STYLE_TABLENAME_PLUS_COL
- else:
- self.label_style = LABEL_STYLE_DISAMBIGUATE_ONLY
- else:
- self.label_style = self.select_statement._label_style
-
- if select_statement._memoized_select_entities:
- self._memoized_entities = {
- memoized_entities: _QueryEntity.to_compile_state(
- self,
- memoized_entities._raw_columns,
- [],
- is_current_entities=False,
- )
- for memoized_entities in (
- select_statement._memoized_select_entities
- )
- }
-
- # label_convention is stateful and will yield deduping keys if it
- # sees the same key twice. therefore it's important that it is not
- # invoked for the above "memoized" entities that aren't actually
- # in the columns clause
- self._label_convention = self._column_naming_convention(
- statement._label_style, self.use_legacy_query_style
- )
-
- _QueryEntity.to_compile_state(
- self,
- select_statement._raw_columns,
- self._entities,
- is_current_entities=True,
- )
-
- self.current_path = select_statement._compile_options._current_path
-
- self.eager_order_by = ()
-
- self._init_global_attributes(
- select_statement,
- compiler,
- toplevel=toplevel,
- process_criteria_for_toplevel=False,
- )
-
- if toplevel and (
- select_statement._with_options
- or select_statement._memoized_select_entities
- ):
- for (
- memoized_entities
- ) in select_statement._memoized_select_entities:
- for opt in memoized_entities._with_options:
- if opt._is_compile_state:
- opt.process_compile_state_replaced_entities(
- self,
- [
- ent
- for ent in self._memoized_entities[
- memoized_entities
- ]
- if isinstance(ent, _MapperEntity)
- ],
- )
-
- for opt in self.select_statement._with_options:
- if opt._is_compile_state:
- opt.process_compile_state(self)
-
- # uncomment to print out the context.attributes structure
- # after it's been set up above
- # self._dump_option_struct()
-
- if select_statement._with_context_options:
- for fn, key in select_statement._with_context_options:
- fn(self)
-
- self.primary_columns = []
- self.secondary_columns = []
- self.dedupe_columns = set()
- self.eager_joins = {}
- self.extra_criteria_entities = {}
- self.create_eager_joins = []
- self._fallback_from_clauses = []
-
- # normalize the FROM clauses early by themselves, as this makes
- # it an easier job when we need to assemble a JOIN onto these,
- # for select.join() as well as joinedload(). As of 1.4 there are now
- # potentially more complex sets of FROM objects here as the use
- # of lambda statements for lazyload, load_on_pk etc. uses more
- # cloning of the select() construct. See #6495
- self.from_clauses = self._normalize_froms(
- info.selectable for info in select_statement._from_obj
- )
-
- # this is a fairly arbitrary break into a second method,
- # so it might be nicer to break up create_for_statement()
- # and _setup_for_generate into three or four logical sections
- self._setup_for_generate()
-
- SelectState.__init__(self, self.statement, compiler, **kw)
- return self
-
- def _dump_option_struct(self):
- print("\n---------------------------------------------------\n")
- print(f"current path: {self.current_path}")
- for key in self.attributes:
- if isinstance(key, tuple) and key[0] == "loader":
- print(f"\nLoader: {PathRegistry.coerce(key[1])}")
- print(f" {self.attributes[key]}")
- print(f" {self.attributes[key].__dict__}")
- elif isinstance(key, tuple) and key[0] == "path_with_polymorphic":
- print(f"\nWith Polymorphic: {PathRegistry.coerce(key[1])}")
- print(f" {self.attributes[key]}")
-
- def _setup_for_generate(self):
- query = self.select_statement
-
- self.statement = None
- self._join_entities = ()
-
- if self.compile_options._set_base_alias:
- # legacy Query only
- self._set_select_from_alias()
-
- for memoized_entities in query._memoized_select_entities:
- if memoized_entities._setup_joins:
- self._join(
- memoized_entities._setup_joins,
- self._memoized_entities[memoized_entities],
- )
-
- if query._setup_joins:
- self._join(query._setup_joins, self._entities)
-
- current_adapter = self._get_current_adapter()
-
- if query._where_criteria:
- self._where_criteria = query._where_criteria
-
- if current_adapter:
- self._where_criteria = tuple(
- current_adapter(crit, True)
- for crit in self._where_criteria
- )
-
- # TODO: some complexity with order_by here was due to mapper.order_by.
- # now that this is removed we can hopefully make order_by /
- # group_by act identically to how they are in Core select.
- self.order_by = (
- self._adapt_col_list(query._order_by_clauses, current_adapter)
- if current_adapter and query._order_by_clauses not in (None, False)
- else query._order_by_clauses
- )
-
- if query._having_criteria:
- self._having_criteria = tuple(
- current_adapter(crit, True) if current_adapter else crit
- for crit in query._having_criteria
- )
-
- self.group_by = (
- self._adapt_col_list(
- util.flatten_iterator(query._group_by_clauses), current_adapter
- )
- if current_adapter and query._group_by_clauses not in (None, False)
- else query._group_by_clauses or None
- )
-
- if self.eager_order_by:
- adapter = self.from_clauses[0]._target_adapter
- self.eager_order_by = adapter.copy_and_process(self.eager_order_by)
-
- if query._distinct_on:
- self.distinct_on = self._adapt_col_list(
- query._distinct_on, current_adapter
- )
- else:
- self.distinct_on = ()
-
- self.distinct = query._distinct
-
- if query._correlate:
- # ORM mapped entities that are mapped to joins can be passed
- # to .correlate, so here they are broken into their component
- # tables.
- self.correlate = tuple(
- util.flatten_iterator(
- sql_util.surface_selectables(s) if s is not None else None
- for s in query._correlate
- )
- )
- elif query._correlate_except is not None:
- self.correlate_except = tuple(
- util.flatten_iterator(
- sql_util.surface_selectables(s) if s is not None else None
- for s in query._correlate_except
- )
- )
- elif not query._auto_correlate:
- self.correlate = (None,)
-
- # PART II
-
- self._for_update_arg = query._for_update_arg
-
- if self.compile_options._is_star and (len(self._entities) != 1):
- raise sa_exc.CompileError(
- "Can't generate ORM query that includes multiple expressions "
- "at the same time as '*'; query for '*' alone if present"
- )
- for entity in self._entities:
- entity.setup_compile_state(self)
-
- for rec in self.create_eager_joins:
- strategy = rec[0]
- strategy(self, *rec[1:])
-
- # else "load from discrete FROMs" mode,
- # i.e. when each _MappedEntity has its own FROM
-
- if self.compile_options._enable_single_crit:
- self._adjust_for_extra_criteria()
-
- if not self.primary_columns:
- if self.compile_options._only_load_props:
- assert False, "no columns were included in _only_load_props"
-
- raise sa_exc.InvalidRequestError(
- "Query contains no columns with which to SELECT from."
- )
-
- if not self.from_clauses:
- self.from_clauses = list(self._fallback_from_clauses)
-
- if self.order_by is False:
- self.order_by = None
-
- if (
- self.multi_row_eager_loaders
- and self.eager_adding_joins
- and self._should_nest_selectable
- ):
- self.statement = self._compound_eager_statement()
- else:
- self.statement = self._simple_statement()
-
- if self.for_statement:
- ezero = self._mapper_zero()
- if ezero is not None:
- # TODO: this goes away once we get rid of the deep entity
- # thing
- self.statement = self.statement._annotate(
- {"deepentity": ezero}
- )
-
- @classmethod
- def _create_entities_collection(cls, query, legacy):
- """Creates a partial ORMSelectCompileState that includes
- the full collection of _MapperEntity and other _QueryEntity objects.
-
- Supports a few remaining use cases that are pre-compilation
- but still need to gather some of the column / adaption information.
-
- """
- self = cls.__new__(cls)
-
- self._entities = []
- self._primary_entity = None
- self._polymorphic_adapters = {}
-
- self._label_convention = self._column_naming_convention(
- query._label_style, legacy
- )
-
- # entities will also set up polymorphic adapters for mappers
- # that have with_polymorphic configured
- _QueryEntity.to_compile_state(
- self, query._raw_columns, self._entities, is_current_entities=True
- )
- return self
-
- @classmethod
- def determine_last_joined_entity(cls, statement):
- setup_joins = statement._setup_joins
-
- return _determine_last_joined_entity(setup_joins, None)
-
- @classmethod
- def all_selected_columns(cls, statement):
- for element in statement._raw_columns:
- if (
- element.is_selectable
- and "entity_namespace" in element._annotations
- ):
- ens = element._annotations["entity_namespace"]
- if not ens.is_mapper and not ens.is_aliased_class:
- yield from _select_iterables([element])
- else:
- yield from _select_iterables(ens._all_column_expressions)
- else:
- yield from _select_iterables([element])
-
- @classmethod
- def get_columns_clause_froms(cls, statement):
- return cls._normalize_froms(
- itertools.chain.from_iterable(
- (
- element._from_objects
- if "parententity" not in element._annotations
- else [
- element._annotations[
- "parententity"
- ].__clause_element__()
- ]
- )
- for element in statement._raw_columns
- )
- )
-
- @classmethod
- def from_statement(cls, statement, from_statement):
- from_statement = coercions.expect(
- roles.ReturnsRowsRole,
- from_statement,
- apply_propagate_attrs=statement,
- )
-
- stmt = FromStatement(statement._raw_columns, from_statement)
-
- stmt.__dict__.update(
- _with_options=statement._with_options,
- _with_context_options=statement._with_context_options,
- _execution_options=statement._execution_options,
- _propagate_attrs=statement._propagate_attrs,
- )
- return stmt
-
- def _set_select_from_alias(self):
- """used only for legacy Query cases"""
-
- query = self.select_statement # query
-
- assert self.compile_options._set_base_alias
- assert len(query._from_obj) == 1
-
- adapter = self._get_select_from_alias_from_obj(query._from_obj[0])
- if adapter:
- self.compile_options += {"_enable_single_crit": False}
- self._from_obj_alias = adapter
-
- def _get_select_from_alias_from_obj(self, from_obj):
- """used only for legacy Query cases"""
-
- info = from_obj
-
- if "parententity" in info._annotations:
- info = info._annotations["parententity"]
-
- if hasattr(info, "mapper"):
- if not info.is_aliased_class:
- raise sa_exc.ArgumentError(
- "A selectable (FromClause) instance is "
- "expected when the base alias is being set."
- )
- else:
- return info._adapter
-
- elif isinstance(info.selectable, sql.selectable.AliasedReturnsRows):
- equivs = self._all_equivs()
- assert info is info.selectable
- return ORMStatementAdapter(
- _TraceAdaptRole.LEGACY_SELECT_FROM_ALIAS,
- info.selectable,
- equivalents=equivs,
- )
- else:
- return None
-
- def _mapper_zero(self):
- """return the Mapper associated with the first QueryEntity."""
- return self._entities[0].mapper
-
- def _entity_zero(self):
- """Return the 'entity' (mapper or AliasedClass) associated
- with the first QueryEntity, or alternatively the 'select from'
- entity if specified."""
-
- for ent in self.from_clauses:
- if "parententity" in ent._annotations:
- return ent._annotations["parententity"]
- for qent in self._entities:
- if qent.entity_zero:
- return qent.entity_zero
-
- return None
-
- def _only_full_mapper_zero(self, methname):
- if self._entities != [self._primary_entity]:
- raise sa_exc.InvalidRequestError(
- "%s() can only be used against "
- "a single mapped class." % methname
- )
- return self._primary_entity.entity_zero
-
- def _only_entity_zero(self, rationale=None):
- if len(self._entities) > 1:
- raise sa_exc.InvalidRequestError(
- rationale
- or "This operation requires a Query "
- "against a single mapper."
- )
- return self._entity_zero()
-
- def _all_equivs(self):
- equivs = {}
-
- for memoized_entities in self._memoized_entities.values():
- for ent in [
- ent
- for ent in memoized_entities
- if isinstance(ent, _MapperEntity)
- ]:
- equivs.update(ent.mapper._equivalent_columns)
-
- for ent in [
- ent for ent in self._entities if isinstance(ent, _MapperEntity)
- ]:
- equivs.update(ent.mapper._equivalent_columns)
- return equivs
-
- def _compound_eager_statement(self):
- # for eager joins present and LIMIT/OFFSET/DISTINCT,
- # wrap the query inside a select,
- # then append eager joins onto that
-
- if self.order_by:
- # the default coercion for ORDER BY is now the OrderByRole,
- # which adds an additional post coercion to ByOfRole in that
- # elements are converted into label references. For the
- # eager load / subquery wrapping case, we need to un-coerce
- # the original expressions outside of the label references
- # in order to have them render.
- unwrapped_order_by = [
- (
- elem.element
- if isinstance(elem, sql.elements._label_reference)
- else elem
- )
- for elem in self.order_by
- ]
-
- order_by_col_expr = sql_util.expand_column_list_from_order_by(
- self.primary_columns, unwrapped_order_by
- )
- else:
- order_by_col_expr = []
- unwrapped_order_by = None
-
- # put FOR UPDATE on the inner query, where MySQL will honor it,
- # as well as if it has an OF so PostgreSQL can use it.
- inner = self._select_statement(
- self.primary_columns
- + [c for c in order_by_col_expr if c not in self.dedupe_columns],
- self.from_clauses,
- self._where_criteria,
- self._having_criteria,
- self.label_style,
- self.order_by,
- for_update=self._for_update_arg,
- hints=self.select_statement._hints,
- statement_hints=self.select_statement._statement_hints,
- correlate=self.correlate,
- correlate_except=self.correlate_except,
- **self._select_args,
- )
-
- inner = inner.alias()
-
- equivs = self._all_equivs()
-
- self.compound_eager_adapter = ORMStatementAdapter(
- _TraceAdaptRole.COMPOUND_EAGER_STATEMENT, inner, equivalents=equivs
- )
-
- statement = future.select(
- *([inner] + self.secondary_columns) # use_labels=self.labels
- )
- statement._label_style = self.label_style
-
- # Oracle however does not allow FOR UPDATE on the subquery,
- # and the Oracle dialect ignores it, plus for PostgreSQL, MySQL
- # we expect that all elements of the row are locked, so also put it
- # on the outside (except in the case of PG when OF is used)
- if (
- self._for_update_arg is not None
- and self._for_update_arg.of is None
- ):
- statement._for_update_arg = self._for_update_arg
-
- from_clause = inner
- for eager_join in self.eager_joins.values():
- # EagerLoader places a 'stop_on' attribute on the join,
- # giving us a marker as to where the "splice point" of
- # the join should be
- from_clause = sql_util.splice_joins(
- from_clause, eager_join, eager_join.stop_on
- )
-
- statement.select_from.non_generative(statement, from_clause)
-
- if unwrapped_order_by:
- statement.order_by.non_generative(
- statement,
- *self.compound_eager_adapter.copy_and_process(
- unwrapped_order_by
- ),
- )
-
- statement.order_by.non_generative(statement, *self.eager_order_by)
- return statement
-
- def _simple_statement(self):
- statement = self._select_statement(
- self.primary_columns + self.secondary_columns,
- tuple(self.from_clauses) + tuple(self.eager_joins.values()),
- self._where_criteria,
- self._having_criteria,
- self.label_style,
- self.order_by,
- for_update=self._for_update_arg,
- hints=self.select_statement._hints,
- statement_hints=self.select_statement._statement_hints,
- correlate=self.correlate,
- correlate_except=self.correlate_except,
- **self._select_args,
- )
-
- if self.eager_order_by:
- statement.order_by.non_generative(statement, *self.eager_order_by)
- return statement
-
- def _select_statement(
- self,
- raw_columns,
- from_obj,
- where_criteria,
- having_criteria,
- label_style,
- order_by,
- for_update,
- hints,
- statement_hints,
- correlate,
- correlate_except,
- limit_clause,
- offset_clause,
- fetch_clause,
- fetch_clause_options,
- distinct,
- distinct_on,
- prefixes,
- suffixes,
- group_by,
- independent_ctes,
- independent_ctes_opts,
- ):
- statement = Select._create_raw_select(
- _raw_columns=raw_columns,
- _from_obj=from_obj,
- _label_style=label_style,
- )
-
- if where_criteria:
- statement._where_criteria = where_criteria
- if having_criteria:
- statement._having_criteria = having_criteria
-
- if order_by:
- statement._order_by_clauses += tuple(order_by)
-
- if distinct_on:
- statement.distinct.non_generative(statement, *distinct_on)
- elif distinct:
- statement.distinct.non_generative(statement)
-
- if group_by:
- statement._group_by_clauses += tuple(group_by)
-
- statement._limit_clause = limit_clause
- statement._offset_clause = offset_clause
- statement._fetch_clause = fetch_clause
- statement._fetch_clause_options = fetch_clause_options
- statement._independent_ctes = independent_ctes
- statement._independent_ctes_opts = independent_ctes_opts
-
- if prefixes:
- statement._prefixes = prefixes
-
- if suffixes:
- statement._suffixes = suffixes
-
- statement._for_update_arg = for_update
-
- if hints:
- statement._hints = hints
- if statement_hints:
- statement._statement_hints = statement_hints
-
- if correlate:
- statement.correlate.non_generative(statement, *correlate)
-
- if correlate_except is not None:
- statement.correlate_except.non_generative(
- statement, *correlate_except
- )
-
- return statement
-
- def _adapt_polymorphic_element(self, element):
- if "parententity" in element._annotations:
- search = element._annotations["parententity"]
- alias = self._polymorphic_adapters.get(search, None)
- if alias:
- return alias.adapt_clause(element)
-
- if isinstance(element, expression.FromClause):
- search = element
- elif hasattr(element, "table"):
- search = element.table
- else:
- return None
-
- alias = self._polymorphic_adapters.get(search, None)
- if alias:
- return alias.adapt_clause(element)
-
- def _adapt_col_list(self, cols, current_adapter):
- if current_adapter:
- return [current_adapter(o, True) for o in cols]
- else:
- return cols
-
- def _get_current_adapter(self):
- adapters = []
-
- if self._from_obj_alias:
- # used for legacy going forward for query set_ops, e.g.
- # union(), union_all(), etc.
- # 1.4 and previously, also used for from_self(),
- # select_entity_from()
- #
- # for the "from obj" alias, apply extra rule to the
- # 'ORM only' check, if this query were generated from a
- # subquery of itself, i.e. _from_selectable(), apply adaption
- # to all SQL constructs.
- adapters.append(
- (
- True,
- self._from_obj_alias.replace,
- )
- )
-
- # this was *hopefully* the only adapter we were going to need
- # going forward...however, we unfortunately need _from_obj_alias
- # for query.union(), which we can't drop
- if self._polymorphic_adapters:
- adapters.append((False, self._adapt_polymorphic_element))
-
- if not adapters:
- return None
-
- def _adapt_clause(clause, as_filter):
- # do we adapt all expression elements or only those
- # tagged as 'ORM' constructs ?
-
- def replace(elem):
- is_orm_adapt = (
- "_orm_adapt" in elem._annotations
- or "parententity" in elem._annotations
- )
- for always_adapt, adapter in adapters:
- if is_orm_adapt or always_adapt:
- e = adapter(elem)
- if e is not None:
- return e
-
- return visitors.replacement_traverse(clause, {}, replace)
-
- return _adapt_clause
-
- def _join(self, args, entities_collection):
- for right, onclause, from_, flags in args:
- isouter = flags["isouter"]
- full = flags["full"]
-
- right = inspect(right)
- if onclause is not None:
- onclause = inspect(onclause)
-
- if isinstance(right, interfaces.PropComparator):
- if onclause is not None:
- raise sa_exc.InvalidRequestError(
- "No 'on clause' argument may be passed when joining "
- "to a relationship path as a target"
- )
-
- onclause = right
- right = None
- elif "parententity" in right._annotations:
- right = right._annotations["parententity"]
-
- if onclause is None:
- if not right.is_selectable and not hasattr(right, "mapper"):
- raise sa_exc.ArgumentError(
- "Expected mapped entity or "
- "selectable/table as join target"
- )
-
- of_type = None
-
- if isinstance(onclause, interfaces.PropComparator):
- # descriptor/property given (or determined); this tells us
- # explicitly what the expected "left" side of the join is.
-
- of_type = getattr(onclause, "_of_type", None)
-
- if right is None:
- if of_type:
- right = of_type
- else:
- right = onclause.property
-
- try:
- right = right.entity
- except AttributeError as err:
- raise sa_exc.ArgumentError(
- "Join target %s does not refer to a "
- "mapped entity" % right
- ) from err
-
- left = onclause._parententity
-
- prop = onclause.property
- if not isinstance(onclause, attributes.QueryableAttribute):
- onclause = prop
-
- # check for this path already present. don't render in that
- # case.
- if (left, right, prop.key) in self._already_joined_edges:
- continue
-
- if from_ is not None:
- if (
- from_ is not left
- and from_._annotations.get("parententity", None)
- is not left
- ):
- raise sa_exc.InvalidRequestError(
- "explicit from clause %s does not match left side "
- "of relationship attribute %s"
- % (
- from_._annotations.get("parententity", from_),
- onclause,
- )
- )
- elif from_ is not None:
- prop = None
- left = from_
- else:
- # no descriptor/property given; we will need to figure out
- # what the effective "left" side is
- prop = left = None
-
- # figure out the final "left" and "right" sides and create an
- # ORMJoin to add to our _from_obj tuple
- self._join_left_to_right(
- entities_collection,
- left,
- right,
- onclause,
- prop,
- isouter,
- full,
- )
-
- def _join_left_to_right(
- self,
- entities_collection,
- left,
- right,
- onclause,
- prop,
- outerjoin,
- full,
- ):
- """given raw "left", "right", "onclause" parameters consumed from
- a particular key within _join(), add a real ORMJoin object to
- our _from_obj list (or augment an existing one)
-
- """
-
- if left is None:
- # left not given (e.g. no relationship object/name specified)
- # figure out the best "left" side based on our existing froms /
- # entities
- assert prop is None
- (
- left,
- replace_from_obj_index,
- use_entity_index,
- ) = self._join_determine_implicit_left_side(
- entities_collection, left, right, onclause
- )
- else:
- # left is given via a relationship/name, or as explicit left side.
- # Determine where in our
- # "froms" list it should be spliced/appended as well as what
- # existing entity it corresponds to.
- (
- replace_from_obj_index,
- use_entity_index,
- ) = self._join_place_explicit_left_side(entities_collection, left)
-
- if left is right:
- raise sa_exc.InvalidRequestError(
- "Can't construct a join from %s to %s, they "
- "are the same entity" % (left, right)
- )
-
- # the right side as given often needs to be adapted. additionally
- # a lot of things can be wrong with it. handle all that and
- # get back the new effective "right" side
- r_info, right, onclause = self._join_check_and_adapt_right_side(
- left, right, onclause, prop
- )
-
- if not r_info.is_selectable:
- extra_criteria = self._get_extra_criteria(r_info)
- else:
- extra_criteria = ()
-
- if replace_from_obj_index is not None:
- # splice into an existing element in the
- # self._from_obj list
- left_clause = self.from_clauses[replace_from_obj_index]
-
- self.from_clauses = (
- self.from_clauses[:replace_from_obj_index]
- + [
- _ORMJoin(
- left_clause,
- right,
- onclause,
- isouter=outerjoin,
- full=full,
- _extra_criteria=extra_criteria,
- )
- ]
- + self.from_clauses[replace_from_obj_index + 1 :]
- )
- else:
- # add a new element to the self._from_obj list
- if use_entity_index is not None:
- # make use of _MapperEntity selectable, which is usually
- # entity_zero.selectable, but if with_polymorphic() were used
- # might be distinct
- assert isinstance(
- entities_collection[use_entity_index], _MapperEntity
- )
- left_clause = entities_collection[use_entity_index].selectable
- else:
- left_clause = left
-
- self.from_clauses = self.from_clauses + [
- _ORMJoin(
- left_clause,
- r_info,
- onclause,
- isouter=outerjoin,
- full=full,
- _extra_criteria=extra_criteria,
- )
- ]
-
- def _join_determine_implicit_left_side(
- self, entities_collection, left, right, onclause
- ):
- """When join conditions don't express the left side explicitly,
- determine if an existing FROM or entity in this query
- can serve as the left hand side.
-
- """
-
- # when we are here, it means join() was called without an ORM-
- # specific way of telling us what the "left" side is, e.g.:
- #
- # join(RightEntity)
- #
- # or
- #
- # join(RightEntity, RightEntity.foo == LeftEntity.bar)
- #
-
- r_info = inspect(right)
-
- replace_from_obj_index = use_entity_index = None
-
- if self.from_clauses:
- # we have a list of FROMs already. So by definition this
- # join has to connect to one of those FROMs.
-
- indexes = sql_util.find_left_clause_to_join_from(
- self.from_clauses, r_info.selectable, onclause
- )
-
- if len(indexes) == 1:
- replace_from_obj_index = indexes[0]
- left = self.from_clauses[replace_from_obj_index]
- elif len(indexes) > 1:
- raise sa_exc.InvalidRequestError(
- "Can't determine which FROM clause to join "
- "from, there are multiple FROMS which can "
- "join to this entity. Please use the .select_from() "
- "method to establish an explicit left side, as well as "
- "providing an explicit ON clause if not present already "
- "to help resolve the ambiguity."
- )
- else:
- raise sa_exc.InvalidRequestError(
- "Don't know how to join to %r. "
- "Please use the .select_from() "
- "method to establish an explicit left side, as well as "
- "providing an explicit ON clause if not present already "
- "to help resolve the ambiguity." % (right,)
- )
-
- elif entities_collection:
- # we have no explicit FROMs, so the implicit left has to
- # come from our list of entities.
-
- potential = {}
- for entity_index, ent in enumerate(entities_collection):
- entity = ent.entity_zero_or_selectable
- if entity is None:
- continue
- ent_info = inspect(entity)
- if ent_info is r_info: # left and right are the same, skip
- continue
-
- # by using a dictionary with the selectables as keys this
- # de-duplicates those selectables as occurs when the query is
- # against a series of columns from the same selectable
- if isinstance(ent, _MapperEntity):
- potential[ent.selectable] = (entity_index, entity)
- else:
- potential[ent_info.selectable] = (None, entity)
-
- all_clauses = list(potential.keys())
- indexes = sql_util.find_left_clause_to_join_from(
- all_clauses, r_info.selectable, onclause
- )
-
- if len(indexes) == 1:
- use_entity_index, left = potential[all_clauses[indexes[0]]]
- elif len(indexes) > 1:
- raise sa_exc.InvalidRequestError(
- "Can't determine which FROM clause to join "
- "from, there are multiple FROMS which can "
- "join to this entity. Please use the .select_from() "
- "method to establish an explicit left side, as well as "
- "providing an explicit ON clause if not present already "
- "to help resolve the ambiguity."
- )
- else:
- raise sa_exc.InvalidRequestError(
- "Don't know how to join to %r. "
- "Please use the .select_from() "
- "method to establish an explicit left side, as well as "
- "providing an explicit ON clause if not present already "
- "to help resolve the ambiguity." % (right,)
- )
- else:
- raise sa_exc.InvalidRequestError(
- "No entities to join from; please use "
- "select_from() to establish the left "
- "entity/selectable of this join"
- )
-
- return left, replace_from_obj_index, use_entity_index
-
- def _join_place_explicit_left_side(self, entities_collection, left):
- """When join conditions express a left side explicitly, determine
- where in our existing list of FROM clauses we should join towards,
- or if we need to make a new join, and if so is it from one of our
- existing entities.
-
- """
-
- # when we are here, it means join() was called with an indicator
- # as to an exact left side, which means a path to a
- # Relationship was given, e.g.:
- #
- # join(RightEntity, LeftEntity.right)
- #
- # or
- #
- # join(LeftEntity.right)
- #
- # as well as string forms:
- #
- # join(RightEntity, "right")
- #
- # etc.
- #
-
- replace_from_obj_index = use_entity_index = None
-
- l_info = inspect(left)
- if self.from_clauses:
- indexes = sql_util.find_left_clause_that_matches_given(
- self.from_clauses, l_info.selectable
- )
-
- if len(indexes) > 1:
- raise sa_exc.InvalidRequestError(
- "Can't identify which entity in which to assign the "
- "left side of this join. Please use a more specific "
- "ON clause."
- )
-
- # have an index, means the left side is already present in
- # an existing FROM in the self._from_obj tuple
- if indexes:
- replace_from_obj_index = indexes[0]
-
- # no index, means we need to add a new element to the
- # self._from_obj tuple
-
- # no from element present, so we will have to add to the
- # self._from_obj tuple. Determine if this left side matches up
- # with existing mapper entities, in which case we want to apply the
- # aliasing / adaptation rules present on that entity if any
- if (
- replace_from_obj_index is None
- and entities_collection
- and hasattr(l_info, "mapper")
- ):
- for idx, ent in enumerate(entities_collection):
- # TODO: should we be checking for multiple mapper entities
- # matching?
- if isinstance(ent, _MapperEntity) and ent.corresponds_to(left):
- use_entity_index = idx
- break
-
- return replace_from_obj_index, use_entity_index
-
- def _join_check_and_adapt_right_side(self, left, right, onclause, prop):
- """transform the "right" side of the join as well as the onclause
- according to polymorphic mapping translations, aliasing on the query
- or on the join, special cases where the right and left side have
- overlapping tables.
-
- """
-
- l_info = inspect(left)
- r_info = inspect(right)
-
- overlap = False
-
- right_mapper = getattr(r_info, "mapper", None)
- # if the target is a joined inheritance mapping,
- # be more liberal about auto-aliasing.
- if right_mapper and (
- right_mapper.with_polymorphic
- or isinstance(right_mapper.persist_selectable, expression.Join)
- ):
- for from_obj in self.from_clauses or [l_info.selectable]:
- if sql_util.selectables_overlap(
- l_info.selectable, from_obj
- ) and sql_util.selectables_overlap(
- from_obj, r_info.selectable
- ):
- overlap = True
- break
-
- if overlap and l_info.selectable is r_info.selectable:
- raise sa_exc.InvalidRequestError(
- "Can't join table/selectable '%s' to itself"
- % l_info.selectable
- )
-
- right_mapper, right_selectable, right_is_aliased = (
- getattr(r_info, "mapper", None),
- r_info.selectable,
- getattr(r_info, "is_aliased_class", False),
- )
-
- if (
- right_mapper
- and prop
- and not right_mapper.common_parent(prop.mapper)
- ):
- raise sa_exc.InvalidRequestError(
- "Join target %s does not correspond to "
- "the right side of join condition %s" % (right, onclause)
- )
-
- # _join_entities is used as a hint for single-table inheritance
- # purposes at the moment
- if hasattr(r_info, "mapper"):
- self._join_entities += (r_info,)
-
- need_adapter = False
-
- # test for joining to an unmapped selectable as the target
- if r_info.is_clause_element:
- if prop:
- right_mapper = prop.mapper
-
- if right_selectable._is_lateral:
- # orm_only is disabled to suit the case where we have to
- # adapt an explicit correlate(Entity) - the select() loses
- # the ORM-ness in this case right now, ideally it would not
- current_adapter = self._get_current_adapter()
- if current_adapter is not None:
- # TODO: we had orm_only=False here before, removing
- # it didn't break things. if we identify the rationale,
- # may need to apply "_orm_only" annotation here.
- right = current_adapter(right, True)
-
- elif prop:
- # joining to selectable with a mapper property given
- # as the ON clause
-
- if not right_selectable.is_derived_from(
- right_mapper.persist_selectable
- ):
- raise sa_exc.InvalidRequestError(
- "Selectable '%s' is not derived from '%s'"
- % (
- right_selectable.description,
- right_mapper.persist_selectable.description,
- )
- )
-
- # if the destination selectable is a plain select(),
- # turn it into an alias().
- if isinstance(right_selectable, expression.SelectBase):
- right_selectable = coercions.expect(
- roles.FromClauseRole, right_selectable
- )
- need_adapter = True
-
- # make the right hand side target into an ORM entity
- right = AliasedClass(right_mapper, right_selectable)
-
- util.warn_deprecated(
- "An alias is being generated automatically against "
- "joined entity %s for raw clauseelement, which is "
- "deprecated and will be removed in a later release. "
- "Use the aliased() "
- "construct explicitly, see the linked example."
- % right_mapper,
- "1.4",
- code="xaj1",
- )
-
- # test for overlap:
- # orm/inheritance/relationships.py
- # SelfReferentialM2MTest
- aliased_entity = right_mapper and not right_is_aliased and overlap
-
- if not need_adapter and aliased_entity:
- # there are a few places in the ORM that automatic aliasing
- # is still desirable, and can't be automatic with a Core
- # only approach. For illustrations of "overlaps" see
- # test/orm/inheritance/test_relationships.py. There are also
- # general overlap cases with many-to-many tables where automatic
- # aliasing is desirable.
- right = AliasedClass(right, flat=True)
- need_adapter = True
-
- util.warn(
- "An alias is being generated automatically against "
- "joined entity %s due to overlapping tables. This is a "
- "legacy pattern which may be "
- "deprecated in a later release. Use the "
- "aliased(<entity>, flat=True) "
- "construct explicitly, see the linked example." % right_mapper,
- code="xaj2",
- )
-
- if need_adapter:
- # if need_adapter is True, we are in a deprecated case and
- # a warning has been emitted.
- assert right_mapper
-
- adapter = ORMAdapter(
- _TraceAdaptRole.DEPRECATED_JOIN_ADAPT_RIGHT_SIDE,
- inspect(right),
- equivalents=right_mapper._equivalent_columns,
- )
-
- # if an alias() on the right side was generated,
- # which is intended to wrap a the right side in a subquery,
- # ensure that columns retrieved from this target in the result
- # set are also adapted.
- self._mapper_loads_polymorphically_with(right_mapper, adapter)
- elif (
- not r_info.is_clause_element
- and not right_is_aliased
- and right_mapper._has_aliased_polymorphic_fromclause
- ):
- # for the case where the target mapper has a with_polymorphic
- # set up, ensure an adapter is set up for criteria that works
- # against this mapper. Previously, this logic used to
- # use the "create_aliases or aliased_entity" case to generate
- # an aliased() object, but this creates an alias that isn't
- # strictly necessary.
- # see test/orm/test_core_compilation.py
- # ::RelNaturalAliasedJoinsTest::test_straight
- # and similar
- self._mapper_loads_polymorphically_with(
- right_mapper,
- ORMAdapter(
- _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER_RIGHT_JOIN,
- right_mapper,
- selectable=right_mapper.selectable,
- equivalents=right_mapper._equivalent_columns,
- ),
- )
- # if the onclause is a ClauseElement, adapt it with any
- # adapters that are in place right now
- if isinstance(onclause, expression.ClauseElement):
- current_adapter = self._get_current_adapter()
- if current_adapter:
- onclause = current_adapter(onclause, True)
-
- # if joining on a MapperProperty path,
- # track the path to prevent redundant joins
- if prop:
- self._already_joined_edges += ((left, right, prop.key),)
-
- return inspect(right), right, onclause
-
- @property
- def _select_args(self):
- return {
- "limit_clause": self.select_statement._limit_clause,
- "offset_clause": self.select_statement._offset_clause,
- "distinct": self.distinct,
- "distinct_on": self.distinct_on,
- "prefixes": self.select_statement._prefixes,
- "suffixes": self.select_statement._suffixes,
- "group_by": self.group_by or None,
- "fetch_clause": self.select_statement._fetch_clause,
- "fetch_clause_options": (
- self.select_statement._fetch_clause_options
- ),
- "independent_ctes": self.select_statement._independent_ctes,
- "independent_ctes_opts": (
- self.select_statement._independent_ctes_opts
- ),
- }
-
- @property
- def _should_nest_selectable(self):
- kwargs = self._select_args
- return (
- kwargs.get("limit_clause") is not None
- or kwargs.get("offset_clause") is not None
- or kwargs.get("distinct", False)
- or kwargs.get("distinct_on", ())
- or kwargs.get("group_by", False)
- )
-
- def _get_extra_criteria(self, ext_info):
- if (
- "additional_entity_criteria",
- ext_info.mapper,
- ) in self.global_attributes:
- return tuple(
- ae._resolve_where_criteria(ext_info)
- for ae in self.global_attributes[
- ("additional_entity_criteria", ext_info.mapper)
- ]
- if (ae.include_aliases or ae.entity is ext_info)
- and ae._should_include(self)
- )
- else:
- return ()
-
- def _adjust_for_extra_criteria(self):
- """Apply extra criteria filtering.
-
- For all distinct single-table-inheritance mappers represented in
- the columns clause of this query, as well as the "select from entity",
- add criterion to the WHERE
- clause of the given QueryContext such that only the appropriate
- subtypes are selected from the total results.
-
- Additionally, add WHERE criteria originating from LoaderCriteriaOptions
- associated with the global context.
-
- """
-
- for fromclause in self.from_clauses:
- ext_info = fromclause._annotations.get("parententity", None)
-
- if (
- ext_info
- and (
- ext_info.mapper._single_table_criterion is not None
- or ("additional_entity_criteria", ext_info.mapper)
- in self.global_attributes
- )
- and ext_info not in self.extra_criteria_entities
- ):
- self.extra_criteria_entities[ext_info] = (
- ext_info,
- ext_info._adapter if ext_info.is_aliased_class else None,
- )
-
- search = set(self.extra_criteria_entities.values())
-
- for ext_info, adapter in search:
- if ext_info in self._join_entities:
- continue
-
- single_crit = ext_info.mapper._single_table_criterion
-
- if self.compile_options._for_refresh_state:
- additional_entity_criteria = []
- else:
- additional_entity_criteria = self._get_extra_criteria(ext_info)
-
- if single_crit is not None:
- additional_entity_criteria += (single_crit,)
-
- current_adapter = self._get_current_adapter()
- for crit in additional_entity_criteria:
- if adapter:
- crit = adapter.traverse(crit)
-
- if current_adapter:
- crit = sql_util._deep_annotate(crit, {"_orm_adapt": True})
- crit = current_adapter(crit, False)
- self._where_criteria += (crit,)
-
-
-def _column_descriptions(
- query_or_select_stmt: Union[Query, Select, FromStatement],
- compile_state: Optional[ORMSelectCompileState] = None,
- legacy: bool = False,
-) -> List[ORMColumnDescription]:
- if compile_state is None:
- compile_state = ORMSelectCompileState._create_entities_collection(
- query_or_select_stmt, legacy=legacy
- )
- ctx = compile_state
- d = [
- {
- "name": ent._label_name,
- "type": ent.type,
- "aliased": getattr(insp_ent, "is_aliased_class", False),
- "expr": ent.expr,
- "entity": (
- getattr(insp_ent, "entity", None)
- if ent.entity_zero is not None
- and not insp_ent.is_clause_element
- else None
- ),
- }
- for ent, insp_ent in [
- (_ent, _ent.entity_zero) for _ent in ctx._entities
- ]
- ]
- return d
-
-
-def _legacy_filter_by_entity_zero(
- query_or_augmented_select: Union[Query[Any], Select[Any]]
-) -> Optional[_InternalEntityType[Any]]:
- self = query_or_augmented_select
- if self._setup_joins:
- _last_joined_entity = self._last_joined_entity
- if _last_joined_entity is not None:
- return _last_joined_entity
-
- if self._from_obj and "parententity" in self._from_obj[0]._annotations:
- return self._from_obj[0]._annotations["parententity"]
-
- return _entity_from_pre_ent_zero(self)
-
-
-def _entity_from_pre_ent_zero(
- query_or_augmented_select: Union[Query[Any], Select[Any]]
-) -> Optional[_InternalEntityType[Any]]:
- self = query_or_augmented_select
- if not self._raw_columns:
- return None
-
- ent = self._raw_columns[0]
-
- if "parententity" in ent._annotations:
- return ent._annotations["parententity"]
- elif isinstance(ent, ORMColumnsClauseRole):
- return ent.entity
- elif "bundle" in ent._annotations:
- return ent._annotations["bundle"]
- else:
- return ent
-
-
-def _determine_last_joined_entity(
- setup_joins: Tuple[_SetupJoinsElement, ...],
- entity_zero: Optional[_InternalEntityType[Any]] = None,
-) -> Optional[Union[_InternalEntityType[Any], _JoinTargetElement]]:
- if not setup_joins:
- return None
-
- (target, onclause, from_, flags) = setup_joins[-1]
-
- if isinstance(
- target,
- attributes.QueryableAttribute,
- ):
- return target.entity
- else:
- return target
-
-
-class _QueryEntity:
- """represent an entity column returned within a Query result."""
-
- __slots__ = ()
-
- supports_single_entity: bool
-
- _non_hashable_value = False
- _null_column_type = False
- use_id_for_hash = False
-
- _label_name: Optional[str]
- type: Union[Type[Any], TypeEngine[Any]]
- expr: Union[_InternalEntityType, ColumnElement[Any]]
- entity_zero: Optional[_InternalEntityType]
-
- def setup_compile_state(self, compile_state: ORMCompileState) -> None:
- raise NotImplementedError()
-
- def setup_dml_returning_compile_state(
- self,
- compile_state: ORMCompileState,
- adapter: DMLReturningColFilter,
- ) -> None:
- raise NotImplementedError()
-
- def row_processor(self, context, result):
- raise NotImplementedError()
-
- @classmethod
- def to_compile_state(
- cls, compile_state, entities, entities_collection, is_current_entities
- ):
- for idx, entity in enumerate(entities):
- if entity._is_lambda_element:
- if entity._is_sequence:
- cls.to_compile_state(
- compile_state,
- entity._resolved,
- entities_collection,
- is_current_entities,
- )
- continue
- else:
- entity = entity._resolved
-
- if entity.is_clause_element:
- if entity.is_selectable:
- if "parententity" in entity._annotations:
- _MapperEntity(
- compile_state,
- entity,
- entities_collection,
- is_current_entities,
- )
- else:
- _ColumnEntity._for_columns(
- compile_state,
- entity._select_iterable,
- entities_collection,
- idx,
- is_current_entities,
- )
- else:
- if entity._annotations.get("bundle", False):
- _BundleEntity(
- compile_state,
- entity,
- entities_collection,
- is_current_entities,
- )
- elif entity._is_clause_list:
- # this is legacy only - test_composites.py
- # test_query_cols_legacy
- _ColumnEntity._for_columns(
- compile_state,
- entity._select_iterable,
- entities_collection,
- idx,
- is_current_entities,
- )
- else:
- _ColumnEntity._for_columns(
- compile_state,
- [entity],
- entities_collection,
- idx,
- is_current_entities,
- )
- elif entity.is_bundle:
- _BundleEntity(compile_state, entity, entities_collection)
-
- return entities_collection
-
-
-class _MapperEntity(_QueryEntity):
- """mapper/class/AliasedClass entity"""
-
- __slots__ = (
- "expr",
- "mapper",
- "entity_zero",
- "is_aliased_class",
- "path",
- "_extra_entities",
- "_label_name",
- "_with_polymorphic_mappers",
- "selectable",
- "_polymorphic_discriminator",
- )
-
- expr: _InternalEntityType
- mapper: Mapper[Any]
- entity_zero: _InternalEntityType
- is_aliased_class: bool
- path: PathRegistry
- _label_name: str
-
- def __init__(
- self, compile_state, entity, entities_collection, is_current_entities
- ):
- entities_collection.append(self)
- if is_current_entities:
- if compile_state._primary_entity is None:
- compile_state._primary_entity = self
- compile_state._has_mapper_entities = True
- compile_state._has_orm_entities = True
-
- entity = entity._annotations["parententity"]
- entity._post_inspect
- ext_info = self.entity_zero = entity
- entity = ext_info.entity
-
- self.expr = entity
- self.mapper = mapper = ext_info.mapper
-
- self._extra_entities = (self.expr,)
-
- if ext_info.is_aliased_class:
- self._label_name = ext_info.name
- else:
- self._label_name = mapper.class_.__name__
-
- self.is_aliased_class = ext_info.is_aliased_class
- self.path = ext_info._path_registry
-
- self.selectable = ext_info.selectable
- self._with_polymorphic_mappers = ext_info.with_polymorphic_mappers
- self._polymorphic_discriminator = ext_info.polymorphic_on
-
- if mapper._should_select_with_poly_adapter:
- compile_state._create_with_polymorphic_adapter(
- ext_info, self.selectable
- )
-
- supports_single_entity = True
-
- _non_hashable_value = True
- use_id_for_hash = True
-
- @property
- def type(self):
- return self.mapper.class_
-
- @property
- def entity_zero_or_selectable(self):
- return self.entity_zero
-
- def corresponds_to(self, entity):
- return _entity_corresponds_to(self.entity_zero, entity)
-
- def _get_entity_clauses(self, compile_state):
- adapter = None
-
- if not self.is_aliased_class:
- if compile_state._polymorphic_adapters:
- adapter = compile_state._polymorphic_adapters.get(
- self.mapper, None
- )
- else:
- adapter = self.entity_zero._adapter
-
- if adapter:
- if compile_state._from_obj_alias:
- ret = adapter.wrap(compile_state._from_obj_alias)
- else:
- ret = adapter
- else:
- ret = compile_state._from_obj_alias
-
- return ret
-
- def row_processor(self, context, result):
- compile_state = context.compile_state
- adapter = self._get_entity_clauses(compile_state)
-
- if compile_state.compound_eager_adapter and adapter:
- adapter = adapter.wrap(compile_state.compound_eager_adapter)
- elif not adapter:
- adapter = compile_state.compound_eager_adapter
-
- if compile_state._primary_entity is self:
- only_load_props = compile_state.compile_options._only_load_props
- refresh_state = context.refresh_state
- else:
- only_load_props = refresh_state = None
-
- _instance = loading._instance_processor(
- self,
- self.mapper,
- context,
- result,
- self.path,
- adapter,
- only_load_props=only_load_props,
- refresh_state=refresh_state,
- polymorphic_discriminator=self._polymorphic_discriminator,
- )
-
- return _instance, self._label_name, self._extra_entities
-
- def setup_dml_returning_compile_state(
- self,
- compile_state: ORMCompileState,
- adapter: DMLReturningColFilter,
- ) -> None:
- loading._setup_entity_query(
- compile_state,
- self.mapper,
- self,
- self.path,
- adapter,
- compile_state.primary_columns,
- with_polymorphic=self._with_polymorphic_mappers,
- only_load_props=compile_state.compile_options._only_load_props,
- polymorphic_discriminator=self._polymorphic_discriminator,
- )
-
- def setup_compile_state(self, compile_state):
- adapter = self._get_entity_clauses(compile_state)
-
- single_table_crit = self.mapper._single_table_criterion
- if (
- single_table_crit is not None
- or ("additional_entity_criteria", self.mapper)
- in compile_state.global_attributes
- ):
- ext_info = self.entity_zero
- compile_state.extra_criteria_entities[ext_info] = (
- ext_info,
- ext_info._adapter if ext_info.is_aliased_class else None,
- )
-
- loading._setup_entity_query(
- compile_state,
- self.mapper,
- self,
- self.path,
- adapter,
- compile_state.primary_columns,
- with_polymorphic=self._with_polymorphic_mappers,
- only_load_props=compile_state.compile_options._only_load_props,
- polymorphic_discriminator=self._polymorphic_discriminator,
- )
- compile_state._fallback_from_clauses.append(self.selectable)
-
-
-class _BundleEntity(_QueryEntity):
- _extra_entities = ()
-
- __slots__ = (
- "bundle",
- "expr",
- "type",
- "_label_name",
- "_entities",
- "supports_single_entity",
- )
-
- _entities: List[_QueryEntity]
- bundle: Bundle
- type: Type[Any]
- _label_name: str
- supports_single_entity: bool
- expr: Bundle
-
- def __init__(
- self,
- compile_state,
- expr,
- entities_collection,
- is_current_entities,
- setup_entities=True,
- parent_bundle=None,
- ):
- compile_state._has_orm_entities = True
-
- expr = expr._annotations["bundle"]
- if parent_bundle:
- parent_bundle._entities.append(self)
- else:
- entities_collection.append(self)
-
- if isinstance(
- expr, (attributes.QueryableAttribute, interfaces.PropComparator)
- ):
- bundle = expr.__clause_element__()
- else:
- bundle = expr
-
- self.bundle = self.expr = bundle
- self.type = type(bundle)
- self._label_name = bundle.name
- self._entities = []
-
- if setup_entities:
- for expr in bundle.exprs:
- if "bundle" in expr._annotations:
- _BundleEntity(
- compile_state,
- expr,
- entities_collection,
- is_current_entities,
- parent_bundle=self,
- )
- elif isinstance(expr, Bundle):
- _BundleEntity(
- compile_state,
- expr,
- entities_collection,
- is_current_entities,
- parent_bundle=self,
- )
- else:
- _ORMColumnEntity._for_columns(
- compile_state,
- [expr],
- entities_collection,
- None,
- is_current_entities,
- parent_bundle=self,
- )
-
- self.supports_single_entity = self.bundle.single_entity
-
- @property
- def mapper(self):
- ezero = self.entity_zero
- if ezero is not None:
- return ezero.mapper
- else:
- return None
-
- @property
- def entity_zero(self):
- for ent in self._entities:
- ezero = ent.entity_zero
- if ezero is not None:
- return ezero
- else:
- return None
-
- def corresponds_to(self, entity):
- # TODO: we might be able to implement this but for now
- # we are working around it
- return False
-
- @property
- def entity_zero_or_selectable(self):
- for ent in self._entities:
- ezero = ent.entity_zero_or_selectable
- if ezero is not None:
- return ezero
- else:
- return None
-
- def setup_compile_state(self, compile_state):
- for ent in self._entities:
- ent.setup_compile_state(compile_state)
-
- def setup_dml_returning_compile_state(
- self,
- compile_state: ORMCompileState,
- adapter: DMLReturningColFilter,
- ) -> None:
- return self.setup_compile_state(compile_state)
-
- def row_processor(self, context, result):
- procs, labels, extra = zip(
- *[ent.row_processor(context, result) for ent in self._entities]
- )
-
- proc = self.bundle.create_row_processor(context.query, procs, labels)
-
- return proc, self._label_name, self._extra_entities
-
-
-class _ColumnEntity(_QueryEntity):
- __slots__ = (
- "_fetch_column",
- "_row_processor",
- "raw_column_index",
- "translate_raw_column",
- )
-
- @classmethod
- def _for_columns(
- cls,
- compile_state,
- columns,
- entities_collection,
- raw_column_index,
- is_current_entities,
- parent_bundle=None,
- ):
- for column in columns:
- annotations = column._annotations
- if "parententity" in annotations:
- _entity = annotations["parententity"]
- else:
- _entity = sql_util.extract_first_column_annotation(
- column, "parententity"
- )
-
- if _entity:
- if "identity_token" in column._annotations:
- _IdentityTokenEntity(
- compile_state,
- column,
- entities_collection,
- _entity,
- raw_column_index,
- is_current_entities,
- parent_bundle=parent_bundle,
- )
- else:
- _ORMColumnEntity(
- compile_state,
- column,
- entities_collection,
- _entity,
- raw_column_index,
- is_current_entities,
- parent_bundle=parent_bundle,
- )
- else:
- _RawColumnEntity(
- compile_state,
- column,
- entities_collection,
- raw_column_index,
- is_current_entities,
- parent_bundle=parent_bundle,
- )
-
- @property
- def type(self):
- return self.column.type
-
- @property
- def _non_hashable_value(self):
- return not self.column.type.hashable
-
- @property
- def _null_column_type(self):
- return self.column.type._isnull
-
- def row_processor(self, context, result):
- compile_state = context.compile_state
-
- # the resulting callable is entirely cacheable so just return
- # it if we already made one
- if self._row_processor is not None:
- getter, label_name, extra_entities = self._row_processor
- if self.translate_raw_column:
- extra_entities += (
- context.query._raw_columns[self.raw_column_index],
- )
-
- return getter, label_name, extra_entities
-
- # retrieve the column that would have been set up in
- # setup_compile_state, to avoid doing redundant work
- if self._fetch_column is not None:
- column = self._fetch_column
- else:
- # fetch_column will be None when we are doing a from_statement
- # and setup_compile_state may not have been called.
- column = self.column
-
- # previously, the RawColumnEntity didn't look for from_obj_alias
- # however I can't think of a case where we would be here and
- # we'd want to ignore it if this is the from_statement use case.
- # it's not really a use case to have raw columns + from_statement
- if compile_state._from_obj_alias:
- column = compile_state._from_obj_alias.columns[column]
-
- if column._annotations:
- # annotated columns perform more slowly in compiler and
- # result due to the __eq__() method, so use deannotated
- column = column._deannotate()
-
- if compile_state.compound_eager_adapter:
- column = compile_state.compound_eager_adapter.columns[column]
-
- getter = result._getter(column)
- ret = getter, self._label_name, self._extra_entities
- self._row_processor = ret
-
- if self.translate_raw_column:
- extra_entities = self._extra_entities + (
- context.query._raw_columns[self.raw_column_index],
- )
- return getter, self._label_name, extra_entities
- else:
- return ret
-
-
-class _RawColumnEntity(_ColumnEntity):
- entity_zero = None
- mapper = None
- supports_single_entity = False
-
- __slots__ = (
- "expr",
- "column",
- "_label_name",
- "entity_zero_or_selectable",
- "_extra_entities",
- )
-
- def __init__(
- self,
- compile_state,
- column,
- entities_collection,
- raw_column_index,
- is_current_entities,
- parent_bundle=None,
- ):
- self.expr = column
- self.raw_column_index = raw_column_index
- self.translate_raw_column = raw_column_index is not None
-
- if column._is_star:
- compile_state.compile_options += {"_is_star": True}
-
- if not is_current_entities or column._is_text_clause:
- self._label_name = None
- else:
- self._label_name = compile_state._label_convention(column)
-
- if parent_bundle:
- parent_bundle._entities.append(self)
- else:
- entities_collection.append(self)
-
- self.column = column
- self.entity_zero_or_selectable = (
- self.column._from_objects[0] if self.column._from_objects else None
- )
- self._extra_entities = (self.expr, self.column)
- self._fetch_column = self._row_processor = None
-
- def corresponds_to(self, entity):
- return False
-
- def setup_dml_returning_compile_state(
- self,
- compile_state: ORMCompileState,
- adapter: DMLReturningColFilter,
- ) -> None:
- return self.setup_compile_state(compile_state)
-
- def setup_compile_state(self, compile_state):
- current_adapter = compile_state._get_current_adapter()
- if current_adapter:
- column = current_adapter(self.column, False)
- if column is None:
- return
- else:
- column = self.column
-
- if column._annotations:
- # annotated columns perform more slowly in compiler and
- # result due to the __eq__() method, so use deannotated
- column = column._deannotate()
-
- compile_state.dedupe_columns.add(column)
- compile_state.primary_columns.append(column)
- self._fetch_column = column
-
-
-class _ORMColumnEntity(_ColumnEntity):
- """Column/expression based entity."""
-
- supports_single_entity = False
-
- __slots__ = (
- "expr",
- "mapper",
- "column",
- "_label_name",
- "entity_zero_or_selectable",
- "entity_zero",
- "_extra_entities",
- )
-
- def __init__(
- self,
- compile_state,
- column,
- entities_collection,
- parententity,
- raw_column_index,
- is_current_entities,
- parent_bundle=None,
- ):
- annotations = column._annotations
-
- _entity = parententity
-
- # an AliasedClass won't have proxy_key in the annotations for
- # a column if it was acquired using the class' adapter directly,
- # such as using AliasedInsp._adapt_element(). this occurs
- # within internal loaders.
-
- orm_key = annotations.get("proxy_key", None)
- proxy_owner = annotations.get("proxy_owner", _entity)
- if orm_key:
- self.expr = getattr(proxy_owner.entity, orm_key)
- self.translate_raw_column = False
- else:
- # if orm_key is not present, that means this is an ad-hoc
- # SQL ColumnElement, like a CASE() or other expression.
- # include this column position from the invoked statement
- # in the ORM-level ResultSetMetaData on each execute, so that
- # it can be targeted by identity after caching
- self.expr = column
- self.translate_raw_column = raw_column_index is not None
-
- self.raw_column_index = raw_column_index
-
- if is_current_entities:
- self._label_name = compile_state._label_convention(
- column, col_name=orm_key
- )
- else:
- self._label_name = None
-
- _entity._post_inspect
- self.entity_zero = self.entity_zero_or_selectable = ezero = _entity
- self.mapper = mapper = _entity.mapper
-
- if parent_bundle:
- parent_bundle._entities.append(self)
- else:
- entities_collection.append(self)
-
- compile_state._has_orm_entities = True
-
- self.column = column
-
- self._fetch_column = self._row_processor = None
-
- self._extra_entities = (self.expr, self.column)
-
- if mapper._should_select_with_poly_adapter:
- compile_state._create_with_polymorphic_adapter(
- ezero, ezero.selectable
- )
-
- def corresponds_to(self, entity):
- if _is_aliased_class(entity):
- # TODO: polymorphic subclasses ?
- return entity is self.entity_zero
- else:
- return not _is_aliased_class(
- self.entity_zero
- ) and entity.common_parent(self.entity_zero)
-
- def setup_dml_returning_compile_state(
- self,
- compile_state: ORMCompileState,
- adapter: DMLReturningColFilter,
- ) -> None:
- self._fetch_column = self.column
- column = adapter(self.column, False)
- if column is not None:
- compile_state.dedupe_columns.add(column)
- compile_state.primary_columns.append(column)
-
- def setup_compile_state(self, compile_state):
- current_adapter = compile_state._get_current_adapter()
- if current_adapter:
- column = current_adapter(self.column, False)
- if column is None:
- assert compile_state.is_dml_returning
- self._fetch_column = self.column
- return
- else:
- column = self.column
-
- ezero = self.entity_zero
-
- single_table_crit = self.mapper._single_table_criterion
- if (
- single_table_crit is not None
- or ("additional_entity_criteria", self.mapper)
- in compile_state.global_attributes
- ):
- compile_state.extra_criteria_entities[ezero] = (
- ezero,
- ezero._adapter if ezero.is_aliased_class else None,
- )
-
- if column._annotations and not column._expression_label:
- # annotated columns perform more slowly in compiler and
- # result due to the __eq__() method, so use deannotated
- column = column._deannotate()
-
- # use entity_zero as the from if we have it. this is necessary
- # for polymorphic scenarios where our FROM is based on ORM entity,
- # not the FROM of the column. but also, don't use it if our column
- # doesn't actually have any FROMs that line up, such as when its
- # a scalar subquery.
- if set(self.column._from_objects).intersection(
- ezero.selectable._from_objects
- ):
- compile_state._fallback_from_clauses.append(ezero.selectable)
-
- compile_state.dedupe_columns.add(column)
- compile_state.primary_columns.append(column)
- self._fetch_column = column
-
-
-class _IdentityTokenEntity(_ORMColumnEntity):
- translate_raw_column = False
-
- def setup_compile_state(self, compile_state):
- pass
-
- def row_processor(self, context, result):
- def getter(row):
- return context.load_options._identity_token
-
- return getter, self._label_name, self._extra_entities
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/decl_api.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/decl_api.py
deleted file mode 100644
index 09128ea..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/decl_api.py
+++ /dev/null
@@ -1,1875 +0,0 @@
-# orm/decl_api.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
-
-"""Public API functions and helpers for declarative."""
-
-from __future__ import annotations
-
-import itertools
-import re
-import typing
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import ClassVar
-from typing import Dict
-from typing import FrozenSet
-from typing import Generic
-from typing import Iterable
-from typing import Iterator
-from typing import Mapping
-from typing import Optional
-from typing import overload
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-import weakref
-
-from . import attributes
-from . import clsregistry
-from . import instrumentation
-from . import interfaces
-from . import mapperlib
-from ._orm_constructors import composite
-from ._orm_constructors import deferred
-from ._orm_constructors import mapped_column
-from ._orm_constructors import relationship
-from ._orm_constructors import synonym
-from .attributes import InstrumentedAttribute
-from .base import _inspect_mapped_class
-from .base import _is_mapped_class
-from .base import Mapped
-from .base import ORMDescriptor
-from .decl_base import _add_attribute
-from .decl_base import _as_declarative
-from .decl_base import _ClassScanMapperConfig
-from .decl_base import _declarative_constructor
-from .decl_base import _DeferredMapperConfig
-from .decl_base import _del_attribute
-from .decl_base import _mapper
-from .descriptor_props import Composite
-from .descriptor_props import Synonym
-from .descriptor_props import Synonym as _orm_synonym
-from .mapper import Mapper
-from .properties import MappedColumn
-from .relationships import RelationshipProperty
-from .state import InstanceState
-from .. import exc
-from .. import inspection
-from .. import util
-from ..sql import sqltypes
-from ..sql.base import _NoArg
-from ..sql.elements import SQLCoreOperations
-from ..sql.schema import MetaData
-from ..sql.selectable import FromClause
-from ..util import hybridmethod
-from ..util import hybridproperty
-from ..util import typing as compat_typing
-from ..util.typing import CallableReference
-from ..util.typing import flatten_newtype
-from ..util.typing import is_generic
-from ..util.typing import is_literal
-from ..util.typing import is_newtype
-from ..util.typing import is_pep695
-from ..util.typing import Literal
-from ..util.typing import Self
-
-if TYPE_CHECKING:
- from ._typing import _O
- from ._typing import _RegistryType
- from .decl_base import _DataclassArguments
- from .instrumentation import ClassManager
- from .interfaces import MapperProperty
- from .state import InstanceState # noqa
- from ..sql._typing import _TypeEngineArgument
- from ..sql.type_api import _MatchedOnType
-
-_T = TypeVar("_T", bound=Any)
-
-_TT = TypeVar("_TT", bound=Any)
-
-# it's not clear how to have Annotated, Union objects etc. as keys here
-# from a typing perspective so just leave it open ended for now
-_TypeAnnotationMapType = Mapping[Any, "_TypeEngineArgument[Any]"]
-_MutableTypeAnnotationMapType = Dict[Any, "_TypeEngineArgument[Any]"]
-
-_DeclaredAttrDecorated = Callable[
- ..., Union[Mapped[_T], ORMDescriptor[_T], SQLCoreOperations[_T]]
-]
-
-
-def has_inherited_table(cls: Type[_O]) -> bool:
- """Given a class, return True if any of the classes it inherits from has a
- mapped table, otherwise return False.
-
- This is used in declarative mixins to build attributes that behave
- differently for the base class vs. a subclass in an inheritance
- hierarchy.
-
- .. seealso::
-
- :ref:`decl_mixin_inheritance`
-
- """
- for class_ in cls.__mro__[1:]:
- if getattr(class_, "__table__", None) is not None:
- return True
- return False
-
-
-class _DynamicAttributesType(type):
- def __setattr__(cls, key: str, value: Any) -> None:
- if "__mapper__" in cls.__dict__:
- _add_attribute(cls, key, value)
- else:
- type.__setattr__(cls, key, value)
-
- def __delattr__(cls, key: str) -> None:
- if "__mapper__" in cls.__dict__:
- _del_attribute(cls, key)
- else:
- type.__delattr__(cls, key)
-
-
-class DeclarativeAttributeIntercept(
- _DynamicAttributesType,
- # Inspectable is used only by the mypy plugin
- inspection.Inspectable[Mapper[Any]],
-):
- """Metaclass that may be used in conjunction with the
- :class:`_orm.DeclarativeBase` class to support addition of class
- attributes dynamically.
-
- """
-
-
-@compat_typing.dataclass_transform(
- field_specifiers=(
- MappedColumn,
- RelationshipProperty,
- Composite,
- Synonym,
- mapped_column,
- relationship,
- composite,
- synonym,
- deferred,
- ),
-)
-class DCTransformDeclarative(DeclarativeAttributeIntercept):
- """metaclass that includes @dataclass_transforms"""
-
-
-class DeclarativeMeta(DeclarativeAttributeIntercept):
- metadata: MetaData
- registry: RegistryType
-
- def __init__(
- cls, classname: Any, bases: Any, dict_: Any, **kw: Any
- ) -> None:
- # use cls.__dict__, which can be modified by an
- # __init_subclass__() method (#7900)
- dict_ = cls.__dict__
-
- # early-consume registry from the initial declarative base,
- # assign privately to not conflict with subclass attributes named
- # "registry"
- reg = getattr(cls, "_sa_registry", None)
- if reg is None:
- reg = dict_.get("registry", None)
- if not isinstance(reg, registry):
- raise exc.InvalidRequestError(
- "Declarative base class has no 'registry' attribute, "
- "or registry is not a sqlalchemy.orm.registry() object"
- )
- else:
- cls._sa_registry = reg
-
- if not cls.__dict__.get("__abstract__", False):
- _as_declarative(reg, cls, dict_)
- type.__init__(cls, classname, bases, dict_)
-
-
-def synonym_for(
- name: str, map_column: bool = False
-) -> Callable[[Callable[..., Any]], Synonym[Any]]:
- """Decorator that produces an :func:`_orm.synonym`
- attribute in conjunction with a Python descriptor.
-
- The function being decorated is passed to :func:`_orm.synonym` as the
- :paramref:`.orm.synonym.descriptor` parameter::
-
- class MyClass(Base):
- __tablename__ = 'my_table'
-
- id = Column(Integer, primary_key=True)
- _job_status = Column("job_status", String(50))
-
- @synonym_for("job_status")
- @property
- def job_status(self):
- return "Status: %s" % self._job_status
-
- The :ref:`hybrid properties <mapper_hybrids>` feature of SQLAlchemy
- is typically preferred instead of synonyms, which is a more legacy
- feature.
-
- .. seealso::
-
- :ref:`synonyms` - Overview of synonyms
-
- :func:`_orm.synonym` - the mapper-level function
-
- :ref:`mapper_hybrids` - The Hybrid Attribute extension provides an
- updated approach to augmenting attribute behavior more flexibly than
- can be achieved with synonyms.
-
- """
-
- def decorate(fn: Callable[..., Any]) -> Synonym[Any]:
- return _orm_synonym(name, map_column=map_column, descriptor=fn)
-
- return decorate
-
-
-class _declared_attr_common:
- def __init__(
- self,
- fn: Callable[..., Any],
- cascading: bool = False,
- quiet: bool = False,
- ):
- # suppport
- # @declared_attr
- # @classmethod
- # def foo(cls) -> Mapped[thing]:
- # ...
- # which seems to help typing tools interpret the fn as a classmethod
- # for situations where needed
- if isinstance(fn, classmethod):
- fn = fn.__func__
-
- self.fget = fn
- self._cascading = cascading
- self._quiet = quiet
- self.__doc__ = fn.__doc__
-
- def _collect_return_annotation(self) -> Optional[Type[Any]]:
- return util.get_annotations(self.fget).get("return")
-
- def __get__(self, instance: Optional[object], owner: Any) -> Any:
- # the declared_attr needs to make use of a cache that exists
- # for the span of the declarative scan_attributes() phase.
- # to achieve this we look at the class manager that's configured.
-
- # note this method should not be called outside of the declarative
- # setup phase
-
- cls = owner
- manager = attributes.opt_manager_of_class(cls)
- if manager is None:
- if not re.match(r"^__.+__$", self.fget.__name__):
- # if there is no manager at all, then this class hasn't been
- # run through declarative or mapper() at all, emit a warning.
- util.warn(
- "Unmanaged access of declarative attribute %s from "
- "non-mapped class %s" % (self.fget.__name__, cls.__name__)
- )
- return self.fget(cls)
- elif manager.is_mapped:
- # the class is mapped, which means we're outside of the declarative
- # scan setup, just run the function.
- return self.fget(cls)
-
- # here, we are inside of the declarative scan. use the registry
- # that is tracking the values of these attributes.
- declarative_scan = manager.declarative_scan()
-
- # assert that we are in fact in the declarative scan
- assert declarative_scan is not None
-
- reg = declarative_scan.declared_attr_reg
-
- if self in reg:
- return reg[self]
- else:
- reg[self] = obj = self.fget(cls)
- return obj
-
-
-class _declared_directive(_declared_attr_common, Generic[_T]):
- # see mapping_api.rst for docstring
-
- if typing.TYPE_CHECKING:
-
- def __init__(
- self,
- fn: Callable[..., _T],
- cascading: bool = False,
- ): ...
-
- def __get__(self, instance: Optional[object], owner: Any) -> _T: ...
-
- def __set__(self, instance: Any, value: Any) -> None: ...
-
- def __delete__(self, instance: Any) -> None: ...
-
- def __call__(self, fn: Callable[..., _TT]) -> _declared_directive[_TT]:
- # extensive fooling of mypy underway...
- ...
-
-
-class declared_attr(interfaces._MappedAttribute[_T], _declared_attr_common):
- """Mark a class-level method as representing the definition of
- a mapped property or Declarative directive.
-
- :class:`_orm.declared_attr` is typically applied as a decorator to a class
- level method, turning the attribute into a scalar-like property that can be
- invoked from the uninstantiated class. The Declarative mapping process
- looks for these :class:`_orm.declared_attr` callables as it scans classes,
- and assumes any attribute marked with :class:`_orm.declared_attr` will be a
- callable that will produce an object specific to the Declarative mapping or
- table configuration.
-
- :class:`_orm.declared_attr` is usually applicable to
- :ref:`mixins <orm_mixins_toplevel>`, to define relationships that are to be
- applied to different implementors of the class. It may also be used to
- define dynamically generated column expressions and other Declarative
- attributes.
-
- Example::
-
- class ProvidesUserMixin:
- "A mixin that adds a 'user' relationship to classes."
-
- user_id: Mapped[int] = mapped_column(ForeignKey("user_table.id"))
-
- @declared_attr
- def user(cls) -> Mapped["User"]:
- return relationship("User")
-
- When used with Declarative directives such as ``__tablename__``, the
- :meth:`_orm.declared_attr.directive` modifier may be used which indicates
- to :pep:`484` typing tools that the given method is not dealing with
- :class:`_orm.Mapped` attributes::
-
- class CreateTableName:
- @declared_attr.directive
- def __tablename__(cls) -> str:
- return cls.__name__.lower()
-
- :class:`_orm.declared_attr` can also be applied directly to mapped
- classes, to allow for attributes that dynamically configure themselves
- on subclasses when using mapped inheritance schemes. Below
- illustrates :class:`_orm.declared_attr` to create a dynamic scheme
- for generating the :paramref:`_orm.Mapper.polymorphic_identity` parameter
- for subclasses::
-
- class Employee(Base):
- __tablename__ = 'employee'
-
- id: Mapped[int] = mapped_column(primary_key=True)
- type: Mapped[str] = mapped_column(String(50))
-
- @declared_attr.directive
- def __mapper_args__(cls) -> Dict[str, Any]:
- if cls.__name__ == 'Employee':
- return {
- "polymorphic_on":cls.type,
- "polymorphic_identity":"Employee"
- }
- else:
- return {"polymorphic_identity":cls.__name__}
-
- class Engineer(Employee):
- pass
-
- :class:`_orm.declared_attr` supports decorating functions that are
- explicitly decorated with ``@classmethod``. This is never necessary from a
- runtime perspective, however may be needed in order to support :pep:`484`
- typing tools that don't otherwise recognize the decorated function as
- having class-level behaviors for the ``cls`` parameter::
-
- class SomethingMixin:
- x: Mapped[int]
- y: Mapped[int]
-
- @declared_attr
- @classmethod
- def x_plus_y(cls) -> Mapped[int]:
- return column_property(cls.x + cls.y)
-
- .. versionadded:: 2.0 - :class:`_orm.declared_attr` can accommodate a
- function decorated with ``@classmethod`` to help with :pep:`484`
- integration where needed.
-
-
- .. seealso::
-
- :ref:`orm_mixins_toplevel` - Declarative Mixin documentation with
- background on use patterns for :class:`_orm.declared_attr`.
-
- """ # noqa: E501
-
- if typing.TYPE_CHECKING:
-
- def __init__(
- self,
- fn: _DeclaredAttrDecorated[_T],
- cascading: bool = False,
- ): ...
-
- def __set__(self, instance: Any, value: Any) -> None: ...
-
- def __delete__(self, instance: Any) -> None: ...
-
- # this is the Mapped[] API where at class descriptor get time we want
- # the type checker to see InstrumentedAttribute[_T]. However the
- # callable function prior to mapping in fact calls the given
- # declarative function that does not return InstrumentedAttribute
- @overload
- def __get__(
- self, instance: None, owner: Any
- ) -> InstrumentedAttribute[_T]: ...
-
- @overload
- def __get__(self, instance: object, owner: Any) -> _T: ...
-
- def __get__(
- self, instance: Optional[object], owner: Any
- ) -> Union[InstrumentedAttribute[_T], _T]: ...
-
- @hybridmethod
- def _stateful(cls, **kw: Any) -> _stateful_declared_attr[_T]:
- return _stateful_declared_attr(**kw)
-
- @hybridproperty
- def directive(cls) -> _declared_directive[Any]:
- # see mapping_api.rst for docstring
- return _declared_directive # type: ignore
-
- @hybridproperty
- def cascading(cls) -> _stateful_declared_attr[_T]:
- # see mapping_api.rst for docstring
- return cls._stateful(cascading=True)
-
-
-class _stateful_declared_attr(declared_attr[_T]):
- kw: Dict[str, Any]
-
- def __init__(self, **kw: Any):
- self.kw = kw
-
- @hybridmethod
- def _stateful(self, **kw: Any) -> _stateful_declared_attr[_T]:
- new_kw = self.kw.copy()
- new_kw.update(kw)
- return _stateful_declared_attr(**new_kw)
-
- def __call__(self, fn: _DeclaredAttrDecorated[_T]) -> declared_attr[_T]:
- return declared_attr(fn, **self.kw)
-
-
-def declarative_mixin(cls: Type[_T]) -> Type[_T]:
- """Mark a class as providing the feature of "declarative mixin".
-
- E.g.::
-
- from sqlalchemy.orm import declared_attr
- from sqlalchemy.orm import declarative_mixin
-
- @declarative_mixin
- class MyMixin:
-
- @declared_attr
- def __tablename__(cls):
- return cls.__name__.lower()
-
- __table_args__ = {'mysql_engine': 'InnoDB'}
- __mapper_args__= {'always_refresh': True}
-
- id = Column(Integer, primary_key=True)
-
- class MyModel(MyMixin, Base):
- name = Column(String(1000))
-
- The :func:`_orm.declarative_mixin` decorator currently does not modify
- the given class in any way; it's current purpose is strictly to assist
- the :ref:`Mypy plugin <mypy_toplevel>` in being able to identify
- SQLAlchemy declarative mixin classes when no other context is present.
-
- .. versionadded:: 1.4.6
-
- .. seealso::
-
- :ref:`orm_mixins_toplevel`
-
- :ref:`mypy_declarative_mixins` - in the
- :ref:`Mypy plugin documentation <mypy_toplevel>`
-
- """ # noqa: E501
-
- return cls
-
-
-def _setup_declarative_base(cls: Type[Any]) -> None:
- if "metadata" in cls.__dict__:
- metadata = cls.__dict__["metadata"]
- else:
- metadata = None
-
- if "type_annotation_map" in cls.__dict__:
- type_annotation_map = cls.__dict__["type_annotation_map"]
- else:
- type_annotation_map = None
-
- reg = cls.__dict__.get("registry", None)
- if reg is not None:
- if not isinstance(reg, registry):
- raise exc.InvalidRequestError(
- "Declarative base class has a 'registry' attribute that is "
- "not an instance of sqlalchemy.orm.registry()"
- )
- elif type_annotation_map is not None:
- raise exc.InvalidRequestError(
- "Declarative base class has both a 'registry' attribute and a "
- "type_annotation_map entry. Per-base type_annotation_maps "
- "are not supported. Please apply the type_annotation_map "
- "to this registry directly."
- )
-
- else:
- reg = registry(
- metadata=metadata, type_annotation_map=type_annotation_map
- )
- cls.registry = reg
-
- cls._sa_registry = reg
-
- if "metadata" not in cls.__dict__:
- cls.metadata = cls.registry.metadata
-
- if getattr(cls, "__init__", object.__init__) is object.__init__:
- cls.__init__ = cls.registry.constructor
-
-
-class MappedAsDataclass(metaclass=DCTransformDeclarative):
- """Mixin class to indicate when mapping this class, also convert it to be
- a dataclass.
-
- .. seealso::
-
- :ref:`orm_declarative_native_dataclasses` - complete background
- on SQLAlchemy native dataclass mapping
-
- .. versionadded:: 2.0
-
- """
-
- def __init_subclass__(
- cls,
- init: Union[_NoArg, bool] = _NoArg.NO_ARG,
- repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
- eq: Union[_NoArg, bool] = _NoArg.NO_ARG,
- order: Union[_NoArg, bool] = _NoArg.NO_ARG,
- unsafe_hash: Union[_NoArg, bool] = _NoArg.NO_ARG,
- match_args: Union[_NoArg, bool] = _NoArg.NO_ARG,
- kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
- dataclass_callable: Union[
- _NoArg, Callable[..., Type[Any]]
- ] = _NoArg.NO_ARG,
- **kw: Any,
- ) -> None:
- apply_dc_transforms: _DataclassArguments = {
- "init": init,
- "repr": repr,
- "eq": eq,
- "order": order,
- "unsafe_hash": unsafe_hash,
- "match_args": match_args,
- "kw_only": kw_only,
- "dataclass_callable": dataclass_callable,
- }
-
- current_transforms: _DataclassArguments
-
- if hasattr(cls, "_sa_apply_dc_transforms"):
- current = cls._sa_apply_dc_transforms
-
- _ClassScanMapperConfig._assert_dc_arguments(current)
-
- cls._sa_apply_dc_transforms = current_transforms = { # type: ignore # noqa: E501
- k: current.get(k, _NoArg.NO_ARG) if v is _NoArg.NO_ARG else v
- for k, v in apply_dc_transforms.items()
- }
- else:
- cls._sa_apply_dc_transforms = current_transforms = (
- apply_dc_transforms
- )
-
- super().__init_subclass__(**kw)
-
- if not _is_mapped_class(cls):
- new_anno = (
- _ClassScanMapperConfig._update_annotations_for_non_mapped_class
- )(cls)
- _ClassScanMapperConfig._apply_dataclasses_to_any_class(
- current_transforms, cls, new_anno
- )
-
-
-class DeclarativeBase(
- # Inspectable is used only by the mypy plugin
- inspection.Inspectable[InstanceState[Any]],
- metaclass=DeclarativeAttributeIntercept,
-):
- """Base class used for declarative class definitions.
-
- The :class:`_orm.DeclarativeBase` allows for the creation of new
- declarative bases in such a way that is compatible with type checkers::
-
-
- from sqlalchemy.orm import DeclarativeBase
-
- class Base(DeclarativeBase):
- pass
-
-
- The above ``Base`` class is now usable as the base for new declarative
- mappings. The superclass makes use of the ``__init_subclass__()``
- method to set up new classes and metaclasses aren't used.
-
- When first used, the :class:`_orm.DeclarativeBase` class instantiates a new
- :class:`_orm.registry` to be used with the base, assuming one was not
- provided explicitly. The :class:`_orm.DeclarativeBase` class supports
- class-level attributes which act as parameters for the construction of this
- registry; such as to indicate a specific :class:`_schema.MetaData`
- collection as well as a specific value for
- :paramref:`_orm.registry.type_annotation_map`::
-
- from typing_extensions import Annotated
-
- from sqlalchemy import BigInteger
- from sqlalchemy import MetaData
- from sqlalchemy import String
- from sqlalchemy.orm import DeclarativeBase
-
- bigint = Annotated[int, "bigint"]
- my_metadata = MetaData()
-
- class Base(DeclarativeBase):
- metadata = my_metadata
- type_annotation_map = {
- str: String().with_variant(String(255), "mysql", "mariadb"),
- bigint: BigInteger()
- }
-
- Class-level attributes which may be specified include:
-
- :param metadata: optional :class:`_schema.MetaData` collection.
- If a :class:`_orm.registry` is constructed automatically, this
- :class:`_schema.MetaData` collection will be used to construct it.
- Otherwise, the local :class:`_schema.MetaData` collection will supercede
- that used by an existing :class:`_orm.registry` passed using the
- :paramref:`_orm.DeclarativeBase.registry` parameter.
- :param type_annotation_map: optional type annotation map that will be
- passed to the :class:`_orm.registry` as
- :paramref:`_orm.registry.type_annotation_map`.
- :param registry: supply a pre-existing :class:`_orm.registry` directly.
-
- .. versionadded:: 2.0 Added :class:`.DeclarativeBase`, so that declarative
- base classes may be constructed in such a way that is also recognized
- by :pep:`484` type checkers. As a result, :class:`.DeclarativeBase`
- and other subclassing-oriented APIs should be seen as
- superseding previous "class returned by a function" APIs, namely
- :func:`_orm.declarative_base` and :meth:`_orm.registry.generate_base`,
- where the base class returned cannot be recognized by type checkers
- without using plugins.
-
- **__init__ behavior**
-
- In a plain Python class, the base-most ``__init__()`` method in the class
- hierarchy is ``object.__init__()``, which accepts no arguments. However,
- when the :class:`_orm.DeclarativeBase` subclass is first declared, the
- class is given an ``__init__()`` method that links to the
- :paramref:`_orm.registry.constructor` constructor function, if no
- ``__init__()`` method is already present; this is the usual declarative
- constructor that will assign keyword arguments as attributes on the
- instance, assuming those attributes are established at the class level
- (i.e. are mapped, or are linked to a descriptor). This constructor is
- **never accessed by a mapped class without being called explicitly via
- super()**, as mapped classes are themselves given an ``__init__()`` method
- directly which calls :paramref:`_orm.registry.constructor`, so in the
- default case works independently of what the base-most ``__init__()``
- method does.
-
- .. versionchanged:: 2.0.1 :class:`_orm.DeclarativeBase` has a default
- constructor that links to :paramref:`_orm.registry.constructor` by
- default, so that calls to ``super().__init__()`` can access this
- constructor. Previously, due to an implementation mistake, this default
- constructor was missing, and calling ``super().__init__()`` would invoke
- ``object.__init__()``.
-
- The :class:`_orm.DeclarativeBase` subclass may also declare an explicit
- ``__init__()`` method which will replace the use of the
- :paramref:`_orm.registry.constructor` function at this level::
-
- class Base(DeclarativeBase):
- def __init__(self, id=None):
- self.id = id
-
- Mapped classes still will not invoke this constructor implicitly; it
- remains only accessible by calling ``super().__init__()``::
-
- class MyClass(Base):
- def __init__(self, id=None, name=None):
- self.name = name
- super().__init__(id=id)
-
- Note that this is a different behavior from what functions like the legacy
- :func:`_orm.declarative_base` would do; the base created by those functions
- would always install :paramref:`_orm.registry.constructor` for
- ``__init__()``.
-
-
- """
-
- if typing.TYPE_CHECKING:
-
- def _sa_inspect_type(self) -> Mapper[Self]: ...
-
- def _sa_inspect_instance(self) -> InstanceState[Self]: ...
-
- _sa_registry: ClassVar[_RegistryType]
-
- registry: ClassVar[_RegistryType]
- """Refers to the :class:`_orm.registry` in use where new
- :class:`_orm.Mapper` objects will be associated."""
-
- metadata: ClassVar[MetaData]
- """Refers to the :class:`_schema.MetaData` collection that will be used
- for new :class:`_schema.Table` objects.
-
- .. seealso::
-
- :ref:`orm_declarative_metadata`
-
- """
-
- __name__: ClassVar[str]
-
- # this ideally should be Mapper[Self], but mypy as of 1.4.1 does not
- # like it, and breaks the declared_attr_one test. Pyright/pylance is
- # ok with it.
- __mapper__: ClassVar[Mapper[Any]]
- """The :class:`_orm.Mapper` object to which a particular class is
- mapped.
-
- May also be acquired using :func:`_sa.inspect`, e.g.
- ``inspect(klass)``.
-
- """
-
- __table__: ClassVar[FromClause]
- """The :class:`_sql.FromClause` to which a particular subclass is
- mapped.
-
- This is usually an instance of :class:`_schema.Table` but may also
- refer to other kinds of :class:`_sql.FromClause` such as
- :class:`_sql.Subquery`, depending on how the class is mapped.
-
- .. seealso::
-
- :ref:`orm_declarative_metadata`
-
- """
-
- # pyright/pylance do not consider a classmethod a ClassVar so use Any
- # https://github.com/microsoft/pylance-release/issues/3484
- __tablename__: Any
- """String name to assign to the generated
- :class:`_schema.Table` object, if not specified directly via
- :attr:`_orm.DeclarativeBase.__table__`.
-
- .. seealso::
-
- :ref:`orm_declarative_table`
-
- """
-
- __mapper_args__: Any
- """Dictionary of arguments which will be passed to the
- :class:`_orm.Mapper` constructor.
-
- .. seealso::
-
- :ref:`orm_declarative_mapper_options`
-
- """
-
- __table_args__: Any
- """A dictionary or tuple of arguments that will be passed to the
- :class:`_schema.Table` constructor. See
- :ref:`orm_declarative_table_configuration`
- for background on the specific structure of this collection.
-
- .. seealso::
-
- :ref:`orm_declarative_table_configuration`
-
- """
-
- def __init__(self, **kw: Any): ...
-
- def __init_subclass__(cls, **kw: Any) -> None:
- if DeclarativeBase in cls.__bases__:
- _check_not_declarative(cls, DeclarativeBase)
- _setup_declarative_base(cls)
- else:
- _as_declarative(cls._sa_registry, cls, cls.__dict__)
- super().__init_subclass__(**kw)
-
-
-def _check_not_declarative(cls: Type[Any], base: Type[Any]) -> None:
- cls_dict = cls.__dict__
- if (
- "__table__" in cls_dict
- and not (
- callable(cls_dict["__table__"])
- or hasattr(cls_dict["__table__"], "__get__")
- )
- ) or isinstance(cls_dict.get("__tablename__", None), str):
- raise exc.InvalidRequestError(
- f"Cannot use {base.__name__!r} directly as a declarative base "
- "class. Create a Base by creating a subclass of it."
- )
-
-
-class DeclarativeBaseNoMeta(
- # Inspectable is used only by the mypy plugin
- inspection.Inspectable[InstanceState[Any]]
-):
- """Same as :class:`_orm.DeclarativeBase`, but does not use a metaclass
- to intercept new attributes.
-
- The :class:`_orm.DeclarativeBaseNoMeta` base may be used when use of
- custom metaclasses is desirable.
-
- .. versionadded:: 2.0
-
-
- """
-
- _sa_registry: ClassVar[_RegistryType]
-
- registry: ClassVar[_RegistryType]
- """Refers to the :class:`_orm.registry` in use where new
- :class:`_orm.Mapper` objects will be associated."""
-
- metadata: ClassVar[MetaData]
- """Refers to the :class:`_schema.MetaData` collection that will be used
- for new :class:`_schema.Table` objects.
-
- .. seealso::
-
- :ref:`orm_declarative_metadata`
-
- """
-
- # this ideally should be Mapper[Self], but mypy as of 1.4.1 does not
- # like it, and breaks the declared_attr_one test. Pyright/pylance is
- # ok with it.
- __mapper__: ClassVar[Mapper[Any]]
- """The :class:`_orm.Mapper` object to which a particular class is
- mapped.
-
- May also be acquired using :func:`_sa.inspect`, e.g.
- ``inspect(klass)``.
-
- """
-
- __table__: Optional[FromClause]
- """The :class:`_sql.FromClause` to which a particular subclass is
- mapped.
-
- This is usually an instance of :class:`_schema.Table` but may also
- refer to other kinds of :class:`_sql.FromClause` such as
- :class:`_sql.Subquery`, depending on how the class is mapped.
-
- .. seealso::
-
- :ref:`orm_declarative_metadata`
-
- """
-
- if typing.TYPE_CHECKING:
-
- def _sa_inspect_type(self) -> Mapper[Self]: ...
-
- def _sa_inspect_instance(self) -> InstanceState[Self]: ...
-
- __tablename__: Any
- """String name to assign to the generated
- :class:`_schema.Table` object, if not specified directly via
- :attr:`_orm.DeclarativeBase.__table__`.
-
- .. seealso::
-
- :ref:`orm_declarative_table`
-
- """
-
- __mapper_args__: Any
- """Dictionary of arguments which will be passed to the
- :class:`_orm.Mapper` constructor.
-
- .. seealso::
-
- :ref:`orm_declarative_mapper_options`
-
- """
-
- __table_args__: Any
- """A dictionary or tuple of arguments that will be passed to the
- :class:`_schema.Table` constructor. See
- :ref:`orm_declarative_table_configuration`
- for background on the specific structure of this collection.
-
- .. seealso::
-
- :ref:`orm_declarative_table_configuration`
-
- """
-
- def __init__(self, **kw: Any): ...
-
- def __init_subclass__(cls, **kw: Any) -> None:
- if DeclarativeBaseNoMeta in cls.__bases__:
- _check_not_declarative(cls, DeclarativeBaseNoMeta)
- _setup_declarative_base(cls)
- else:
- _as_declarative(cls._sa_registry, cls, cls.__dict__)
- super().__init_subclass__(**kw)
-
-
-def add_mapped_attribute(
- target: Type[_O], key: str, attr: MapperProperty[Any]
-) -> None:
- """Add a new mapped attribute to an ORM mapped class.
-
- E.g.::
-
- add_mapped_attribute(User, "addresses", relationship(Address))
-
- This may be used for ORM mappings that aren't using a declarative
- metaclass that intercepts attribute set operations.
-
- .. versionadded:: 2.0
-
-
- """
- _add_attribute(target, key, attr)
-
-
-def declarative_base(
- *,
- metadata: Optional[MetaData] = None,
- mapper: Optional[Callable[..., Mapper[Any]]] = None,
- cls: Type[Any] = object,
- name: str = "Base",
- class_registry: Optional[clsregistry._ClsRegistryType] = None,
- type_annotation_map: Optional[_TypeAnnotationMapType] = None,
- constructor: Callable[..., None] = _declarative_constructor,
- metaclass: Type[Any] = DeclarativeMeta,
-) -> Any:
- r"""Construct a base class for declarative class definitions.
-
- The new base class will be given a metaclass that produces
- appropriate :class:`~sqlalchemy.schema.Table` objects and makes
- the appropriate :class:`_orm.Mapper` calls based on the
- information provided declaratively in the class and any subclasses
- of the class.
-
- .. versionchanged:: 2.0 Note that the :func:`_orm.declarative_base`
- function is superseded by the new :class:`_orm.DeclarativeBase` class,
- which generates a new "base" class using subclassing, rather than
- return value of a function. This allows an approach that is compatible
- with :pep:`484` typing tools.
-
- The :func:`_orm.declarative_base` function is a shorthand version
- of using the :meth:`_orm.registry.generate_base`
- method. That is, the following::
-
- from sqlalchemy.orm import declarative_base
-
- Base = declarative_base()
-
- Is equivalent to::
-
- from sqlalchemy.orm import registry
-
- mapper_registry = registry()
- Base = mapper_registry.generate_base()
-
- See the docstring for :class:`_orm.registry`
- and :meth:`_orm.registry.generate_base`
- for more details.
-
- .. versionchanged:: 1.4 The :func:`_orm.declarative_base`
- function is now a specialization of the more generic
- :class:`_orm.registry` class. The function also moves to the
- ``sqlalchemy.orm`` package from the ``declarative.ext`` package.
-
-
- :param metadata:
- An optional :class:`~sqlalchemy.schema.MetaData` instance. All
- :class:`~sqlalchemy.schema.Table` objects implicitly declared by
- subclasses of the base will share this MetaData. A MetaData instance
- will be created if none is provided. The
- :class:`~sqlalchemy.schema.MetaData` instance will be available via the
- ``metadata`` attribute of the generated declarative base class.
-
- :param mapper:
- An optional callable, defaults to :class:`_orm.Mapper`. Will
- be used to map subclasses to their Tables.
-
- :param cls:
- Defaults to :class:`object`. A type to use as the base for the generated
- declarative base class. May be a class or tuple of classes.
-
- :param name:
- Defaults to ``Base``. The display name for the generated
- class. Customizing this is not required, but can improve clarity in
- tracebacks and debugging.
-
- :param constructor:
- Specify the implementation for the ``__init__`` function on a mapped
- class that has no ``__init__`` of its own. Defaults to an
- implementation that assigns \**kwargs for declared
- fields and relationships to an instance. If ``None`` is supplied,
- no __init__ will be provided and construction will fall back to
- cls.__init__ by way of the normal Python semantics.
-
- :param class_registry: optional dictionary that will serve as the
- registry of class names-> mapped classes when string names
- are used to identify classes inside of :func:`_orm.relationship`
- and others. Allows two or more declarative base classes
- to share the same registry of class names for simplified
- inter-base relationships.
-
- :param type_annotation_map: optional dictionary of Python types to
- SQLAlchemy :class:`_types.TypeEngine` classes or instances. This
- is used exclusively by the :class:`_orm.MappedColumn` construct
- to produce column types based on annotations within the
- :class:`_orm.Mapped` type.
-
-
- .. versionadded:: 2.0
-
- .. seealso::
-
- :ref:`orm_declarative_mapped_column_type_map`
-
- :param metaclass:
- Defaults to :class:`.DeclarativeMeta`. A metaclass or __metaclass__
- compatible callable to use as the meta type of the generated
- declarative base class.
-
- .. seealso::
-
- :class:`_orm.registry`
-
- """
-
- return registry(
- metadata=metadata,
- class_registry=class_registry,
- constructor=constructor,
- type_annotation_map=type_annotation_map,
- ).generate_base(
- mapper=mapper,
- cls=cls,
- name=name,
- metaclass=metaclass,
- )
-
-
-class registry:
- """Generalized registry for mapping classes.
-
- The :class:`_orm.registry` serves as the basis for maintaining a collection
- of mappings, and provides configurational hooks used to map classes.
-
- The three general kinds of mappings supported are Declarative Base,
- Declarative Decorator, and Imperative Mapping. All of these mapping
- styles may be used interchangeably:
-
- * :meth:`_orm.registry.generate_base` returns a new declarative base
- class, and is the underlying implementation of the
- :func:`_orm.declarative_base` function.
-
- * :meth:`_orm.registry.mapped` provides a class decorator that will
- apply declarative mapping to a class without the use of a declarative
- base class.
-
- * :meth:`_orm.registry.map_imperatively` will produce a
- :class:`_orm.Mapper` for a class without scanning the class for
- declarative class attributes. This method suits the use case historically
- provided by the ``sqlalchemy.orm.mapper()`` classical mapping function,
- which is removed as of SQLAlchemy 2.0.
-
- .. versionadded:: 1.4
-
- .. seealso::
-
- :ref:`orm_mapping_classes_toplevel` - overview of class mapping
- styles.
-
- """
-
- _class_registry: clsregistry._ClsRegistryType
- _managers: weakref.WeakKeyDictionary[ClassManager[Any], Literal[True]]
- _non_primary_mappers: weakref.WeakKeyDictionary[Mapper[Any], Literal[True]]
- metadata: MetaData
- constructor: CallableReference[Callable[..., None]]
- type_annotation_map: _MutableTypeAnnotationMapType
- _dependents: Set[_RegistryType]
- _dependencies: Set[_RegistryType]
- _new_mappers: bool
-
- def __init__(
- self,
- *,
- metadata: Optional[MetaData] = None,
- class_registry: Optional[clsregistry._ClsRegistryType] = None,
- type_annotation_map: Optional[_TypeAnnotationMapType] = None,
- constructor: Callable[..., None] = _declarative_constructor,
- ):
- r"""Construct a new :class:`_orm.registry`
-
- :param metadata:
- An optional :class:`_schema.MetaData` instance. All
- :class:`_schema.Table` objects generated using declarative
- table mapping will make use of this :class:`_schema.MetaData`
- collection. If this argument is left at its default of ``None``,
- a blank :class:`_schema.MetaData` collection is created.
-
- :param constructor:
- Specify the implementation for the ``__init__`` function on a mapped
- class that has no ``__init__`` of its own. Defaults to an
- implementation that assigns \**kwargs for declared
- fields and relationships to an instance. If ``None`` is supplied,
- no __init__ will be provided and construction will fall back to
- cls.__init__ by way of the normal Python semantics.
-
- :param class_registry: optional dictionary that will serve as the
- registry of class names-> mapped classes when string names
- are used to identify classes inside of :func:`_orm.relationship`
- and others. Allows two or more declarative base classes
- to share the same registry of class names for simplified
- inter-base relationships.
-
- :param type_annotation_map: optional dictionary of Python types to
- SQLAlchemy :class:`_types.TypeEngine` classes or instances.
- The provided dict will update the default type mapping. This
- is used exclusively by the :class:`_orm.MappedColumn` construct
- to produce column types based on annotations within the
- :class:`_orm.Mapped` type.
-
- .. versionadded:: 2.0
-
- .. seealso::
-
- :ref:`orm_declarative_mapped_column_type_map`
-
-
- """
- lcl_metadata = metadata or MetaData()
-
- if class_registry is None:
- class_registry = weakref.WeakValueDictionary()
-
- self._class_registry = class_registry
- self._managers = weakref.WeakKeyDictionary()
- self._non_primary_mappers = weakref.WeakKeyDictionary()
- self.metadata = lcl_metadata
- self.constructor = constructor
- self.type_annotation_map = {}
- if type_annotation_map is not None:
- self.update_type_annotation_map(type_annotation_map)
- self._dependents = set()
- self._dependencies = set()
-
- self._new_mappers = False
-
- with mapperlib._CONFIGURE_MUTEX:
- mapperlib._mapper_registries[self] = True
-
- def update_type_annotation_map(
- self,
- type_annotation_map: _TypeAnnotationMapType,
- ) -> None:
- """update the :paramref:`_orm.registry.type_annotation_map` with new
- values."""
-
- self.type_annotation_map.update(
- {
- sub_type: sqltype
- for typ, sqltype in type_annotation_map.items()
- for sub_type in compat_typing.expand_unions(
- typ, include_union=True, discard_none=True
- )
- }
- )
-
- def _resolve_type(
- self, python_type: _MatchedOnType
- ) -> Optional[sqltypes.TypeEngine[Any]]:
- search: Iterable[Tuple[_MatchedOnType, Type[Any]]]
- python_type_type: Type[Any]
-
- if is_generic(python_type):
- if is_literal(python_type):
- python_type_type = cast("Type[Any]", python_type)
-
- search = ( # type: ignore[assignment]
- (python_type, python_type_type),
- (Literal, python_type_type),
- )
- else:
- python_type_type = python_type.__origin__
- search = ((python_type, python_type_type),)
- elif is_newtype(python_type):
- python_type_type = flatten_newtype(python_type)
- search = ((python_type, python_type_type),)
- elif is_pep695(python_type):
- python_type_type = python_type.__value__
- flattened = None
- search = ((python_type, python_type_type),)
- else:
- python_type_type = cast("Type[Any]", python_type)
- flattened = None
- search = ((pt, pt) for pt in python_type_type.__mro__)
-
- for pt, flattened in search:
- # we search through full __mro__ for types. however...
- sql_type = self.type_annotation_map.get(pt)
- if sql_type is None:
- sql_type = sqltypes._type_map_get(pt) # type: ignore # noqa: E501
-
- if sql_type is not None:
- sql_type_inst = sqltypes.to_instance(sql_type)
-
- # ... this additional step will reject most
- # type -> supertype matches, such as if we had
- # a MyInt(int) subclass. note also we pass NewType()
- # here directly; these always have to be in the
- # type_annotation_map to be useful
- resolved_sql_type = sql_type_inst._resolve_for_python_type(
- python_type_type,
- pt,
- flattened,
- )
- if resolved_sql_type is not None:
- return resolved_sql_type
-
- return None
-
- @property
- def mappers(self) -> FrozenSet[Mapper[Any]]:
- """read only collection of all :class:`_orm.Mapper` objects."""
-
- return frozenset(manager.mapper for manager in self._managers).union(
- self._non_primary_mappers
- )
-
- def _set_depends_on(self, registry: RegistryType) -> None:
- if registry is self:
- return
- registry._dependents.add(self)
- self._dependencies.add(registry)
-
- def _flag_new_mapper(self, mapper: Mapper[Any]) -> None:
- mapper._ready_for_configure = True
- if self._new_mappers:
- return
-
- for reg in self._recurse_with_dependents({self}):
- reg._new_mappers = True
-
- @classmethod
- def _recurse_with_dependents(
- cls, registries: Set[RegistryType]
- ) -> Iterator[RegistryType]:
- todo = registries
- done = set()
- while todo:
- reg = todo.pop()
- done.add(reg)
-
- # if yielding would remove dependents, make sure we have
- # them before
- todo.update(reg._dependents.difference(done))
- yield reg
-
- # if yielding would add dependents, make sure we have them
- # after
- todo.update(reg._dependents.difference(done))
-
- @classmethod
- def _recurse_with_dependencies(
- cls, registries: Set[RegistryType]
- ) -> Iterator[RegistryType]:
- todo = registries
- done = set()
- while todo:
- reg = todo.pop()
- done.add(reg)
-
- # if yielding would remove dependencies, make sure we have
- # them before
- todo.update(reg._dependencies.difference(done))
-
- yield reg
-
- # if yielding would remove dependencies, make sure we have
- # them before
- todo.update(reg._dependencies.difference(done))
-
- def _mappers_to_configure(self) -> Iterator[Mapper[Any]]:
- return itertools.chain(
- (
- manager.mapper
- for manager in list(self._managers)
- if manager.is_mapped
- and not manager.mapper.configured
- and manager.mapper._ready_for_configure
- ),
- (
- npm
- for npm in list(self._non_primary_mappers)
- if not npm.configured and npm._ready_for_configure
- ),
- )
-
- def _add_non_primary_mapper(self, np_mapper: Mapper[Any]) -> None:
- self._non_primary_mappers[np_mapper] = True
-
- def _dispose_cls(self, cls: Type[_O]) -> None:
- clsregistry.remove_class(cls.__name__, cls, self._class_registry)
-
- def _add_manager(self, manager: ClassManager[Any]) -> None:
- self._managers[manager] = True
- if manager.is_mapped:
- raise exc.ArgumentError(
- "Class '%s' already has a primary mapper defined. "
- % manager.class_
- )
- assert manager.registry is None
- manager.registry = self
-
- def configure(self, cascade: bool = False) -> None:
- """Configure all as-yet unconfigured mappers in this
- :class:`_orm.registry`.
-
- The configure step is used to reconcile and initialize the
- :func:`_orm.relationship` linkages between mapped classes, as well as
- to invoke configuration events such as the
- :meth:`_orm.MapperEvents.before_configured` and
- :meth:`_orm.MapperEvents.after_configured`, which may be used by ORM
- extensions or user-defined extension hooks.
-
- If one or more mappers in this registry contain
- :func:`_orm.relationship` constructs that refer to mapped classes in
- other registries, this registry is said to be *dependent* on those
- registries. In order to configure those dependent registries
- automatically, the :paramref:`_orm.registry.configure.cascade` flag
- should be set to ``True``. Otherwise, if they are not configured, an
- exception will be raised. The rationale behind this behavior is to
- allow an application to programmatically invoke configuration of
- registries while controlling whether or not the process implicitly
- reaches other registries.
-
- As an alternative to invoking :meth:`_orm.registry.configure`, the ORM
- function :func:`_orm.configure_mappers` function may be used to ensure
- configuration is complete for all :class:`_orm.registry` objects in
- memory. This is generally simpler to use and also predates the usage of
- :class:`_orm.registry` objects overall. However, this function will
- impact all mappings throughout the running Python process and may be
- more memory/time consuming for an application that has many registries
- in use for different purposes that may not be needed immediately.
-
- .. seealso::
-
- :func:`_orm.configure_mappers`
-
-
- .. versionadded:: 1.4.0b2
-
- """
- mapperlib._configure_registries({self}, cascade=cascade)
-
- def dispose(self, cascade: bool = False) -> None:
- """Dispose of all mappers in this :class:`_orm.registry`.
-
- After invocation, all the classes that were mapped within this registry
- will no longer have class instrumentation associated with them. This
- method is the per-:class:`_orm.registry` analogue to the
- application-wide :func:`_orm.clear_mappers` function.
-
- If this registry contains mappers that are dependencies of other
- registries, typically via :func:`_orm.relationship` links, then those
- registries must be disposed as well. When such registries exist in
- relation to this one, their :meth:`_orm.registry.dispose` method will
- also be called, if the :paramref:`_orm.registry.dispose.cascade` flag
- is set to ``True``; otherwise, an error is raised if those registries
- were not already disposed.
-
- .. versionadded:: 1.4.0b2
-
- .. seealso::
-
- :func:`_orm.clear_mappers`
-
- """
-
- mapperlib._dispose_registries({self}, cascade=cascade)
-
- def _dispose_manager_and_mapper(self, manager: ClassManager[Any]) -> None:
- if "mapper" in manager.__dict__:
- mapper = manager.mapper
-
- mapper._set_dispose_flags()
-
- class_ = manager.class_
- self._dispose_cls(class_)
- instrumentation._instrumentation_factory.unregister(class_)
-
- def generate_base(
- self,
- mapper: Optional[Callable[..., Mapper[Any]]] = None,
- cls: Type[Any] = object,
- name: str = "Base",
- metaclass: Type[Any] = DeclarativeMeta,
- ) -> Any:
- """Generate a declarative base class.
-
- Classes that inherit from the returned class object will be
- automatically mapped using declarative mapping.
-
- E.g.::
-
- from sqlalchemy.orm import registry
-
- mapper_registry = registry()
-
- Base = mapper_registry.generate_base()
-
- class MyClass(Base):
- __tablename__ = "my_table"
- id = Column(Integer, primary_key=True)
-
- The above dynamically generated class is equivalent to the
- non-dynamic example below::
-
- from sqlalchemy.orm import registry
- from sqlalchemy.orm.decl_api import DeclarativeMeta
-
- mapper_registry = registry()
-
- class Base(metaclass=DeclarativeMeta):
- __abstract__ = True
- registry = mapper_registry
- metadata = mapper_registry.metadata
-
- __init__ = mapper_registry.constructor
-
- .. versionchanged:: 2.0 Note that the
- :meth:`_orm.registry.generate_base` method is superseded by the new
- :class:`_orm.DeclarativeBase` class, which generates a new "base"
- class using subclassing, rather than return value of a function.
- This allows an approach that is compatible with :pep:`484` typing
- tools.
-
- The :meth:`_orm.registry.generate_base` method provides the
- implementation for the :func:`_orm.declarative_base` function, which
- creates the :class:`_orm.registry` and base class all at once.
-
- See the section :ref:`orm_declarative_mapping` for background and
- examples.
-
- :param mapper:
- An optional callable, defaults to :class:`_orm.Mapper`.
- This function is used to generate new :class:`_orm.Mapper` objects.
-
- :param cls:
- Defaults to :class:`object`. A type to use as the base for the
- generated declarative base class. May be a class or tuple of classes.
-
- :param name:
- Defaults to ``Base``. The display name for the generated
- class. Customizing this is not required, but can improve clarity in
- tracebacks and debugging.
-
- :param metaclass:
- Defaults to :class:`.DeclarativeMeta`. A metaclass or __metaclass__
- compatible callable to use as the meta type of the generated
- declarative base class.
-
- .. seealso::
-
- :ref:`orm_declarative_mapping`
-
- :func:`_orm.declarative_base`
-
- """
- metadata = self.metadata
-
- bases = not isinstance(cls, tuple) and (cls,) or cls
-
- class_dict: Dict[str, Any] = dict(registry=self, metadata=metadata)
- if isinstance(cls, type):
- class_dict["__doc__"] = cls.__doc__
-
- if self.constructor is not None:
- class_dict["__init__"] = self.constructor
-
- class_dict["__abstract__"] = True
- if mapper:
- class_dict["__mapper_cls__"] = mapper
-
- if hasattr(cls, "__class_getitem__"):
-
- def __class_getitem__(cls: Type[_T], key: Any) -> Type[_T]:
- # allow generic classes in py3.9+
- return cls
-
- class_dict["__class_getitem__"] = __class_getitem__
-
- return metaclass(name, bases, class_dict)
-
- @compat_typing.dataclass_transform(
- field_specifiers=(
- MappedColumn,
- RelationshipProperty,
- Composite,
- Synonym,
- mapped_column,
- relationship,
- composite,
- synonym,
- deferred,
- ),
- )
- @overload
- def mapped_as_dataclass(self, __cls: Type[_O]) -> Type[_O]: ...
-
- @overload
- def mapped_as_dataclass(
- self,
- __cls: Literal[None] = ...,
- *,
- init: Union[_NoArg, bool] = ...,
- repr: Union[_NoArg, bool] = ..., # noqa: A002
- eq: Union[_NoArg, bool] = ...,
- order: Union[_NoArg, bool] = ...,
- unsafe_hash: Union[_NoArg, bool] = ...,
- match_args: Union[_NoArg, bool] = ...,
- kw_only: Union[_NoArg, bool] = ...,
- dataclass_callable: Union[_NoArg, Callable[..., Type[Any]]] = ...,
- ) -> Callable[[Type[_O]], Type[_O]]: ...
-
- def mapped_as_dataclass(
- self,
- __cls: Optional[Type[_O]] = None,
- *,
- init: Union[_NoArg, bool] = _NoArg.NO_ARG,
- repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
- eq: Union[_NoArg, bool] = _NoArg.NO_ARG,
- order: Union[_NoArg, bool] = _NoArg.NO_ARG,
- unsafe_hash: Union[_NoArg, bool] = _NoArg.NO_ARG,
- match_args: Union[_NoArg, bool] = _NoArg.NO_ARG,
- kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
- dataclass_callable: Union[
- _NoArg, Callable[..., Type[Any]]
- ] = _NoArg.NO_ARG,
- ) -> Union[Type[_O], Callable[[Type[_O]], Type[_O]]]:
- """Class decorator that will apply the Declarative mapping process
- to a given class, and additionally convert the class to be a
- Python dataclass.
-
- .. seealso::
-
- :ref:`orm_declarative_native_dataclasses` - complete background
- on SQLAlchemy native dataclass mapping
-
-
- .. versionadded:: 2.0
-
-
- """
-
- def decorate(cls: Type[_O]) -> Type[_O]:
- setattr(
- cls,
- "_sa_apply_dc_transforms",
- {
- "init": init,
- "repr": repr,
- "eq": eq,
- "order": order,
- "unsafe_hash": unsafe_hash,
- "match_args": match_args,
- "kw_only": kw_only,
- "dataclass_callable": dataclass_callable,
- },
- )
- _as_declarative(self, cls, cls.__dict__)
- return cls
-
- if __cls:
- return decorate(__cls)
- else:
- return decorate
-
- def mapped(self, cls: Type[_O]) -> Type[_O]:
- """Class decorator that will apply the Declarative mapping process
- to a given class.
-
- E.g.::
-
- from sqlalchemy.orm import registry
-
- mapper_registry = registry()
-
- @mapper_registry.mapped
- class Foo:
- __tablename__ = 'some_table'
-
- id = Column(Integer, primary_key=True)
- name = Column(String)
-
- See the section :ref:`orm_declarative_mapping` for complete
- details and examples.
-
- :param cls: class to be mapped.
-
- :return: the class that was passed.
-
- .. seealso::
-
- :ref:`orm_declarative_mapping`
-
- :meth:`_orm.registry.generate_base` - generates a base class
- that will apply Declarative mapping to subclasses automatically
- using a Python metaclass.
-
- .. seealso::
-
- :meth:`_orm.registry.mapped_as_dataclass`
-
- """
- _as_declarative(self, cls, cls.__dict__)
- return cls
-
- def as_declarative_base(self, **kw: Any) -> Callable[[Type[_T]], Type[_T]]:
- """
- Class decorator which will invoke
- :meth:`_orm.registry.generate_base`
- for a given base class.
-
- E.g.::
-
- from sqlalchemy.orm import registry
-
- mapper_registry = registry()
-
- @mapper_registry.as_declarative_base()
- class Base:
- @declared_attr
- def __tablename__(cls):
- return cls.__name__.lower()
- id = Column(Integer, primary_key=True)
-
- class MyMappedClass(Base):
- # ...
-
- All keyword arguments passed to
- :meth:`_orm.registry.as_declarative_base` are passed
- along to :meth:`_orm.registry.generate_base`.
-
- """
-
- def decorate(cls: Type[_T]) -> Type[_T]:
- kw["cls"] = cls
- kw["name"] = cls.__name__
- return self.generate_base(**kw) # type: ignore
-
- return decorate
-
- def map_declaratively(self, cls: Type[_O]) -> Mapper[_O]:
- """Map a class declaratively.
-
- In this form of mapping, the class is scanned for mapping information,
- including for columns to be associated with a table, and/or an
- actual table object.
-
- Returns the :class:`_orm.Mapper` object.
-
- E.g.::
-
- from sqlalchemy.orm import registry
-
- mapper_registry = registry()
-
- class Foo:
- __tablename__ = 'some_table'
-
- id = Column(Integer, primary_key=True)
- name = Column(String)
-
- mapper = mapper_registry.map_declaratively(Foo)
-
- This function is more conveniently invoked indirectly via either the
- :meth:`_orm.registry.mapped` class decorator or by subclassing a
- declarative metaclass generated from
- :meth:`_orm.registry.generate_base`.
-
- See the section :ref:`orm_declarative_mapping` for complete
- details and examples.
-
- :param cls: class to be mapped.
-
- :return: a :class:`_orm.Mapper` object.
-
- .. seealso::
-
- :ref:`orm_declarative_mapping`
-
- :meth:`_orm.registry.mapped` - more common decorator interface
- to this function.
-
- :meth:`_orm.registry.map_imperatively`
-
- """
- _as_declarative(self, cls, cls.__dict__)
- return cls.__mapper__ # type: ignore
-
- def map_imperatively(
- self,
- class_: Type[_O],
- local_table: Optional[FromClause] = None,
- **kw: Any,
- ) -> Mapper[_O]:
- r"""Map a class imperatively.
-
- In this form of mapping, the class is not scanned for any mapping
- information. Instead, all mapping constructs are passed as
- arguments.
-
- This method is intended to be fully equivalent to the now-removed
- SQLAlchemy ``mapper()`` function, except that it's in terms of
- a particular registry.
-
- E.g.::
-
- from sqlalchemy.orm import registry
-
- mapper_registry = registry()
-
- my_table = Table(
- "my_table",
- mapper_registry.metadata,
- Column('id', Integer, primary_key=True)
- )
-
- class MyClass:
- pass
-
- mapper_registry.map_imperatively(MyClass, my_table)
-
- See the section :ref:`orm_imperative_mapping` for complete background
- and usage examples.
-
- :param class\_: The class to be mapped. Corresponds to the
- :paramref:`_orm.Mapper.class_` parameter.
-
- :param local_table: the :class:`_schema.Table` or other
- :class:`_sql.FromClause` object that is the subject of the mapping.
- Corresponds to the
- :paramref:`_orm.Mapper.local_table` parameter.
-
- :param \**kw: all other keyword arguments are passed to the
- :class:`_orm.Mapper` constructor directly.
-
- .. seealso::
-
- :ref:`orm_imperative_mapping`
-
- :ref:`orm_declarative_mapping`
-
- """
- return _mapper(self, class_, local_table, kw)
-
-
-RegistryType = registry
-
-if not TYPE_CHECKING:
- # allow for runtime type resolution of ``ClassVar[_RegistryType]``
- _RegistryType = registry # noqa
-
-
-def as_declarative(**kw: Any) -> Callable[[Type[_T]], Type[_T]]:
- """
- Class decorator which will adapt a given class into a
- :func:`_orm.declarative_base`.
-
- This function makes use of the :meth:`_orm.registry.as_declarative_base`
- method, by first creating a :class:`_orm.registry` automatically
- and then invoking the decorator.
-
- E.g.::
-
- from sqlalchemy.orm import as_declarative
-
- @as_declarative()
- class Base:
- @declared_attr
- def __tablename__(cls):
- return cls.__name__.lower()
- id = Column(Integer, primary_key=True)
-
- class MyMappedClass(Base):
- # ...
-
- .. seealso::
-
- :meth:`_orm.registry.as_declarative_base`
-
- """
- metadata, class_registry = (
- kw.pop("metadata", None),
- kw.pop("class_registry", None),
- )
-
- return registry(
- metadata=metadata, class_registry=class_registry
- ).as_declarative_base(**kw)
-
-
-@inspection._inspects(
- DeclarativeMeta, DeclarativeBase, DeclarativeAttributeIntercept
-)
-def _inspect_decl_meta(cls: Type[Any]) -> Optional[Mapper[Any]]:
- mp: Optional[Mapper[Any]] = _inspect_mapped_class(cls)
- if mp is None:
- if _DeferredMapperConfig.has_cls(cls):
- _DeferredMapperConfig.raise_unmapped_for_cls(cls)
- return mp
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py
deleted file mode 100644
index 96530c3..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py
+++ /dev/null
@@ -1,2152 +0,0 @@
-# orm/decl_base.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
-
-"""Internal implementation for declarative."""
-
-from __future__ import annotations
-
-import collections
-import dataclasses
-import re
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Dict
-from typing import Iterable
-from typing import List
-from typing import Mapping
-from typing import NamedTuple
-from typing import NoReturn
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-import weakref
-
-from . import attributes
-from . import clsregistry
-from . import exc as orm_exc
-from . import instrumentation
-from . import mapperlib
-from ._typing import _O
-from ._typing import attr_is_internal_proxy
-from .attributes import InstrumentedAttribute
-from .attributes import QueryableAttribute
-from .base import _is_mapped_class
-from .base import InspectionAttr
-from .descriptor_props import CompositeProperty
-from .descriptor_props import SynonymProperty
-from .interfaces import _AttributeOptions
-from .interfaces import _DCAttributeOptions
-from .interfaces import _IntrospectsAnnotations
-from .interfaces import _MappedAttribute
-from .interfaces import _MapsColumns
-from .interfaces import MapperProperty
-from .mapper import Mapper
-from .properties import ColumnProperty
-from .properties import MappedColumn
-from .util import _extract_mapped_subtype
-from .util import _is_mapped_annotation
-from .util import class_mapper
-from .util import de_stringify_annotation
-from .. import event
-from .. import exc
-from .. import util
-from ..sql import expression
-from ..sql.base import _NoArg
-from ..sql.schema import Column
-from ..sql.schema import Table
-from ..util import topological
-from ..util.typing import _AnnotationScanType
-from ..util.typing import is_fwd_ref
-from ..util.typing import is_literal
-from ..util.typing import Protocol
-from ..util.typing import TypedDict
-from ..util.typing import typing_get_args
-
-if TYPE_CHECKING:
- from ._typing import _ClassDict
- from ._typing import _RegistryType
- from .base import Mapped
- from .decl_api import declared_attr
- from .instrumentation import ClassManager
- from ..sql.elements import NamedColumn
- from ..sql.schema import MetaData
- from ..sql.selectable import FromClause
-
-_T = TypeVar("_T", bound=Any)
-
-_MapperKwArgs = Mapping[str, Any]
-_TableArgsType = Union[Tuple[Any, ...], Dict[str, Any]]
-
-
-class MappedClassProtocol(Protocol[_O]):
- """A protocol representing a SQLAlchemy mapped class.
-
- The protocol is generic on the type of class, use
- ``MappedClassProtocol[Any]`` to allow any mapped class.
- """
-
- __name__: str
- __mapper__: Mapper[_O]
- __table__: FromClause
-
- def __call__(self, **kw: Any) -> _O: ...
-
-
-class _DeclMappedClassProtocol(MappedClassProtocol[_O], Protocol):
- "Internal more detailed version of ``MappedClassProtocol``."
- metadata: MetaData
- __tablename__: str
- __mapper_args__: _MapperKwArgs
- __table_args__: Optional[_TableArgsType]
-
- _sa_apply_dc_transforms: Optional[_DataclassArguments]
-
- def __declare_first__(self) -> None: ...
-
- def __declare_last__(self) -> None: ...
-
-
-class _DataclassArguments(TypedDict):
- init: Union[_NoArg, bool]
- repr: Union[_NoArg, bool]
- eq: Union[_NoArg, bool]
- order: Union[_NoArg, bool]
- unsafe_hash: Union[_NoArg, bool]
- match_args: Union[_NoArg, bool]
- kw_only: Union[_NoArg, bool]
- dataclass_callable: Union[_NoArg, Callable[..., Type[Any]]]
-
-
-def _declared_mapping_info(
- cls: Type[Any],
-) -> Optional[Union[_DeferredMapperConfig, Mapper[Any]]]:
- # deferred mapping
- if _DeferredMapperConfig.has_cls(cls):
- return _DeferredMapperConfig.config_for_cls(cls)
- # regular mapping
- elif _is_mapped_class(cls):
- return class_mapper(cls, configure=False)
- else:
- return None
-
-
-def _is_supercls_for_inherits(cls: Type[Any]) -> bool:
- """return True if this class will be used as a superclass to set in
- 'inherits'.
-
- This includes deferred mapper configs that aren't mapped yet, however does
- not include classes with _sa_decl_prepare_nocascade (e.g.
- ``AbstractConcreteBase``); these concrete-only classes are not set up as
- "inherits" until after mappers are configured using
- mapper._set_concrete_base()
-
- """
- if _DeferredMapperConfig.has_cls(cls):
- return not _get_immediate_cls_attr(
- cls, "_sa_decl_prepare_nocascade", strict=True
- )
- # regular mapping
- elif _is_mapped_class(cls):
- return True
- else:
- return False
-
-
-def _resolve_for_abstract_or_classical(cls: Type[Any]) -> Optional[Type[Any]]:
- if cls is object:
- return None
-
- sup: Optional[Type[Any]]
-
- if cls.__dict__.get("__abstract__", False):
- for base_ in cls.__bases__:
- sup = _resolve_for_abstract_or_classical(base_)
- if sup is not None:
- return sup
- else:
- return None
- else:
- clsmanager = _dive_for_cls_manager(cls)
-
- if clsmanager:
- return clsmanager.class_
- else:
- return cls
-
-
-def _get_immediate_cls_attr(
- cls: Type[Any], attrname: str, strict: bool = False
-) -> Optional[Any]:
- """return an attribute of the class that is either present directly
- on the class, e.g. not on a superclass, or is from a superclass but
- this superclass is a non-mapped mixin, that is, not a descendant of
- the declarative base and is also not classically mapped.
-
- This is used to detect attributes that indicate something about
- a mapped class independently from any mapped classes that it may
- inherit from.
-
- """
-
- # the rules are different for this name than others,
- # make sure we've moved it out. transitional
- assert attrname != "__abstract__"
-
- if not issubclass(cls, object):
- return None
-
- if attrname in cls.__dict__:
- return getattr(cls, attrname)
-
- for base in cls.__mro__[1:]:
- _is_classical_inherits = _dive_for_cls_manager(base) is not None
-
- if attrname in base.__dict__ and (
- base is cls
- or (
- (base in cls.__bases__ if strict else True)
- and not _is_classical_inherits
- )
- ):
- return getattr(base, attrname)
- else:
- return None
-
-
-def _dive_for_cls_manager(cls: Type[_O]) -> Optional[ClassManager[_O]]:
- # because the class manager registration is pluggable,
- # we need to do the search for every class in the hierarchy,
- # rather than just a simple "cls._sa_class_manager"
-
- for base in cls.__mro__:
- manager: Optional[ClassManager[_O]] = attributes.opt_manager_of_class(
- base
- )
- if manager:
- return manager
- return None
-
-
-def _as_declarative(
- registry: _RegistryType, cls: Type[Any], dict_: _ClassDict
-) -> Optional[_MapperConfig]:
- # declarative scans the class for attributes. no table or mapper
- # args passed separately.
- return _MapperConfig.setup_mapping(registry, cls, dict_, None, {})
-
-
-def _mapper(
- registry: _RegistryType,
- cls: Type[_O],
- table: Optional[FromClause],
- mapper_kw: _MapperKwArgs,
-) -> Mapper[_O]:
- _ImperativeMapperConfig(registry, cls, table, mapper_kw)
- return cast("MappedClassProtocol[_O]", cls).__mapper__
-
-
-@util.preload_module("sqlalchemy.orm.decl_api")
-def _is_declarative_props(obj: Any) -> bool:
- _declared_attr_common = util.preloaded.orm_decl_api._declared_attr_common
-
- return isinstance(obj, (_declared_attr_common, util.classproperty))
-
-
-def _check_declared_props_nocascade(
- obj: Any, name: str, cls: Type[_O]
-) -> bool:
- if _is_declarative_props(obj):
- if getattr(obj, "_cascading", False):
- util.warn(
- "@declared_attr.cascading is not supported on the %s "
- "attribute on class %s. This attribute invokes for "
- "subclasses in any case." % (name, cls)
- )
- return True
- else:
- return False
-
-
-class _MapperConfig:
- __slots__ = (
- "cls",
- "classname",
- "properties",
- "declared_attr_reg",
- "__weakref__",
- )
-
- cls: Type[Any]
- classname: str
- properties: util.OrderedDict[
- str,
- Union[
- Sequence[NamedColumn[Any]], NamedColumn[Any], MapperProperty[Any]
- ],
- ]
- declared_attr_reg: Dict[declared_attr[Any], Any]
-
- @classmethod
- def setup_mapping(
- cls,
- registry: _RegistryType,
- cls_: Type[_O],
- dict_: _ClassDict,
- table: Optional[FromClause],
- mapper_kw: _MapperKwArgs,
- ) -> Optional[_MapperConfig]:
- manager = attributes.opt_manager_of_class(cls)
- if manager and manager.class_ is cls_:
- raise exc.InvalidRequestError(
- f"Class {cls!r} already has been instrumented declaratively"
- )
-
- if cls_.__dict__.get("__abstract__", False):
- return None
-
- defer_map = _get_immediate_cls_attr(
- cls_, "_sa_decl_prepare_nocascade", strict=True
- ) or hasattr(cls_, "_sa_decl_prepare")
-
- if defer_map:
- return _DeferredMapperConfig(
- registry, cls_, dict_, table, mapper_kw
- )
- else:
- return _ClassScanMapperConfig(
- registry, cls_, dict_, table, mapper_kw
- )
-
- def __init__(
- self,
- registry: _RegistryType,
- cls_: Type[Any],
- mapper_kw: _MapperKwArgs,
- ):
- self.cls = util.assert_arg_type(cls_, type, "cls_")
- self.classname = cls_.__name__
- self.properties = util.OrderedDict()
- self.declared_attr_reg = {}
-
- if not mapper_kw.get("non_primary", False):
- instrumentation.register_class(
- self.cls,
- finalize=False,
- registry=registry,
- declarative_scan=self,
- init_method=registry.constructor,
- )
- else:
- manager = attributes.opt_manager_of_class(self.cls)
- if not manager or not manager.is_mapped:
- raise exc.InvalidRequestError(
- "Class %s has no primary mapper configured. Configure "
- "a primary mapper first before setting up a non primary "
- "Mapper." % self.cls
- )
-
- def set_cls_attribute(self, attrname: str, value: _T) -> _T:
- manager = instrumentation.manager_of_class(self.cls)
- manager.install_member(attrname, value)
- return value
-
- def map(self, mapper_kw: _MapperKwArgs = ...) -> Mapper[Any]:
- raise NotImplementedError()
-
- def _early_mapping(self, mapper_kw: _MapperKwArgs) -> None:
- self.map(mapper_kw)
-
-
-class _ImperativeMapperConfig(_MapperConfig):
- __slots__ = ("local_table", "inherits")
-
- def __init__(
- self,
- registry: _RegistryType,
- cls_: Type[_O],
- table: Optional[FromClause],
- mapper_kw: _MapperKwArgs,
- ):
- super().__init__(registry, cls_, mapper_kw)
-
- self.local_table = self.set_cls_attribute("__table__", table)
-
- with mapperlib._CONFIGURE_MUTEX:
- if not mapper_kw.get("non_primary", False):
- clsregistry.add_class(
- self.classname, self.cls, registry._class_registry
- )
-
- self._setup_inheritance(mapper_kw)
-
- self._early_mapping(mapper_kw)
-
- def map(self, mapper_kw: _MapperKwArgs = util.EMPTY_DICT) -> Mapper[Any]:
- mapper_cls = Mapper
-
- return self.set_cls_attribute(
- "__mapper__",
- mapper_cls(self.cls, self.local_table, **mapper_kw),
- )
-
- def _setup_inheritance(self, mapper_kw: _MapperKwArgs) -> None:
- cls = self.cls
-
- inherits = mapper_kw.get("inherits", None)
-
- if inherits is None:
- # since we search for classical mappings now, search for
- # multiple mapped bases as well and raise an error.
- inherits_search = []
- for base_ in cls.__bases__:
- c = _resolve_for_abstract_or_classical(base_)
- if c is None:
- continue
-
- if _is_supercls_for_inherits(c) and c not in inherits_search:
- inherits_search.append(c)
-
- if inherits_search:
- if len(inherits_search) > 1:
- raise exc.InvalidRequestError(
- "Class %s has multiple mapped bases: %r"
- % (cls, inherits_search)
- )
- inherits = inherits_search[0]
- elif isinstance(inherits, Mapper):
- inherits = inherits.class_
-
- self.inherits = inherits
-
-
-class _CollectedAnnotation(NamedTuple):
- raw_annotation: _AnnotationScanType
- mapped_container: Optional[Type[Mapped[Any]]]
- extracted_mapped_annotation: Union[Type[Any], str]
- is_dataclass: bool
- attr_value: Any
- originating_module: str
- originating_class: Type[Any]
-
-
-class _ClassScanMapperConfig(_MapperConfig):
- __slots__ = (
- "registry",
- "clsdict_view",
- "collected_attributes",
- "collected_annotations",
- "local_table",
- "persist_selectable",
- "declared_columns",
- "column_ordering",
- "column_copies",
- "table_args",
- "tablename",
- "mapper_args",
- "mapper_args_fn",
- "inherits",
- "single",
- "allow_dataclass_fields",
- "dataclass_setup_arguments",
- "is_dataclass_prior_to_mapping",
- "allow_unmapped_annotations",
- )
-
- is_deferred = False
- registry: _RegistryType
- clsdict_view: _ClassDict
- collected_annotations: Dict[str, _CollectedAnnotation]
- collected_attributes: Dict[str, Any]
- local_table: Optional[FromClause]
- persist_selectable: Optional[FromClause]
- declared_columns: util.OrderedSet[Column[Any]]
- column_ordering: Dict[Column[Any], int]
- column_copies: Dict[
- Union[MappedColumn[Any], Column[Any]],
- Union[MappedColumn[Any], Column[Any]],
- ]
- tablename: Optional[str]
- mapper_args: Mapping[str, Any]
- table_args: Optional[_TableArgsType]
- mapper_args_fn: Optional[Callable[[], Dict[str, Any]]]
- inherits: Optional[Type[Any]]
- single: bool
-
- is_dataclass_prior_to_mapping: bool
- allow_unmapped_annotations: bool
-
- dataclass_setup_arguments: Optional[_DataclassArguments]
- """if the class has SQLAlchemy native dataclass parameters, where
- we will turn the class into a dataclass within the declarative mapping
- process.
-
- """
-
- allow_dataclass_fields: bool
- """if true, look for dataclass-processed Field objects on the target
- class as well as superclasses and extract ORM mapping directives from
- the "metadata" attribute of each Field.
-
- if False, dataclass fields can still be used, however they won't be
- mapped.
-
- """
-
- def __init__(
- self,
- registry: _RegistryType,
- cls_: Type[_O],
- dict_: _ClassDict,
- table: Optional[FromClause],
- mapper_kw: _MapperKwArgs,
- ):
- # grab class dict before the instrumentation manager has been added.
- # reduces cycles
- self.clsdict_view = (
- util.immutabledict(dict_) if dict_ else util.EMPTY_DICT
- )
- super().__init__(registry, cls_, mapper_kw)
- self.registry = registry
- self.persist_selectable = None
-
- self.collected_attributes = {}
- self.collected_annotations = {}
- self.declared_columns = util.OrderedSet()
- self.column_ordering = {}
- self.column_copies = {}
- self.single = False
- self.dataclass_setup_arguments = dca = getattr(
- self.cls, "_sa_apply_dc_transforms", None
- )
-
- self.allow_unmapped_annotations = getattr(
- self.cls, "__allow_unmapped__", False
- ) or bool(self.dataclass_setup_arguments)
-
- self.is_dataclass_prior_to_mapping = cld = dataclasses.is_dataclass(
- cls_
- )
-
- sdk = _get_immediate_cls_attr(cls_, "__sa_dataclass_metadata_key__")
-
- # we don't want to consume Field objects from a not-already-dataclass.
- # the Field objects won't have their "name" or "type" populated,
- # and while it seems like we could just set these on Field as we
- # read them, Field is documented as "user read only" and we need to
- # stay far away from any off-label use of dataclasses APIs.
- if (not cld or dca) and sdk:
- raise exc.InvalidRequestError(
- "SQLAlchemy mapped dataclasses can't consume mapping "
- "information from dataclass.Field() objects if the immediate "
- "class is not already a dataclass."
- )
-
- # if already a dataclass, and __sa_dataclass_metadata_key__ present,
- # then also look inside of dataclass.Field() objects yielded by
- # dataclasses.get_fields(cls) when scanning for attributes
- self.allow_dataclass_fields = bool(sdk and cld)
-
- self._setup_declared_events()
-
- self._scan_attributes()
-
- self._setup_dataclasses_transforms()
-
- with mapperlib._CONFIGURE_MUTEX:
- clsregistry.add_class(
- self.classname, self.cls, registry._class_registry
- )
-
- self._setup_inheriting_mapper(mapper_kw)
-
- self._extract_mappable_attributes()
-
- self._extract_declared_columns()
-
- self._setup_table(table)
-
- self._setup_inheriting_columns(mapper_kw)
-
- self._early_mapping(mapper_kw)
-
- def _setup_declared_events(self) -> None:
- if _get_immediate_cls_attr(self.cls, "__declare_last__"):
-
- @event.listens_for(Mapper, "after_configured")
- def after_configured() -> None:
- cast(
- "_DeclMappedClassProtocol[Any]", self.cls
- ).__declare_last__()
-
- if _get_immediate_cls_attr(self.cls, "__declare_first__"):
-
- @event.listens_for(Mapper, "before_configured")
- def before_configured() -> None:
- cast(
- "_DeclMappedClassProtocol[Any]", self.cls
- ).__declare_first__()
-
- def _cls_attr_override_checker(
- self, cls: Type[_O]
- ) -> Callable[[str, Any], bool]:
- """Produce a function that checks if a class has overridden an
- attribute, taking SQLAlchemy-enabled dataclass fields into account.
-
- """
-
- if self.allow_dataclass_fields:
- sa_dataclass_metadata_key = _get_immediate_cls_attr(
- cls, "__sa_dataclass_metadata_key__"
- )
- else:
- sa_dataclass_metadata_key = None
-
- if not sa_dataclass_metadata_key:
-
- def attribute_is_overridden(key: str, obj: Any) -> bool:
- return getattr(cls, key, obj) is not obj
-
- else:
- all_datacls_fields = {
- f.name: f.metadata[sa_dataclass_metadata_key]
- for f in util.dataclass_fields(cls)
- if sa_dataclass_metadata_key in f.metadata
- }
- local_datacls_fields = {
- f.name: f.metadata[sa_dataclass_metadata_key]
- for f in util.local_dataclass_fields(cls)
- if sa_dataclass_metadata_key in f.metadata
- }
-
- absent = object()
-
- def attribute_is_overridden(key: str, obj: Any) -> bool:
- if _is_declarative_props(obj):
- obj = obj.fget
-
- # this function likely has some failure modes still if
- # someone is doing a deep mixing of the same attribute
- # name as plain Python attribute vs. dataclass field.
-
- ret = local_datacls_fields.get(key, absent)
- if _is_declarative_props(ret):
- ret = ret.fget
-
- if ret is obj:
- return False
- elif ret is not absent:
- return True
-
- all_field = all_datacls_fields.get(key, absent)
-
- ret = getattr(cls, key, obj)
-
- if ret is obj:
- return False
-
- # for dataclasses, this could be the
- # 'default' of the field. so filter more specifically
- # for an already-mapped InstrumentedAttribute
- if ret is not absent and isinstance(
- ret, InstrumentedAttribute
- ):
- return True
-
- if all_field is obj:
- return False
- elif all_field is not absent:
- return True
-
- # can't find another attribute
- return False
-
- return attribute_is_overridden
-
- _include_dunders = {
- "__table__",
- "__mapper_args__",
- "__tablename__",
- "__table_args__",
- }
-
- _match_exclude_dunders = re.compile(r"^(?:_sa_|__)")
-
- def _cls_attr_resolver(
- self, cls: Type[Any]
- ) -> Callable[[], Iterable[Tuple[str, Any, Any, bool]]]:
- """produce a function to iterate the "attributes" of a class
- which we want to consider for mapping, adjusting for SQLAlchemy fields
- embedded in dataclass fields.
-
- """
- cls_annotations = util.get_annotations(cls)
-
- cls_vars = vars(cls)
-
- _include_dunders = self._include_dunders
- _match_exclude_dunders = self._match_exclude_dunders
-
- names = [
- n
- for n in util.merge_lists_w_ordering(
- list(cls_vars), list(cls_annotations)
- )
- if not _match_exclude_dunders.match(n) or n in _include_dunders
- ]
-
- if self.allow_dataclass_fields:
- sa_dataclass_metadata_key: Optional[str] = _get_immediate_cls_attr(
- cls, "__sa_dataclass_metadata_key__"
- )
- else:
- sa_dataclass_metadata_key = None
-
- if not sa_dataclass_metadata_key:
-
- def local_attributes_for_class() -> (
- Iterable[Tuple[str, Any, Any, bool]]
- ):
- return (
- (
- name,
- cls_vars.get(name),
- cls_annotations.get(name),
- False,
- )
- for name in names
- )
-
- else:
- dataclass_fields = {
- field.name: field for field in util.local_dataclass_fields(cls)
- }
-
- fixed_sa_dataclass_metadata_key = sa_dataclass_metadata_key
-
- def local_attributes_for_class() -> (
- Iterable[Tuple[str, Any, Any, bool]]
- ):
- for name in names:
- field = dataclass_fields.get(name, None)
- if field and sa_dataclass_metadata_key in field.metadata:
- yield field.name, _as_dc_declaredattr(
- field.metadata, fixed_sa_dataclass_metadata_key
- ), cls_annotations.get(field.name), True
- else:
- yield name, cls_vars.get(name), cls_annotations.get(
- name
- ), False
-
- return local_attributes_for_class
-
- def _scan_attributes(self) -> None:
- cls = self.cls
-
- cls_as_Decl = cast("_DeclMappedClassProtocol[Any]", cls)
-
- clsdict_view = self.clsdict_view
- collected_attributes = self.collected_attributes
- column_copies = self.column_copies
- _include_dunders = self._include_dunders
- mapper_args_fn = None
- table_args = inherited_table_args = None
-
- tablename = None
- fixed_table = "__table__" in clsdict_view
-
- attribute_is_overridden = self._cls_attr_override_checker(self.cls)
-
- bases = []
-
- for base in cls.__mro__:
- # collect bases and make sure standalone columns are copied
- # to be the column they will ultimately be on the class,
- # so that declared_attr functions use the right columns.
- # need to do this all the way up the hierarchy first
- # (see #8190)
-
- class_mapped = base is not cls and _is_supercls_for_inherits(base)
-
- local_attributes_for_class = self._cls_attr_resolver(base)
-
- if not class_mapped and base is not cls:
- locally_collected_columns = self._produce_column_copies(
- local_attributes_for_class,
- attribute_is_overridden,
- fixed_table,
- base,
- )
- else:
- locally_collected_columns = {}
-
- bases.append(
- (
- base,
- class_mapped,
- local_attributes_for_class,
- locally_collected_columns,
- )
- )
-
- for (
- base,
- class_mapped,
- local_attributes_for_class,
- locally_collected_columns,
- ) in bases:
- # this transfer can also take place as we scan each name
- # for finer-grained control of how collected_attributes is
- # populated, as this is what impacts column ordering.
- # however it's simpler to get it out of the way here.
- collected_attributes.update(locally_collected_columns)
-
- for (
- name,
- obj,
- annotation,
- is_dataclass_field,
- ) in local_attributes_for_class():
- if name in _include_dunders:
- if name == "__mapper_args__":
- check_decl = _check_declared_props_nocascade(
- obj, name, cls
- )
- if not mapper_args_fn and (
- not class_mapped or check_decl
- ):
- # don't even invoke __mapper_args__ until
- # after we've determined everything about the
- # mapped table.
- # make a copy of it so a class-level dictionary
- # is not overwritten when we update column-based
- # arguments.
- def _mapper_args_fn() -> Dict[str, Any]:
- return dict(cls_as_Decl.__mapper_args__)
-
- mapper_args_fn = _mapper_args_fn
-
- elif name == "__tablename__":
- check_decl = _check_declared_props_nocascade(
- obj, name, cls
- )
- if not tablename and (not class_mapped or check_decl):
- tablename = cls_as_Decl.__tablename__
- elif name == "__table_args__":
- check_decl = _check_declared_props_nocascade(
- obj, name, cls
- )
- if not table_args and (not class_mapped or check_decl):
- table_args = cls_as_Decl.__table_args__
- if not isinstance(
- table_args, (tuple, dict, type(None))
- ):
- raise exc.ArgumentError(
- "__table_args__ value must be a tuple, "
- "dict, or None"
- )
- if base is not cls:
- inherited_table_args = True
- else:
- # skip all other dunder names, which at the moment
- # should only be __table__
- continue
- elif class_mapped:
- if _is_declarative_props(obj) and not obj._quiet:
- util.warn(
- "Regular (i.e. not __special__) "
- "attribute '%s.%s' uses @declared_attr, "
- "but owning class %s is mapped - "
- "not applying to subclass %s."
- % (base.__name__, name, base, cls)
- )
-
- continue
- elif base is not cls:
- # we're a mixin, abstract base, or something that is
- # acting like that for now.
-
- if isinstance(obj, (Column, MappedColumn)):
- # already copied columns to the mapped class.
- continue
- elif isinstance(obj, MapperProperty):
- raise exc.InvalidRequestError(
- "Mapper properties (i.e. deferred,"
- "column_property(), relationship(), etc.) must "
- "be declared as @declared_attr callables "
- "on declarative mixin classes. For dataclass "
- "field() objects, use a lambda:"
- )
- elif _is_declarative_props(obj):
- # tried to get overloads to tell this to
- # pylance, no luck
- assert obj is not None
-
- if obj._cascading:
- if name in clsdict_view:
- # unfortunately, while we can use the user-
- # defined attribute here to allow a clean
- # override, if there's another
- # subclass below then it still tries to use
- # this. not sure if there is enough
- # information here to add this as a feature
- # later on.
- util.warn(
- "Attribute '%s' on class %s cannot be "
- "processed due to "
- "@declared_attr.cascading; "
- "skipping" % (name, cls)
- )
- collected_attributes[name] = column_copies[obj] = (
- ret
- ) = obj.__get__(obj, cls)
- setattr(cls, name, ret)
- else:
- if is_dataclass_field:
- # access attribute using normal class access
- # first, to see if it's been mapped on a
- # superclass. note if the dataclasses.field()
- # has "default", this value can be anything.
- ret = getattr(cls, name, None)
-
- # so, if it's anything that's not ORM
- # mapped, assume we should invoke the
- # declared_attr
- if not isinstance(ret, InspectionAttr):
- ret = obj.fget()
- else:
- # access attribute using normal class access.
- # if the declared attr already took place
- # on a superclass that is mapped, then
- # this is no longer a declared_attr, it will
- # be the InstrumentedAttribute
- ret = getattr(cls, name)
-
- # correct for proxies created from hybrid_property
- # or similar. note there is no known case that
- # produces nested proxies, so we are only
- # looking one level deep right now.
-
- if (
- isinstance(ret, InspectionAttr)
- and attr_is_internal_proxy(ret)
- and not isinstance(
- ret.original_property, MapperProperty
- )
- ):
- ret = ret.descriptor
-
- collected_attributes[name] = column_copies[obj] = (
- ret
- )
-
- if (
- isinstance(ret, (Column, MapperProperty))
- and ret.doc is None
- ):
- ret.doc = obj.__doc__
-
- self._collect_annotation(
- name,
- obj._collect_return_annotation(),
- base,
- True,
- obj,
- )
- elif _is_mapped_annotation(annotation, cls, base):
- # Mapped annotation without any object.
- # product_column_copies should have handled this.
- # if future support for other MapperProperty,
- # then test if this name is already handled and
- # otherwise proceed to generate.
- if not fixed_table:
- assert (
- name in collected_attributes
- or attribute_is_overridden(name, None)
- )
- continue
- else:
- # here, the attribute is some other kind of
- # property that we assume is not part of the
- # declarative mapping. however, check for some
- # more common mistakes
- self._warn_for_decl_attributes(base, name, obj)
- elif is_dataclass_field and (
- name not in clsdict_view or clsdict_view[name] is not obj
- ):
- # here, we are definitely looking at the target class
- # and not a superclass. this is currently a
- # dataclass-only path. if the name is only
- # a dataclass field and isn't in local cls.__dict__,
- # put the object there.
- # assert that the dataclass-enabled resolver agrees
- # with what we are seeing
-
- assert not attribute_is_overridden(name, obj)
-
- if _is_declarative_props(obj):
- obj = obj.fget()
-
- collected_attributes[name] = obj
- self._collect_annotation(
- name, annotation, base, False, obj
- )
- else:
- collected_annotation = self._collect_annotation(
- name, annotation, base, None, obj
- )
- is_mapped = (
- collected_annotation is not None
- and collected_annotation.mapped_container is not None
- )
- generated_obj = (
- collected_annotation.attr_value
- if collected_annotation is not None
- else obj
- )
- if obj is None and not fixed_table and is_mapped:
- collected_attributes[name] = (
- generated_obj
- if generated_obj is not None
- else MappedColumn()
- )
- elif name in clsdict_view:
- collected_attributes[name] = obj
- # else if the name is not in the cls.__dict__,
- # don't collect it as an attribute.
- # we will see the annotation only, which is meaningful
- # both for mapping and dataclasses setup
-
- if inherited_table_args and not tablename:
- table_args = None
-
- self.table_args = table_args
- self.tablename = tablename
- self.mapper_args_fn = mapper_args_fn
-
- def _setup_dataclasses_transforms(self) -> None:
- dataclass_setup_arguments = self.dataclass_setup_arguments
- if not dataclass_setup_arguments:
- return
-
- # can't use is_dataclass since it uses hasattr
- if "__dataclass_fields__" in self.cls.__dict__:
- raise exc.InvalidRequestError(
- f"Class {self.cls} is already a dataclass; ensure that "
- "base classes / decorator styles of establishing dataclasses "
- "are not being mixed. "
- "This can happen if a class that inherits from "
- "'MappedAsDataclass', even indirectly, is been mapped with "
- "'@registry.mapped_as_dataclass'"
- )
-
- warn_for_non_dc_attrs = collections.defaultdict(list)
-
- def _allow_dataclass_field(
- key: str, originating_class: Type[Any]
- ) -> bool:
- if (
- originating_class is not self.cls
- and "__dataclass_fields__" not in originating_class.__dict__
- ):
- warn_for_non_dc_attrs[originating_class].append(key)
-
- return True
-
- manager = instrumentation.manager_of_class(self.cls)
- assert manager is not None
-
- field_list = [
- _AttributeOptions._get_arguments_for_make_dataclass(
- key,
- anno,
- mapped_container,
- self.collected_attributes.get(key, _NoArg.NO_ARG),
- )
- for key, anno, mapped_container in (
- (
- key,
- mapped_anno if mapped_anno else raw_anno,
- mapped_container,
- )
- for key, (
- raw_anno,
- mapped_container,
- mapped_anno,
- is_dc,
- attr_value,
- originating_module,
- originating_class,
- ) in self.collected_annotations.items()
- if _allow_dataclass_field(key, originating_class)
- and (
- key not in self.collected_attributes
- # issue #9226; check for attributes that we've collected
- # which are already instrumented, which we would assume
- # mean we are in an ORM inheritance mapping and this
- # attribute is already mapped on the superclass. Under
- # no circumstance should any QueryableAttribute be sent to
- # the dataclass() function; anything that's mapped should
- # be Field and that's it
- or not isinstance(
- self.collected_attributes[key], QueryableAttribute
- )
- )
- )
- ]
-
- if warn_for_non_dc_attrs:
- for (
- originating_class,
- non_dc_attrs,
- ) in warn_for_non_dc_attrs.items():
- util.warn_deprecated(
- f"When transforming {self.cls} to a dataclass, "
- f"attribute(s) "
- f"{', '.join(repr(key) for key in non_dc_attrs)} "
- f"originates from superclass "
- f"{originating_class}, which is not a dataclass. This "
- f"usage is deprecated and will raise an error in "
- f"SQLAlchemy 2.1. When declaring SQLAlchemy Declarative "
- f"Dataclasses, ensure that all mixin classes and other "
- f"superclasses which include attributes are also a "
- f"subclass of MappedAsDataclass.",
- "2.0",
- code="dcmx",
- )
-
- annotations = {}
- defaults = {}
- for item in field_list:
- if len(item) == 2:
- name, tp = item
- elif len(item) == 3:
- name, tp, spec = item
- defaults[name] = spec
- else:
- assert False
- annotations[name] = tp
-
- for k, v in defaults.items():
- setattr(self.cls, k, v)
-
- self._apply_dataclasses_to_any_class(
- dataclass_setup_arguments, self.cls, annotations
- )
-
- @classmethod
- def _update_annotations_for_non_mapped_class(
- cls, klass: Type[_O]
- ) -> Mapping[str, _AnnotationScanType]:
- cls_annotations = util.get_annotations(klass)
-
- new_anno = {}
- for name, annotation in cls_annotations.items():
- if _is_mapped_annotation(annotation, klass, klass):
- extracted = _extract_mapped_subtype(
- annotation,
- klass,
- klass.__module__,
- name,
- type(None),
- required=False,
- is_dataclass_field=False,
- expect_mapped=False,
- )
- if extracted:
- inner, _ = extracted
- new_anno[name] = inner
- else:
- new_anno[name] = annotation
- return new_anno
-
- @classmethod
- def _apply_dataclasses_to_any_class(
- cls,
- dataclass_setup_arguments: _DataclassArguments,
- klass: Type[_O],
- use_annotations: Mapping[str, _AnnotationScanType],
- ) -> None:
- cls._assert_dc_arguments(dataclass_setup_arguments)
-
- dataclass_callable = dataclass_setup_arguments["dataclass_callable"]
- if dataclass_callable is _NoArg.NO_ARG:
- dataclass_callable = dataclasses.dataclass
-
- restored: Optional[Any]
-
- if use_annotations:
- # apply constructed annotations that should look "normal" to a
- # dataclasses callable, based on the fields present. This
- # means remove the Mapped[] container and ensure all Field
- # entries have an annotation
- restored = getattr(klass, "__annotations__", None)
- klass.__annotations__ = cast("Dict[str, Any]", use_annotations)
- else:
- restored = None
-
- try:
- dataclass_callable(
- klass,
- **{
- k: v
- for k, v in dataclass_setup_arguments.items()
- if v is not _NoArg.NO_ARG and k != "dataclass_callable"
- },
- )
- except (TypeError, ValueError) as ex:
- raise exc.InvalidRequestError(
- f"Python dataclasses error encountered when creating "
- f"dataclass for {klass.__name__!r}: "
- f"{ex!r}. Please refer to Python dataclasses "
- "documentation for additional information.",
- code="dcte",
- ) from ex
- finally:
- # restore original annotations outside of the dataclasses
- # process; for mixins and __abstract__ superclasses, SQLAlchemy
- # Declarative will need to see the Mapped[] container inside the
- # annotations in order to map subclasses
- if use_annotations:
- if restored is None:
- del klass.__annotations__
- else:
- klass.__annotations__ = restored
-
- @classmethod
- def _assert_dc_arguments(cls, arguments: _DataclassArguments) -> None:
- allowed = {
- "init",
- "repr",
- "order",
- "eq",
- "unsafe_hash",
- "kw_only",
- "match_args",
- "dataclass_callable",
- }
- disallowed_args = set(arguments).difference(allowed)
- if disallowed_args:
- msg = ", ".join(f"{arg!r}" for arg in sorted(disallowed_args))
- raise exc.ArgumentError(
- f"Dataclass argument(s) {msg} are not accepted"
- )
-
- def _collect_annotation(
- self,
- name: str,
- raw_annotation: _AnnotationScanType,
- originating_class: Type[Any],
- expect_mapped: Optional[bool],
- attr_value: Any,
- ) -> Optional[_CollectedAnnotation]:
- if name in self.collected_annotations:
- return self.collected_annotations[name]
-
- if raw_annotation is None:
- return None
-
- is_dataclass = self.is_dataclass_prior_to_mapping
- allow_unmapped = self.allow_unmapped_annotations
-
- if expect_mapped is None:
- is_dataclass_field = isinstance(attr_value, dataclasses.Field)
- expect_mapped = (
- not is_dataclass_field
- and not allow_unmapped
- and (
- attr_value is None
- or isinstance(attr_value, _MappedAttribute)
- )
- )
- else:
- is_dataclass_field = False
-
- is_dataclass_field = False
- extracted = _extract_mapped_subtype(
- raw_annotation,
- self.cls,
- originating_class.__module__,
- name,
- type(attr_value),
- required=False,
- is_dataclass_field=is_dataclass_field,
- expect_mapped=expect_mapped
- and not is_dataclass, # self.allow_dataclass_fields,
- )
-
- if extracted is None:
- # ClassVar can come out here
- return None
-
- extracted_mapped_annotation, mapped_container = extracted
-
- if attr_value is None and not is_literal(extracted_mapped_annotation):
- for elem in typing_get_args(extracted_mapped_annotation):
- if isinstance(elem, str) or is_fwd_ref(
- elem, check_generic=True
- ):
- elem = de_stringify_annotation(
- self.cls,
- elem,
- originating_class.__module__,
- include_generic=True,
- )
- # look in Annotated[...] for an ORM construct,
- # such as Annotated[int, mapped_column(primary_key=True)]
- if isinstance(elem, _IntrospectsAnnotations):
- attr_value = elem.found_in_pep593_annotated()
-
- self.collected_annotations[name] = ca = _CollectedAnnotation(
- raw_annotation,
- mapped_container,
- extracted_mapped_annotation,
- is_dataclass,
- attr_value,
- originating_class.__module__,
- originating_class,
- )
- return ca
-
- def _warn_for_decl_attributes(
- self, cls: Type[Any], key: str, c: Any
- ) -> None:
- if isinstance(c, expression.ColumnElement):
- util.warn(
- f"Attribute '{key}' on class {cls} appears to "
- "be a non-schema SQLAlchemy expression "
- "object; this won't be part of the declarative mapping. "
- "To map arbitrary expressions, use ``column_property()`` "
- "or a similar function such as ``deferred()``, "
- "``query_expression()`` etc. "
- )
-
- def _produce_column_copies(
- self,
- attributes_for_class: Callable[
- [], Iterable[Tuple[str, Any, Any, bool]]
- ],
- attribute_is_overridden: Callable[[str, Any], bool],
- fixed_table: bool,
- originating_class: Type[Any],
- ) -> Dict[str, Union[Column[Any], MappedColumn[Any]]]:
- cls = self.cls
- dict_ = self.clsdict_view
- locally_collected_attributes = {}
- column_copies = self.column_copies
- # copy mixin columns to the mapped class
-
- for name, obj, annotation, is_dataclass in attributes_for_class():
- if (
- not fixed_table
- and obj is None
- and _is_mapped_annotation(annotation, cls, originating_class)
- ):
- # obj is None means this is the annotation only path
-
- if attribute_is_overridden(name, obj):
- # perform same "overridden" check as we do for
- # Column/MappedColumn, this is how a mixin col is not
- # applied to an inherited subclass that does not have
- # the mixin. the anno-only path added here for
- # #9564
- continue
-
- collected_annotation = self._collect_annotation(
- name, annotation, originating_class, True, obj
- )
- obj = (
- collected_annotation.attr_value
- if collected_annotation is not None
- else obj
- )
- if obj is None:
- obj = MappedColumn()
-
- locally_collected_attributes[name] = obj
- setattr(cls, name, obj)
-
- elif isinstance(obj, (Column, MappedColumn)):
- if attribute_is_overridden(name, obj):
- # if column has been overridden
- # (like by the InstrumentedAttribute of the
- # superclass), skip. don't collect the annotation
- # either (issue #8718)
- continue
-
- collected_annotation = self._collect_annotation(
- name, annotation, originating_class, True, obj
- )
- obj = (
- collected_annotation.attr_value
- if collected_annotation is not None
- else obj
- )
-
- if name not in dict_ and not (
- "__table__" in dict_
- and (getattr(obj, "name", None) or name)
- in dict_["__table__"].c
- ):
- if obj.foreign_keys:
- for fk in obj.foreign_keys:
- if (
- fk._table_column is not None
- and fk._table_column.table is None
- ):
- raise exc.InvalidRequestError(
- "Columns with foreign keys to "
- "non-table-bound "
- "columns must be declared as "
- "@declared_attr callables "
- "on declarative mixin classes. "
- "For dataclass "
- "field() objects, use a lambda:."
- )
-
- column_copies[obj] = copy_ = obj._copy()
-
- locally_collected_attributes[name] = copy_
- setattr(cls, name, copy_)
-
- return locally_collected_attributes
-
- def _extract_mappable_attributes(self) -> None:
- cls = self.cls
- collected_attributes = self.collected_attributes
-
- our_stuff = self.properties
-
- _include_dunders = self._include_dunders
-
- late_mapped = _get_immediate_cls_attr(
- cls, "_sa_decl_prepare_nocascade", strict=True
- )
-
- allow_unmapped_annotations = self.allow_unmapped_annotations
- expect_annotations_wo_mapped = (
- allow_unmapped_annotations or self.is_dataclass_prior_to_mapping
- )
-
- look_for_dataclass_things = bool(self.dataclass_setup_arguments)
-
- for k in list(collected_attributes):
- if k in _include_dunders:
- continue
-
- value = collected_attributes[k]
-
- if _is_declarative_props(value):
- # @declared_attr in collected_attributes only occurs here for a
- # @declared_attr that's directly on the mapped class;
- # for a mixin, these have already been evaluated
- if value._cascading:
- util.warn(
- "Use of @declared_attr.cascading only applies to "
- "Declarative 'mixin' and 'abstract' classes. "
- "Currently, this flag is ignored on mapped class "
- "%s" % self.cls
- )
-
- value = getattr(cls, k)
-
- elif (
- isinstance(value, QueryableAttribute)
- and value.class_ is not cls
- and value.key != k
- ):
- # detect a QueryableAttribute that's already mapped being
- # assigned elsewhere in userland, turn into a synonym()
- value = SynonymProperty(value.key)
- setattr(cls, k, value)
-
- if (
- isinstance(value, tuple)
- and len(value) == 1
- and isinstance(value[0], (Column, _MappedAttribute))
- ):
- util.warn(
- "Ignoring declarative-like tuple value of attribute "
- "'%s': possibly a copy-and-paste error with a comma "
- "accidentally placed at the end of the line?" % k
- )
- continue
- elif look_for_dataclass_things and isinstance(
- value, dataclasses.Field
- ):
- # we collected a dataclass Field; dataclasses would have
- # set up the correct state on the class
- continue
- elif not isinstance(value, (Column, _DCAttributeOptions)):
- # using @declared_attr for some object that
- # isn't Column/MapperProperty/_DCAttributeOptions; remove
- # from the clsdict_view
- # and place the evaluated value onto the class.
- collected_attributes.pop(k)
- self._warn_for_decl_attributes(cls, k, value)
- if not late_mapped:
- setattr(cls, k, value)
- continue
- # we expect to see the name 'metadata' in some valid cases;
- # however at this point we see it's assigned to something trying
- # to be mapped, so raise for that.
- # TODO: should "registry" here be also? might be too late
- # to change that now (2.0 betas)
- elif k in ("metadata",):
- raise exc.InvalidRequestError(
- f"Attribute name '{k}' is reserved when using the "
- "Declarative API."
- )
- elif isinstance(value, Column):
- _undefer_column_name(
- k, self.column_copies.get(value, value) # type: ignore
- )
- else:
- if isinstance(value, _IntrospectsAnnotations):
- (
- annotation,
- mapped_container,
- extracted_mapped_annotation,
- is_dataclass,
- attr_value,
- originating_module,
- originating_class,
- ) = self.collected_annotations.get(
- k, (None, None, None, False, None, None, None)
- )
-
- # issue #8692 - don't do any annotation interpretation if
- # an annotation were present and a container such as
- # Mapped[] etc. were not used. If annotation is None,
- # do declarative_scan so that the property can raise
- # for required
- if (
- mapped_container is not None
- or annotation is None
- # issue #10516: need to do declarative_scan even with
- # a non-Mapped annotation if we are doing
- # __allow_unmapped__, for things like col.name
- # assignment
- or allow_unmapped_annotations
- ):
- try:
- value.declarative_scan(
- self,
- self.registry,
- cls,
- originating_module,
- k,
- mapped_container,
- annotation,
- extracted_mapped_annotation,
- is_dataclass,
- )
- except NameError as ne:
- raise exc.ArgumentError(
- f"Could not resolve all types within mapped "
- f'annotation: "{annotation}". Ensure all '
- f"types are written correctly and are "
- f"imported within the module in use."
- ) from ne
- else:
- # assert that we were expecting annotations
- # without Mapped[] were going to be passed.
- # otherwise an error should have been raised
- # by util._extract_mapped_subtype before we got here.
- assert expect_annotations_wo_mapped
-
- if isinstance(value, _DCAttributeOptions):
- if (
- value._has_dataclass_arguments
- and not look_for_dataclass_things
- ):
- if isinstance(value, MapperProperty):
- argnames = [
- "init",
- "default_factory",
- "repr",
- "default",
- ]
- else:
- argnames = ["init", "default_factory", "repr"]
-
- args = {
- a
- for a in argnames
- if getattr(
- value._attribute_options, f"dataclasses_{a}"
- )
- is not _NoArg.NO_ARG
- }
-
- raise exc.ArgumentError(
- f"Attribute '{k}' on class {cls} includes "
- f"dataclasses argument(s): "
- f"{', '.join(sorted(repr(a) for a in args))} but "
- f"class does not specify "
- "SQLAlchemy native dataclass configuration."
- )
-
- if not isinstance(value, (MapperProperty, _MapsColumns)):
- # filter for _DCAttributeOptions objects that aren't
- # MapperProperty / mapped_column(). Currently this
- # includes AssociationProxy. pop it from the things
- # we're going to map and set it up as a descriptor
- # on the class.
- collected_attributes.pop(k)
-
- # Assoc Prox (or other descriptor object that may
- # use _DCAttributeOptions) is usually here, except if
- # 1. we're a
- # dataclass, dataclasses would have removed the
- # attr here or 2. assoc proxy is coming from a
- # superclass, we want it to be direct here so it
- # tracks state or 3. assoc prox comes from
- # declared_attr, uncommon case
- setattr(cls, k, value)
- continue
-
- our_stuff[k] = value
-
- def _extract_declared_columns(self) -> None:
- our_stuff = self.properties
-
- # extract columns from the class dict
- declared_columns = self.declared_columns
- column_ordering = self.column_ordering
- name_to_prop_key = collections.defaultdict(set)
-
- for key, c in list(our_stuff.items()):
- if isinstance(c, _MapsColumns):
- mp_to_assign = c.mapper_property_to_assign
- if mp_to_assign:
- our_stuff[key] = mp_to_assign
- else:
- # if no mapper property to assign, this currently means
- # this is a MappedColumn that will produce a Column for us
- del our_stuff[key]
-
- for col, sort_order in c.columns_to_assign:
- if not isinstance(c, CompositeProperty):
- name_to_prop_key[col.name].add(key)
- declared_columns.add(col)
-
- # we would assert this, however we want the below
- # warning to take effect instead. See #9630
- # assert col not in column_ordering
-
- column_ordering[col] = sort_order
-
- # if this is a MappedColumn and the attribute key we
- # have is not what the column has for its key, map the
- # Column explicitly under the attribute key name.
- # otherwise, Mapper will map it under the column key.
- if mp_to_assign is None and key != col.key:
- our_stuff[key] = col
- elif isinstance(c, Column):
- # undefer previously occurred here, and now occurs earlier.
- # ensure every column we get here has been named
- assert c.name is not None
- name_to_prop_key[c.name].add(key)
- declared_columns.add(c)
- # if the column is the same name as the key,
- # remove it from the explicit properties dict.
- # the normal rules for assigning column-based properties
- # will take over, including precedence of columns
- # in multi-column ColumnProperties.
- if key == c.key:
- del our_stuff[key]
-
- for name, keys in name_to_prop_key.items():
- if len(keys) > 1:
- util.warn(
- "On class %r, Column object %r named "
- "directly multiple times, "
- "only one will be used: %s. "
- "Consider using orm.synonym instead"
- % (self.classname, name, (", ".join(sorted(keys))))
- )
-
- def _setup_table(self, table: Optional[FromClause] = None) -> None:
- cls = self.cls
- cls_as_Decl = cast("MappedClassProtocol[Any]", cls)
-
- tablename = self.tablename
- table_args = self.table_args
- clsdict_view = self.clsdict_view
- declared_columns = self.declared_columns
- column_ordering = self.column_ordering
-
- manager = attributes.manager_of_class(cls)
-
- if "__table__" not in clsdict_view and table is None:
- if hasattr(cls, "__table_cls__"):
- table_cls = cast(
- Type[Table],
- util.unbound_method_to_callable(cls.__table_cls__), # type: ignore # noqa: E501
- )
- else:
- table_cls = Table
-
- if tablename is not None:
- args: Tuple[Any, ...] = ()
- table_kw: Dict[str, Any] = {}
-
- if table_args:
- if isinstance(table_args, dict):
- table_kw = table_args
- elif isinstance(table_args, tuple):
- if isinstance(table_args[-1], dict):
- args, table_kw = table_args[0:-1], table_args[-1]
- else:
- args = table_args
-
- autoload_with = clsdict_view.get("__autoload_with__")
- if autoload_with:
- table_kw["autoload_with"] = autoload_with
-
- autoload = clsdict_view.get("__autoload__")
- if autoload:
- table_kw["autoload"] = True
-
- sorted_columns = sorted(
- declared_columns,
- key=lambda c: column_ordering.get(c, 0),
- )
- table = self.set_cls_attribute(
- "__table__",
- table_cls(
- tablename,
- self._metadata_for_cls(manager),
- *sorted_columns,
- *args,
- **table_kw,
- ),
- )
- else:
- if table is None:
- table = cls_as_Decl.__table__
- if declared_columns:
- for c in declared_columns:
- if not table.c.contains_column(c):
- raise exc.ArgumentError(
- "Can't add additional column %r when "
- "specifying __table__" % c.key
- )
-
- self.local_table = table
-
- def _metadata_for_cls(self, manager: ClassManager[Any]) -> MetaData:
- meta: Optional[MetaData] = getattr(self.cls, "metadata", None)
- if meta is not None:
- return meta
- else:
- return manager.registry.metadata
-
- def _setup_inheriting_mapper(self, mapper_kw: _MapperKwArgs) -> None:
- cls = self.cls
-
- inherits = mapper_kw.get("inherits", None)
-
- if inherits is None:
- # since we search for classical mappings now, search for
- # multiple mapped bases as well and raise an error.
- inherits_search = []
- for base_ in cls.__bases__:
- c = _resolve_for_abstract_or_classical(base_)
- if c is None:
- continue
-
- if _is_supercls_for_inherits(c) and c not in inherits_search:
- inherits_search.append(c)
-
- if inherits_search:
- if len(inherits_search) > 1:
- raise exc.InvalidRequestError(
- "Class %s has multiple mapped bases: %r"
- % (cls, inherits_search)
- )
- inherits = inherits_search[0]
- elif isinstance(inherits, Mapper):
- inherits = inherits.class_
-
- self.inherits = inherits
-
- clsdict_view = self.clsdict_view
- if "__table__" not in clsdict_view and self.tablename is None:
- self.single = True
-
- def _setup_inheriting_columns(self, mapper_kw: _MapperKwArgs) -> None:
- table = self.local_table
- cls = self.cls
- table_args = self.table_args
- declared_columns = self.declared_columns
-
- if (
- table is None
- and self.inherits is None
- and not _get_immediate_cls_attr(cls, "__no_table__")
- ):
- raise exc.InvalidRequestError(
- "Class %r does not have a __table__ or __tablename__ "
- "specified and does not inherit from an existing "
- "table-mapped class." % cls
- )
- elif self.inherits:
- inherited_mapper_or_config = _declared_mapping_info(self.inherits)
- assert inherited_mapper_or_config is not None
- inherited_table = inherited_mapper_or_config.local_table
- inherited_persist_selectable = (
- inherited_mapper_or_config.persist_selectable
- )
-
- if table is None:
- # single table inheritance.
- # ensure no table args
- if table_args:
- raise exc.ArgumentError(
- "Can't place __table_args__ on an inherited class "
- "with no table."
- )
-
- # add any columns declared here to the inherited table.
- if declared_columns and not isinstance(inherited_table, Table):
- raise exc.ArgumentError(
- f"Can't declare columns on single-table-inherited "
- f"subclass {self.cls}; superclass {self.inherits} "
- "is not mapped to a Table"
- )
-
- for col in declared_columns:
- assert inherited_table is not None
- if col.name in inherited_table.c:
- if inherited_table.c[col.name] is col:
- continue
- raise exc.ArgumentError(
- f"Column '{col}' on class {cls.__name__} "
- f"conflicts with existing column "
- f"'{inherited_table.c[col.name]}'. If using "
- f"Declarative, consider using the "
- "use_existing_column parameter of mapped_column() "
- "to resolve conflicts."
- )
- if col.primary_key:
- raise exc.ArgumentError(
- "Can't place primary key columns on an inherited "
- "class with no table."
- )
-
- if TYPE_CHECKING:
- assert isinstance(inherited_table, Table)
-
- inherited_table.append_column(col)
- if (
- inherited_persist_selectable is not None
- and inherited_persist_selectable is not inherited_table
- ):
- inherited_persist_selectable._refresh_for_new_column(
- col
- )
-
- def _prepare_mapper_arguments(self, mapper_kw: _MapperKwArgs) -> None:
- properties = self.properties
-
- if self.mapper_args_fn:
- mapper_args = self.mapper_args_fn()
- else:
- mapper_args = {}
-
- if mapper_kw:
- mapper_args.update(mapper_kw)
-
- if "properties" in mapper_args:
- properties = dict(properties)
- properties.update(mapper_args["properties"])
-
- # make sure that column copies are used rather
- # than the original columns from any mixins
- for k in ("version_id_col", "polymorphic_on"):
- if k in mapper_args:
- v = mapper_args[k]
- mapper_args[k] = self.column_copies.get(v, v)
-
- if "primary_key" in mapper_args:
- mapper_args["primary_key"] = [
- self.column_copies.get(v, v)
- for v in util.to_list(mapper_args["primary_key"])
- ]
-
- if "inherits" in mapper_args:
- inherits_arg = mapper_args["inherits"]
- if isinstance(inherits_arg, Mapper):
- inherits_arg = inherits_arg.class_
-
- if inherits_arg is not self.inherits:
- raise exc.InvalidRequestError(
- "mapper inherits argument given for non-inheriting "
- "class %s" % (mapper_args["inherits"])
- )
-
- if self.inherits:
- mapper_args["inherits"] = self.inherits
-
- if self.inherits and not mapper_args.get("concrete", False):
- # note the superclass is expected to have a Mapper assigned and
- # not be a deferred config, as this is called within map()
- inherited_mapper = class_mapper(self.inherits, False)
- inherited_table = inherited_mapper.local_table
-
- # single or joined inheritance
- # exclude any cols on the inherited table which are
- # not mapped on the parent class, to avoid
- # mapping columns specific to sibling/nephew classes
- if "exclude_properties" not in mapper_args:
- mapper_args["exclude_properties"] = exclude_properties = {
- c.key
- for c in inherited_table.c
- if c not in inherited_mapper._columntoproperty
- }.union(inherited_mapper.exclude_properties or ())
- exclude_properties.difference_update(
- [c.key for c in self.declared_columns]
- )
-
- # look through columns in the current mapper that
- # are keyed to a propname different than the colname
- # (if names were the same, we'd have popped it out above,
- # in which case the mapper makes this combination).
- # See if the superclass has a similar column property.
- # If so, join them together.
- for k, col in list(properties.items()):
- if not isinstance(col, expression.ColumnElement):
- continue
- if k in inherited_mapper._props:
- p = inherited_mapper._props[k]
- if isinstance(p, ColumnProperty):
- # note here we place the subclass column
- # first. See [ticket:1892] for background.
- properties[k] = [col] + p.columns
- result_mapper_args = mapper_args.copy()
- result_mapper_args["properties"] = properties
- self.mapper_args = result_mapper_args
-
- def map(self, mapper_kw: _MapperKwArgs = util.EMPTY_DICT) -> Mapper[Any]:
- self._prepare_mapper_arguments(mapper_kw)
- if hasattr(self.cls, "__mapper_cls__"):
- mapper_cls = cast(
- "Type[Mapper[Any]]",
- util.unbound_method_to_callable(
- self.cls.__mapper_cls__ # type: ignore
- ),
- )
- else:
- mapper_cls = Mapper
-
- return self.set_cls_attribute(
- "__mapper__",
- mapper_cls(self.cls, self.local_table, **self.mapper_args),
- )
-
-
-@util.preload_module("sqlalchemy.orm.decl_api")
-def _as_dc_declaredattr(
- field_metadata: Mapping[str, Any], sa_dataclass_metadata_key: str
-) -> Any:
- # wrap lambdas inside dataclass fields inside an ad-hoc declared_attr.
- # we can't write it because field.metadata is immutable :( so we have
- # to go through extra trouble to compare these
- decl_api = util.preloaded.orm_decl_api
- obj = field_metadata[sa_dataclass_metadata_key]
- if callable(obj) and not isinstance(obj, decl_api.declared_attr):
- return decl_api.declared_attr(obj)
- else:
- return obj
-
-
-class _DeferredMapperConfig(_ClassScanMapperConfig):
- _cls: weakref.ref[Type[Any]]
-
- is_deferred = True
-
- _configs: util.OrderedDict[
- weakref.ref[Type[Any]], _DeferredMapperConfig
- ] = util.OrderedDict()
-
- def _early_mapping(self, mapper_kw: _MapperKwArgs) -> None:
- pass
-
- # mypy disallows plain property override of variable
- @property # type: ignore
- def cls(self) -> Type[Any]:
- return self._cls() # type: ignore
-
- @cls.setter
- def cls(self, class_: Type[Any]) -> None:
- self._cls = weakref.ref(class_, self._remove_config_cls)
- self._configs[self._cls] = self
-
- @classmethod
- def _remove_config_cls(cls, ref: weakref.ref[Type[Any]]) -> None:
- cls._configs.pop(ref, None)
-
- @classmethod
- def has_cls(cls, class_: Type[Any]) -> bool:
- # 2.6 fails on weakref if class_ is an old style class
- return isinstance(class_, type) and weakref.ref(class_) in cls._configs
-
- @classmethod
- def raise_unmapped_for_cls(cls, class_: Type[Any]) -> NoReturn:
- if hasattr(class_, "_sa_raise_deferred_config"):
- class_._sa_raise_deferred_config()
-
- raise orm_exc.UnmappedClassError(
- class_,
- msg=(
- f"Class {orm_exc._safe_cls_name(class_)} has a deferred "
- "mapping on it. It is not yet usable as a mapped class."
- ),
- )
-
- @classmethod
- def config_for_cls(cls, class_: Type[Any]) -> _DeferredMapperConfig:
- return cls._configs[weakref.ref(class_)]
-
- @classmethod
- def classes_for_base(
- cls, base_cls: Type[Any], sort: bool = True
- ) -> List[_DeferredMapperConfig]:
- classes_for_base = [
- m
- for m, cls_ in [(m, m.cls) for m in cls._configs.values()]
- if cls_ is not None and issubclass(cls_, base_cls)
- ]
-
- if not sort:
- return classes_for_base
-
- all_m_by_cls = {m.cls: m for m in classes_for_base}
-
- tuples: List[Tuple[_DeferredMapperConfig, _DeferredMapperConfig]] = []
- for m_cls in all_m_by_cls:
- tuples.extend(
- (all_m_by_cls[base_cls], all_m_by_cls[m_cls])
- for base_cls in m_cls.__bases__
- if base_cls in all_m_by_cls
- )
- return list(topological.sort(tuples, classes_for_base))
-
- def map(self, mapper_kw: _MapperKwArgs = util.EMPTY_DICT) -> Mapper[Any]:
- self._configs.pop(self._cls, None)
- return super().map(mapper_kw)
-
-
-def _add_attribute(
- cls: Type[Any], key: str, value: MapperProperty[Any]
-) -> None:
- """add an attribute to an existing declarative class.
-
- This runs through the logic to determine MapperProperty,
- adds it to the Mapper, adds a column to the mapped Table, etc.
-
- """
-
- if "__mapper__" in cls.__dict__:
- mapped_cls = cast("MappedClassProtocol[Any]", cls)
-
- def _table_or_raise(mc: MappedClassProtocol[Any]) -> Table:
- if isinstance(mc.__table__, Table):
- return mc.__table__
- raise exc.InvalidRequestError(
- f"Cannot add a new attribute to mapped class {mc.__name__!r} "
- "because it's not mapped against a table."
- )
-
- if isinstance(value, Column):
- _undefer_column_name(key, value)
- _table_or_raise(mapped_cls).append_column(
- value, replace_existing=True
- )
- mapped_cls.__mapper__.add_property(key, value)
- elif isinstance(value, _MapsColumns):
- mp = value.mapper_property_to_assign
- for col, _ in value.columns_to_assign:
- _undefer_column_name(key, col)
- _table_or_raise(mapped_cls).append_column(
- col, replace_existing=True
- )
- if not mp:
- mapped_cls.__mapper__.add_property(key, col)
- if mp:
- mapped_cls.__mapper__.add_property(key, mp)
- elif isinstance(value, MapperProperty):
- mapped_cls.__mapper__.add_property(key, value)
- elif isinstance(value, QueryableAttribute) and value.key != key:
- # detect a QueryableAttribute that's already mapped being
- # assigned elsewhere in userland, turn into a synonym()
- value = SynonymProperty(value.key)
- mapped_cls.__mapper__.add_property(key, value)
- else:
- type.__setattr__(cls, key, value)
- mapped_cls.__mapper__._expire_memoizations()
- else:
- type.__setattr__(cls, key, value)
-
-
-def _del_attribute(cls: Type[Any], key: str) -> None:
- if (
- "__mapper__" in cls.__dict__
- and key in cls.__dict__
- and not cast(
- "MappedClassProtocol[Any]", cls
- ).__mapper__._dispose_called
- ):
- value = cls.__dict__[key]
- if isinstance(
- value, (Column, _MapsColumns, MapperProperty, QueryableAttribute)
- ):
- raise NotImplementedError(
- "Can't un-map individual mapped attributes on a mapped class."
- )
- else:
- type.__delattr__(cls, key)
- cast(
- "MappedClassProtocol[Any]", cls
- ).__mapper__._expire_memoizations()
- else:
- type.__delattr__(cls, key)
-
-
-def _declarative_constructor(self: Any, **kwargs: Any) -> None:
- """A simple constructor that allows initialization from kwargs.
-
- Sets attributes on the constructed instance using the names and
- values in ``kwargs``.
-
- Only keys that are present as
- attributes of the instance's class are allowed. These could be,
- for example, any mapped columns or relationships.
- """
- cls_ = type(self)
- for k in kwargs:
- if not hasattr(cls_, k):
- raise TypeError(
- "%r is an invalid keyword argument for %s" % (k, cls_.__name__)
- )
- setattr(self, k, kwargs[k])
-
-
-_declarative_constructor.__name__ = "__init__"
-
-
-def _undefer_column_name(key: str, column: Column[Any]) -> None:
- if column.key is None:
- column.key = key
- if column.name is None:
- column.name = key
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/dependency.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/dependency.py
deleted file mode 100644
index 71c06fb..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/dependency.py
+++ /dev/null
@@ -1,1304 +0,0 @@
-# orm/dependency.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
-# mypy: ignore-errors
-
-
-"""Relationship dependencies.
-
-"""
-
-from __future__ import annotations
-
-from . import attributes
-from . import exc
-from . import sync
-from . import unitofwork
-from . import util as mapperutil
-from .interfaces import MANYTOMANY
-from .interfaces import MANYTOONE
-from .interfaces import ONETOMANY
-from .. import exc as sa_exc
-from .. import sql
-from .. import util
-
-
-class DependencyProcessor:
- def __init__(self, prop):
- self.prop = prop
- self.cascade = prop.cascade
- self.mapper = prop.mapper
- self.parent = prop.parent
- self.secondary = prop.secondary
- self.direction = prop.direction
- self.post_update = prop.post_update
- self.passive_deletes = prop.passive_deletes
- self.passive_updates = prop.passive_updates
- self.enable_typechecks = prop.enable_typechecks
- if self.passive_deletes:
- self._passive_delete_flag = attributes.PASSIVE_NO_INITIALIZE
- else:
- self._passive_delete_flag = attributes.PASSIVE_OFF
- if self.passive_updates:
- self._passive_update_flag = attributes.PASSIVE_NO_INITIALIZE
- else:
- self._passive_update_flag = attributes.PASSIVE_OFF
-
- self.sort_key = "%s_%s" % (self.parent._sort_key, prop.key)
- self.key = prop.key
- if not self.prop.synchronize_pairs:
- raise sa_exc.ArgumentError(
- "Can't build a DependencyProcessor for relationship %s. "
- "No target attributes to populate between parent and "
- "child are present" % self.prop
- )
-
- @classmethod
- def from_relationship(cls, prop):
- return _direction_to_processor[prop.direction](prop)
-
- def hasparent(self, state):
- """return True if the given object instance has a parent,
- according to the ``InstrumentedAttribute`` handled by this
- ``DependencyProcessor``.
-
- """
- return self.parent.class_manager.get_impl(self.key).hasparent(state)
-
- def per_property_preprocessors(self, uow):
- """establish actions and dependencies related to a flush.
-
- These actions will operate on all relevant states in
- the aggregate.
-
- """
- uow.register_preprocessor(self, True)
-
- def per_property_flush_actions(self, uow):
- after_save = unitofwork.ProcessAll(uow, self, False, True)
- before_delete = unitofwork.ProcessAll(uow, self, True, True)
-
- parent_saves = unitofwork.SaveUpdateAll(
- uow, self.parent.primary_base_mapper
- )
- child_saves = unitofwork.SaveUpdateAll(
- uow, self.mapper.primary_base_mapper
- )
-
- parent_deletes = unitofwork.DeleteAll(
- uow, self.parent.primary_base_mapper
- )
- child_deletes = unitofwork.DeleteAll(
- uow, self.mapper.primary_base_mapper
- )
-
- self.per_property_dependencies(
- uow,
- parent_saves,
- child_saves,
- parent_deletes,
- child_deletes,
- after_save,
- before_delete,
- )
-
- def per_state_flush_actions(self, uow, states, isdelete):
- """establish actions and dependencies related to a flush.
-
- These actions will operate on all relevant states
- individually. This occurs only if there are cycles
- in the 'aggregated' version of events.
-
- """
-
- child_base_mapper = self.mapper.primary_base_mapper
- child_saves = unitofwork.SaveUpdateAll(uow, child_base_mapper)
- child_deletes = unitofwork.DeleteAll(uow, child_base_mapper)
-
- # locate and disable the aggregate processors
- # for this dependency
-
- if isdelete:
- before_delete = unitofwork.ProcessAll(uow, self, True, True)
- before_delete.disabled = True
- else:
- after_save = unitofwork.ProcessAll(uow, self, False, True)
- after_save.disabled = True
-
- # check if the "child" side is part of the cycle
-
- if child_saves not in uow.cycles:
- # based on the current dependencies we use, the saves/
- # deletes should always be in the 'cycles' collection
- # together. if this changes, we will have to break up
- # this method a bit more.
- assert child_deletes not in uow.cycles
-
- # child side is not part of the cycle, so we will link per-state
- # actions to the aggregate "saves", "deletes" actions
- child_actions = [(child_saves, False), (child_deletes, True)]
- child_in_cycles = False
- else:
- child_in_cycles = True
-
- # check if the "parent" side is part of the cycle
- if not isdelete:
- parent_saves = unitofwork.SaveUpdateAll(
- uow, self.parent.base_mapper
- )
- parent_deletes = before_delete = None
- if parent_saves in uow.cycles:
- parent_in_cycles = True
- else:
- parent_deletes = unitofwork.DeleteAll(uow, self.parent.base_mapper)
- parent_saves = after_save = None
- if parent_deletes in uow.cycles:
- parent_in_cycles = True
-
- # now create actions /dependencies for each state.
-
- for state in states:
- # detect if there's anything changed or loaded
- # by a preprocessor on this state/attribute. In the
- # case of deletes we may try to load missing items here as well.
- sum_ = state.manager[self.key].impl.get_all_pending(
- state,
- state.dict,
- (
- self._passive_delete_flag
- if isdelete
- else attributes.PASSIVE_NO_INITIALIZE
- ),
- )
-
- if not sum_:
- continue
-
- if isdelete:
- before_delete = unitofwork.ProcessState(uow, self, True, state)
- if parent_in_cycles:
- parent_deletes = unitofwork.DeleteState(uow, state)
- else:
- after_save = unitofwork.ProcessState(uow, self, False, state)
- if parent_in_cycles:
- parent_saves = unitofwork.SaveUpdateState(uow, state)
-
- if child_in_cycles:
- child_actions = []
- for child_state, child in sum_:
- if child_state not in uow.states:
- child_action = (None, None)
- else:
- (deleted, listonly) = uow.states[child_state]
- if deleted:
- child_action = (
- unitofwork.DeleteState(uow, child_state),
- True,
- )
- else:
- child_action = (
- unitofwork.SaveUpdateState(uow, child_state),
- False,
- )
- child_actions.append(child_action)
-
- # establish dependencies between our possibly per-state
- # parent action and our possibly per-state child action.
- for child_action, childisdelete in child_actions:
- self.per_state_dependencies(
- uow,
- parent_saves,
- parent_deletes,
- child_action,
- after_save,
- before_delete,
- isdelete,
- childisdelete,
- )
-
- def presort_deletes(self, uowcommit, states):
- return False
-
- def presort_saves(self, uowcommit, states):
- return False
-
- def process_deletes(self, uowcommit, states):
- pass
-
- def process_saves(self, uowcommit, states):
- pass
-
- def prop_has_changes(self, uowcommit, states, isdelete):
- if not isdelete or self.passive_deletes:
- passive = (
- attributes.PASSIVE_NO_INITIALIZE
- | attributes.INCLUDE_PENDING_MUTATIONS
- )
- elif self.direction is MANYTOONE:
- # here, we were hoping to optimize having to fetch many-to-one
- # for history and ignore it, if there's no further cascades
- # to take place. however there are too many less common conditions
- # that still take place and tests in test_relationships /
- # test_cascade etc. will still fail.
- passive = attributes.PASSIVE_NO_FETCH_RELATED
- else:
- passive = (
- attributes.PASSIVE_OFF | attributes.INCLUDE_PENDING_MUTATIONS
- )
-
- for s in states:
- # TODO: add a high speed method
- # to InstanceState which returns: attribute
- # has a non-None value, or had one
- history = uowcommit.get_attribute_history(s, self.key, passive)
- if history and not history.empty():
- return True
- else:
- return (
- states
- and not self.prop._is_self_referential
- and self.mapper in uowcommit.mappers
- )
-
- def _verify_canload(self, state):
- if self.prop.uselist and state is None:
- raise exc.FlushError(
- "Can't flush None value found in "
- "collection %s" % (self.prop,)
- )
- elif state is not None and not self.mapper._canload(
- state, allow_subtypes=not self.enable_typechecks
- ):
- if self.mapper._canload(state, allow_subtypes=True):
- raise exc.FlushError(
- "Attempting to flush an item of type "
- "%(x)s as a member of collection "
- '"%(y)s". Expected an object of type '
- "%(z)s or a polymorphic subclass of "
- "this type. If %(x)s is a subclass of "
- '%(z)s, configure mapper "%(zm)s" to '
- "load this subtype polymorphically, or "
- "set enable_typechecks=False to allow "
- "any subtype to be accepted for flush. "
- % {
- "x": state.class_,
- "y": self.prop,
- "z": self.mapper.class_,
- "zm": self.mapper,
- }
- )
- else:
- raise exc.FlushError(
- "Attempting to flush an item of type "
- "%(x)s as a member of collection "
- '"%(y)s". Expected an object of type '
- "%(z)s or a polymorphic subclass of "
- "this type."
- % {
- "x": state.class_,
- "y": self.prop,
- "z": self.mapper.class_,
- }
- )
-
- def _synchronize(self, state, child, associationrow, clearkeys, uowcommit):
- raise NotImplementedError()
-
- def _get_reversed_processed_set(self, uow):
- if not self.prop._reverse_property:
- return None
-
- process_key = tuple(
- sorted([self.key] + [p.key for p in self.prop._reverse_property])
- )
- return uow.memo(("reverse_key", process_key), set)
-
- def _post_update(self, state, uowcommit, related, is_m2o_delete=False):
- for x in related:
- if not is_m2o_delete or x is not None:
- uowcommit.register_post_update(
- state, [r for l, r in self.prop.synchronize_pairs]
- )
- break
-
- def _pks_changed(self, uowcommit, state):
- raise NotImplementedError()
-
- def __repr__(self):
- return "%s(%s)" % (self.__class__.__name__, self.prop)
-
-
-class OneToManyDP(DependencyProcessor):
- def per_property_dependencies(
- self,
- uow,
- parent_saves,
- child_saves,
- parent_deletes,
- child_deletes,
- after_save,
- before_delete,
- ):
- if self.post_update:
- child_post_updates = unitofwork.PostUpdateAll(
- uow, self.mapper.primary_base_mapper, False
- )
- child_pre_updates = unitofwork.PostUpdateAll(
- uow, self.mapper.primary_base_mapper, True
- )
-
- uow.dependencies.update(
- [
- (child_saves, after_save),
- (parent_saves, after_save),
- (after_save, child_post_updates),
- (before_delete, child_pre_updates),
- (child_pre_updates, parent_deletes),
- (child_pre_updates, child_deletes),
- ]
- )
- else:
- uow.dependencies.update(
- [
- (parent_saves, after_save),
- (after_save, child_saves),
- (after_save, child_deletes),
- (child_saves, parent_deletes),
- (child_deletes, parent_deletes),
- (before_delete, child_saves),
- (before_delete, child_deletes),
- ]
- )
-
- def per_state_dependencies(
- self,
- uow,
- save_parent,
- delete_parent,
- child_action,
- after_save,
- before_delete,
- isdelete,
- childisdelete,
- ):
- if self.post_update:
- child_post_updates = unitofwork.PostUpdateAll(
- uow, self.mapper.primary_base_mapper, False
- )
- child_pre_updates = unitofwork.PostUpdateAll(
- uow, self.mapper.primary_base_mapper, True
- )
-
- # TODO: this whole block is not covered
- # by any tests
- if not isdelete:
- if childisdelete:
- uow.dependencies.update(
- [
- (child_action, after_save),
- (after_save, child_post_updates),
- ]
- )
- else:
- uow.dependencies.update(
- [
- (save_parent, after_save),
- (child_action, after_save),
- (after_save, child_post_updates),
- ]
- )
- else:
- if childisdelete:
- uow.dependencies.update(
- [
- (before_delete, child_pre_updates),
- (child_pre_updates, delete_parent),
- ]
- )
- else:
- uow.dependencies.update(
- [
- (before_delete, child_pre_updates),
- (child_pre_updates, delete_parent),
- ]
- )
- elif not isdelete:
- uow.dependencies.update(
- [
- (save_parent, after_save),
- (after_save, child_action),
- (save_parent, child_action),
- ]
- )
- else:
- uow.dependencies.update(
- [(before_delete, child_action), (child_action, delete_parent)]
- )
-
- def presort_deletes(self, uowcommit, states):
- # head object is being deleted, and we manage its list of
- # child objects the child objects have to have their
- # foreign key to the parent set to NULL
- should_null_fks = (
- not self.cascade.delete and not self.passive_deletes == "all"
- )
-
- for state in states:
- history = uowcommit.get_attribute_history(
- state, self.key, self._passive_delete_flag
- )
- if history:
- for child in history.deleted:
- if child is not None and self.hasparent(child) is False:
- if self.cascade.delete_orphan:
- uowcommit.register_object(child, isdelete=True)
- else:
- uowcommit.register_object(child)
-
- if should_null_fks:
- for child in history.unchanged:
- if child is not None:
- uowcommit.register_object(
- child, operation="delete", prop=self.prop
- )
-
- def presort_saves(self, uowcommit, states):
- children_added = uowcommit.memo(("children_added", self), set)
-
- should_null_fks = (
- not self.cascade.delete_orphan
- and not self.passive_deletes == "all"
- )
-
- for state in states:
- pks_changed = self._pks_changed(uowcommit, state)
-
- if not pks_changed or self.passive_updates:
- passive = (
- attributes.PASSIVE_NO_INITIALIZE
- | attributes.INCLUDE_PENDING_MUTATIONS
- )
- else:
- passive = (
- attributes.PASSIVE_OFF
- | attributes.INCLUDE_PENDING_MUTATIONS
- )
-
- history = uowcommit.get_attribute_history(state, self.key, passive)
- if history:
- for child in history.added:
- if child is not None:
- uowcommit.register_object(
- child,
- cancel_delete=True,
- operation="add",
- prop=self.prop,
- )
-
- children_added.update(history.added)
-
- for child in history.deleted:
- if not self.cascade.delete_orphan:
- if should_null_fks:
- uowcommit.register_object(
- child,
- isdelete=False,
- operation="delete",
- prop=self.prop,
- )
- elif self.hasparent(child) is False:
- uowcommit.register_object(
- child,
- isdelete=True,
- operation="delete",
- prop=self.prop,
- )
- for c, m, st_, dct_ in self.mapper.cascade_iterator(
- "delete", child
- ):
- uowcommit.register_object(st_, isdelete=True)
-
- if pks_changed:
- if history:
- for child in history.unchanged:
- if child is not None:
- uowcommit.register_object(
- child,
- False,
- self.passive_updates,
- operation="pk change",
- prop=self.prop,
- )
-
- def process_deletes(self, uowcommit, states):
- # head object is being deleted, and we manage its list of
- # child objects the child objects have to have their foreign
- # key to the parent set to NULL this phase can be called
- # safely for any cascade but is unnecessary if delete cascade
- # is on.
-
- if self.post_update or not self.passive_deletes == "all":
- children_added = uowcommit.memo(("children_added", self), set)
-
- for state in states:
- history = uowcommit.get_attribute_history(
- state, self.key, self._passive_delete_flag
- )
- if history:
- for child in history.deleted:
- if (
- child is not None
- and self.hasparent(child) is False
- ):
- self._synchronize(
- state, child, None, True, uowcommit, False
- )
- if self.post_update and child:
- self._post_update(child, uowcommit, [state])
-
- if self.post_update or not self.cascade.delete:
- for child in set(history.unchanged).difference(
- children_added
- ):
- if child is not None:
- self._synchronize(
- state, child, None, True, uowcommit, False
- )
- if self.post_update and child:
- self._post_update(
- child, uowcommit, [state]
- )
-
- # technically, we can even remove each child from the
- # collection here too. but this would be a somewhat
- # inconsistent behavior since it wouldn't happen
- # if the old parent wasn't deleted but child was moved.
-
- def process_saves(self, uowcommit, states):
- should_null_fks = (
- not self.cascade.delete_orphan
- and not self.passive_deletes == "all"
- )
-
- for state in states:
- history = uowcommit.get_attribute_history(
- state, self.key, attributes.PASSIVE_NO_INITIALIZE
- )
- if history:
- for child in history.added:
- self._synchronize(
- state, child, None, False, uowcommit, False
- )
- if child is not None and self.post_update:
- self._post_update(child, uowcommit, [state])
-
- for child in history.deleted:
- if (
- should_null_fks
- and not self.cascade.delete_orphan
- and not self.hasparent(child)
- ):
- self._synchronize(
- state, child, None, True, uowcommit, False
- )
-
- if self._pks_changed(uowcommit, state):
- for child in history.unchanged:
- self._synchronize(
- state, child, None, False, uowcommit, True
- )
-
- def _synchronize(
- self, state, child, associationrow, clearkeys, uowcommit, pks_changed
- ):
- source = state
- dest = child
- self._verify_canload(child)
- if dest is None or (
- not self.post_update and uowcommit.is_deleted(dest)
- ):
- return
- if clearkeys:
- sync.clear(dest, self.mapper, self.prop.synchronize_pairs)
- else:
- sync.populate(
- source,
- self.parent,
- dest,
- self.mapper,
- self.prop.synchronize_pairs,
- uowcommit,
- self.passive_updates and pks_changed,
- )
-
- def _pks_changed(self, uowcommit, state):
- return sync.source_modified(
- uowcommit, state, self.parent, self.prop.synchronize_pairs
- )
-
-
-class ManyToOneDP(DependencyProcessor):
- def __init__(self, prop):
- DependencyProcessor.__init__(self, prop)
- for mapper in self.mapper.self_and_descendants:
- mapper._dependency_processors.append(DetectKeySwitch(prop))
-
- def per_property_dependencies(
- self,
- uow,
- parent_saves,
- child_saves,
- parent_deletes,
- child_deletes,
- after_save,
- before_delete,
- ):
- if self.post_update:
- parent_post_updates = unitofwork.PostUpdateAll(
- uow, self.parent.primary_base_mapper, False
- )
- parent_pre_updates = unitofwork.PostUpdateAll(
- uow, self.parent.primary_base_mapper, True
- )
-
- uow.dependencies.update(
- [
- (child_saves, after_save),
- (parent_saves, after_save),
- (after_save, parent_post_updates),
- (after_save, parent_pre_updates),
- (before_delete, parent_pre_updates),
- (parent_pre_updates, child_deletes),
- (parent_pre_updates, parent_deletes),
- ]
- )
- else:
- uow.dependencies.update(
- [
- (child_saves, after_save),
- (after_save, parent_saves),
- (parent_saves, child_deletes),
- (parent_deletes, child_deletes),
- ]
- )
-
- def per_state_dependencies(
- self,
- uow,
- save_parent,
- delete_parent,
- child_action,
- after_save,
- before_delete,
- isdelete,
- childisdelete,
- ):
- if self.post_update:
- if not isdelete:
- parent_post_updates = unitofwork.PostUpdateAll(
- uow, self.parent.primary_base_mapper, False
- )
- if childisdelete:
- uow.dependencies.update(
- [
- (after_save, parent_post_updates),
- (parent_post_updates, child_action),
- ]
- )
- else:
- uow.dependencies.update(
- [
- (save_parent, after_save),
- (child_action, after_save),
- (after_save, parent_post_updates),
- ]
- )
- else:
- parent_pre_updates = unitofwork.PostUpdateAll(
- uow, self.parent.primary_base_mapper, True
- )
-
- uow.dependencies.update(
- [
- (before_delete, parent_pre_updates),
- (parent_pre_updates, delete_parent),
- (parent_pre_updates, child_action),
- ]
- )
-
- elif not isdelete:
- if not childisdelete:
- uow.dependencies.update(
- [(child_action, after_save), (after_save, save_parent)]
- )
- else:
- uow.dependencies.update([(after_save, save_parent)])
-
- else:
- if childisdelete:
- uow.dependencies.update([(delete_parent, child_action)])
-
- def presort_deletes(self, uowcommit, states):
- if self.cascade.delete or self.cascade.delete_orphan:
- for state in states:
- history = uowcommit.get_attribute_history(
- state, self.key, self._passive_delete_flag
- )
- if history:
- if self.cascade.delete_orphan:
- todelete = history.sum()
- else:
- todelete = history.non_deleted()
- for child in todelete:
- if child is None:
- continue
- uowcommit.register_object(
- child,
- isdelete=True,
- operation="delete",
- prop=self.prop,
- )
- t = self.mapper.cascade_iterator("delete", child)
- for c, m, st_, dct_ in t:
- uowcommit.register_object(st_, isdelete=True)
-
- def presort_saves(self, uowcommit, states):
- for state in states:
- uowcommit.register_object(state, operation="add", prop=self.prop)
- if self.cascade.delete_orphan:
- history = uowcommit.get_attribute_history(
- state, self.key, self._passive_delete_flag
- )
- if history:
- for child in history.deleted:
- if self.hasparent(child) is False:
- uowcommit.register_object(
- child,
- isdelete=True,
- operation="delete",
- prop=self.prop,
- )
-
- t = self.mapper.cascade_iterator("delete", child)
- for c, m, st_, dct_ in t:
- uowcommit.register_object(st_, isdelete=True)
-
- def process_deletes(self, uowcommit, states):
- if (
- self.post_update
- and not self.cascade.delete_orphan
- and not self.passive_deletes == "all"
- ):
- # post_update means we have to update our
- # row to not reference the child object
- # before we can DELETE the row
- for state in states:
- self._synchronize(state, None, None, True, uowcommit)
- if state and self.post_update:
- history = uowcommit.get_attribute_history(
- state, self.key, self._passive_delete_flag
- )
- if history:
- self._post_update(
- state, uowcommit, history.sum(), is_m2o_delete=True
- )
-
- def process_saves(self, uowcommit, states):
- for state in states:
- history = uowcommit.get_attribute_history(
- state, self.key, attributes.PASSIVE_NO_INITIALIZE
- )
- if history:
- if history.added:
- for child in history.added:
- self._synchronize(
- state, child, None, False, uowcommit, "add"
- )
- elif history.deleted:
- self._synchronize(
- state, None, None, True, uowcommit, "delete"
- )
- if self.post_update:
- self._post_update(state, uowcommit, history.sum())
-
- def _synchronize(
- self,
- state,
- child,
- associationrow,
- clearkeys,
- uowcommit,
- operation=None,
- ):
- if state is None or (
- not self.post_update and uowcommit.is_deleted(state)
- ):
- return
-
- if (
- operation is not None
- and child is not None
- and not uowcommit.session._contains_state(child)
- ):
- util.warn(
- "Object of type %s not in session, %s "
- "operation along '%s' won't proceed"
- % (mapperutil.state_class_str(child), operation, self.prop)
- )
- return
-
- if clearkeys or child is None:
- sync.clear(state, self.parent, self.prop.synchronize_pairs)
- else:
- self._verify_canload(child)
- sync.populate(
- child,
- self.mapper,
- state,
- self.parent,
- self.prop.synchronize_pairs,
- uowcommit,
- False,
- )
-
-
-class DetectKeySwitch(DependencyProcessor):
- """For many-to-one relationships with no one-to-many backref,
- searches for parents through the unit of work when a primary
- key has changed and updates them.
-
- Theoretically, this approach could be expanded to support transparent
- deletion of objects referenced via many-to-one as well, although
- the current attribute system doesn't do enough bookkeeping for this
- to be efficient.
-
- """
-
- def per_property_preprocessors(self, uow):
- if self.prop._reverse_property:
- if self.passive_updates:
- return
- else:
- if False in (
- prop.passive_updates
- for prop in self.prop._reverse_property
- ):
- return
-
- uow.register_preprocessor(self, False)
-
- def per_property_flush_actions(self, uow):
- parent_saves = unitofwork.SaveUpdateAll(uow, self.parent.base_mapper)
- after_save = unitofwork.ProcessAll(uow, self, False, False)
- uow.dependencies.update([(parent_saves, after_save)])
-
- def per_state_flush_actions(self, uow, states, isdelete):
- pass
-
- def presort_deletes(self, uowcommit, states):
- pass
-
- def presort_saves(self, uow, states):
- if not self.passive_updates:
- # for non-passive updates, register in the preprocess stage
- # so that mapper save_obj() gets a hold of changes
- self._process_key_switches(states, uow)
-
- def prop_has_changes(self, uow, states, isdelete):
- if not isdelete and self.passive_updates:
- d = self._key_switchers(uow, states)
- return bool(d)
-
- return False
-
- def process_deletes(self, uowcommit, states):
- assert False
-
- def process_saves(self, uowcommit, states):
- # for passive updates, register objects in the process stage
- # so that we avoid ManyToOneDP's registering the object without
- # the listonly flag in its own preprocess stage (results in UPDATE)
- # statements being emitted
- assert self.passive_updates
- self._process_key_switches(states, uowcommit)
-
- def _key_switchers(self, uow, states):
- switched, notswitched = uow.memo(
- ("pk_switchers", self), lambda: (set(), set())
- )
-
- allstates = switched.union(notswitched)
- for s in states:
- if s not in allstates:
- if self._pks_changed(uow, s):
- switched.add(s)
- else:
- notswitched.add(s)
- return switched
-
- def _process_key_switches(self, deplist, uowcommit):
- switchers = self._key_switchers(uowcommit, deplist)
- if switchers:
- # if primary key values have actually changed somewhere, perform
- # a linear search through the UOW in search of a parent.
- for state in uowcommit.session.identity_map.all_states():
- if not issubclass(state.class_, self.parent.class_):
- continue
- dict_ = state.dict
- related = state.get_impl(self.key).get(
- state, dict_, passive=self._passive_update_flag
- )
- if (
- related is not attributes.PASSIVE_NO_RESULT
- and related is not None
- ):
- if self.prop.uselist:
- if not related:
- continue
- related_obj = related[0]
- else:
- related_obj = related
- related_state = attributes.instance_state(related_obj)
- if related_state in switchers:
- uowcommit.register_object(
- state, False, self.passive_updates
- )
- sync.populate(
- related_state,
- self.mapper,
- state,
- self.parent,
- self.prop.synchronize_pairs,
- uowcommit,
- self.passive_updates,
- )
-
- def _pks_changed(self, uowcommit, state):
- return bool(state.key) and sync.source_modified(
- uowcommit, state, self.mapper, self.prop.synchronize_pairs
- )
-
-
-class ManyToManyDP(DependencyProcessor):
- def per_property_dependencies(
- self,
- uow,
- parent_saves,
- child_saves,
- parent_deletes,
- child_deletes,
- after_save,
- before_delete,
- ):
- uow.dependencies.update(
- [
- (parent_saves, after_save),
- (child_saves, after_save),
- (after_save, child_deletes),
- # a rowswitch on the parent from deleted to saved
- # can make this one occur, as the "save" may remove
- # an element from the
- # "deleted" list before we have a chance to
- # process its child rows
- (before_delete, parent_saves),
- (before_delete, parent_deletes),
- (before_delete, child_deletes),
- (before_delete, child_saves),
- ]
- )
-
- def per_state_dependencies(
- self,
- uow,
- save_parent,
- delete_parent,
- child_action,
- after_save,
- before_delete,
- isdelete,
- childisdelete,
- ):
- if not isdelete:
- if childisdelete:
- uow.dependencies.update(
- [(save_parent, after_save), (after_save, child_action)]
- )
- else:
- uow.dependencies.update(
- [(save_parent, after_save), (child_action, after_save)]
- )
- else:
- uow.dependencies.update(
- [(before_delete, child_action), (before_delete, delete_parent)]
- )
-
- def presort_deletes(self, uowcommit, states):
- # TODO: no tests fail if this whole
- # thing is removed !!!!
- if not self.passive_deletes:
- # if no passive deletes, load history on
- # the collection, so that prop_has_changes()
- # returns True
- for state in states:
- uowcommit.get_attribute_history(
- state, self.key, self._passive_delete_flag
- )
-
- def presort_saves(self, uowcommit, states):
- if not self.passive_updates:
- # if no passive updates, load history on
- # each collection where parent has changed PK,
- # so that prop_has_changes() returns True
- for state in states:
- if self._pks_changed(uowcommit, state):
- history = uowcommit.get_attribute_history(
- state, self.key, attributes.PASSIVE_OFF
- )
-
- if not self.cascade.delete_orphan:
- return
-
- # check for child items removed from the collection
- # if delete_orphan check is turned on.
- for state in states:
- history = uowcommit.get_attribute_history(
- state, self.key, attributes.PASSIVE_NO_INITIALIZE
- )
- if history:
- for child in history.deleted:
- if self.hasparent(child) is False:
- uowcommit.register_object(
- child,
- isdelete=True,
- operation="delete",
- prop=self.prop,
- )
- for c, m, st_, dct_ in self.mapper.cascade_iterator(
- "delete", child
- ):
- uowcommit.register_object(st_, isdelete=True)
-
- def process_deletes(self, uowcommit, states):
- secondary_delete = []
- secondary_insert = []
- secondary_update = []
-
- processed = self._get_reversed_processed_set(uowcommit)
- tmp = set()
- for state in states:
- # this history should be cached already, as
- # we loaded it in preprocess_deletes
- history = uowcommit.get_attribute_history(
- state, self.key, self._passive_delete_flag
- )
- if history:
- for child in history.non_added():
- if child is None or (
- processed is not None and (state, child) in processed
- ):
- continue
- associationrow = {}
- if not self._synchronize(
- state,
- child,
- associationrow,
- False,
- uowcommit,
- "delete",
- ):
- continue
- secondary_delete.append(associationrow)
-
- tmp.update((c, state) for c in history.non_added())
-
- if processed is not None:
- processed.update(tmp)
-
- self._run_crud(
- uowcommit, secondary_insert, secondary_update, secondary_delete
- )
-
- def process_saves(self, uowcommit, states):
- secondary_delete = []
- secondary_insert = []
- secondary_update = []
-
- processed = self._get_reversed_processed_set(uowcommit)
- tmp = set()
-
- for state in states:
- need_cascade_pks = not self.passive_updates and self._pks_changed(
- uowcommit, state
- )
- if need_cascade_pks:
- passive = (
- attributes.PASSIVE_OFF
- | attributes.INCLUDE_PENDING_MUTATIONS
- )
- else:
- passive = (
- attributes.PASSIVE_NO_INITIALIZE
- | attributes.INCLUDE_PENDING_MUTATIONS
- )
- history = uowcommit.get_attribute_history(state, self.key, passive)
- if history:
- for child in history.added:
- if processed is not None and (state, child) in processed:
- continue
- associationrow = {}
- if not self._synchronize(
- state, child, associationrow, False, uowcommit, "add"
- ):
- continue
- secondary_insert.append(associationrow)
- for child in history.deleted:
- if processed is not None and (state, child) in processed:
- continue
- associationrow = {}
- if not self._synchronize(
- state,
- child,
- associationrow,
- False,
- uowcommit,
- "delete",
- ):
- continue
- secondary_delete.append(associationrow)
-
- tmp.update((c, state) for c in history.added + history.deleted)
-
- if need_cascade_pks:
- for child in history.unchanged:
- associationrow = {}
- sync.update(
- state,
- self.parent,
- associationrow,
- "old_",
- self.prop.synchronize_pairs,
- )
- sync.update(
- child,
- self.mapper,
- associationrow,
- "old_",
- self.prop.secondary_synchronize_pairs,
- )
-
- secondary_update.append(associationrow)
-
- if processed is not None:
- processed.update(tmp)
-
- self._run_crud(
- uowcommit, secondary_insert, secondary_update, secondary_delete
- )
-
- def _run_crud(
- self, uowcommit, secondary_insert, secondary_update, secondary_delete
- ):
- connection = uowcommit.transaction.connection(self.mapper)
-
- if secondary_delete:
- associationrow = secondary_delete[0]
- statement = self.secondary.delete().where(
- sql.and_(
- *[
- c == sql.bindparam(c.key, type_=c.type)
- for c in self.secondary.c
- if c.key in associationrow
- ]
- )
- )
- result = connection.execute(statement, secondary_delete)
-
- if (
- result.supports_sane_multi_rowcount()
- ) and result.rowcount != len(secondary_delete):
- raise exc.StaleDataError(
- "DELETE statement on table '%s' expected to delete "
- "%d row(s); Only %d were matched."
- % (
- self.secondary.description,
- len(secondary_delete),
- result.rowcount,
- )
- )
-
- if secondary_update:
- associationrow = secondary_update[0]
- statement = self.secondary.update().where(
- sql.and_(
- *[
- c == sql.bindparam("old_" + c.key, type_=c.type)
- for c in self.secondary.c
- if c.key in associationrow
- ]
- )
- )
- result = connection.execute(statement, secondary_update)
-
- if (
- result.supports_sane_multi_rowcount()
- ) and result.rowcount != len(secondary_update):
- raise exc.StaleDataError(
- "UPDATE statement on table '%s' expected to update "
- "%d row(s); Only %d were matched."
- % (
- self.secondary.description,
- len(secondary_update),
- result.rowcount,
- )
- )
-
- if secondary_insert:
- statement = self.secondary.insert()
- connection.execute(statement, secondary_insert)
-
- def _synchronize(
- self, state, child, associationrow, clearkeys, uowcommit, operation
- ):
- # this checks for None if uselist=True
- self._verify_canload(child)
-
- # but if uselist=False we get here. If child is None,
- # no association row can be generated, so return.
- if child is None:
- return False
-
- if child is not None and not uowcommit.session._contains_state(child):
- if not child.deleted:
- util.warn(
- "Object of type %s not in session, %s "
- "operation along '%s' won't proceed"
- % (mapperutil.state_class_str(child), operation, self.prop)
- )
- return False
-
- sync.populate_dict(
- state, self.parent, associationrow, self.prop.synchronize_pairs
- )
- sync.populate_dict(
- child,
- self.mapper,
- associationrow,
- self.prop.secondary_synchronize_pairs,
- )
-
- return True
-
- def _pks_changed(self, uowcommit, state):
- return sync.source_modified(
- uowcommit, state, self.parent, self.prop.synchronize_pairs
- )
-
-
-_direction_to_processor = {
- ONETOMANY: OneToManyDP,
- MANYTOONE: ManyToOneDP,
- MANYTOMANY: ManyToManyDP,
-}
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/descriptor_props.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/descriptor_props.py
deleted file mode 100644
index a3650f5..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/descriptor_props.py
+++ /dev/null
@@ -1,1074 +0,0 @@
-# orm/descriptor_props.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
-
-"""Descriptor properties are more "auxiliary" properties
-that exist as configurational elements, but don't participate
-as actively in the load/persist ORM loop.
-
-"""
-from __future__ import annotations
-
-from dataclasses import is_dataclass
-import inspect
-import itertools
-import operator
-import typing
-from typing import Any
-from typing import Callable
-from typing import Dict
-from typing import List
-from typing import NoReturn
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-import weakref
-
-from . import attributes
-from . import util as orm_util
-from .base import _DeclarativeMapped
-from .base import LoaderCallableStatus
-from .base import Mapped
-from .base import PassiveFlag
-from .base import SQLORMOperations
-from .interfaces import _AttributeOptions
-from .interfaces import _IntrospectsAnnotations
-from .interfaces import _MapsColumns
-from .interfaces import MapperProperty
-from .interfaces import PropComparator
-from .util import _none_set
-from .util import de_stringify_annotation
-from .. import event
-from .. import exc as sa_exc
-from .. import schema
-from .. import sql
-from .. import util
-from ..sql import expression
-from ..sql import operators
-from ..sql.elements import BindParameter
-from ..util.typing import is_fwd_ref
-from ..util.typing import is_pep593
-from ..util.typing import typing_get_args
-
-if typing.TYPE_CHECKING:
- from ._typing import _InstanceDict
- from ._typing import _RegistryType
- from .attributes import History
- from .attributes import InstrumentedAttribute
- from .attributes import QueryableAttribute
- from .context import ORMCompileState
- from .decl_base import _ClassScanMapperConfig
- from .mapper import Mapper
- from .properties import ColumnProperty
- from .properties import MappedColumn
- from .state import InstanceState
- from ..engine.base import Connection
- from ..engine.row import Row
- from ..sql._typing import _DMLColumnArgument
- from ..sql._typing import _InfoType
- from ..sql.elements import ClauseList
- from ..sql.elements import ColumnElement
- from ..sql.operators import OperatorType
- from ..sql.schema import Column
- from ..sql.selectable import Select
- from ..util.typing import _AnnotationScanType
- from ..util.typing import CallableReference
- from ..util.typing import DescriptorReference
- from ..util.typing import RODescriptorReference
-
-_T = TypeVar("_T", bound=Any)
-_PT = TypeVar("_PT", bound=Any)
-
-
-class DescriptorProperty(MapperProperty[_T]):
- """:class:`.MapperProperty` which proxies access to a
- user-defined descriptor."""
-
- doc: Optional[str] = None
-
- uses_objects = False
- _links_to_entity = False
-
- descriptor: DescriptorReference[Any]
-
- def get_history(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- ) -> History:
- raise NotImplementedError()
-
- def instrument_class(self, mapper: Mapper[Any]) -> None:
- prop = self
-
- class _ProxyImpl(attributes.AttributeImpl):
- accepts_scalar_loader = False
- load_on_unexpire = True
- collection = False
-
- @property
- def uses_objects(self) -> bool: # type: ignore
- return prop.uses_objects
-
- def __init__(self, key: str):
- self.key = key
-
- def get_history(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- ) -> History:
- return prop.get_history(state, dict_, passive)
-
- if self.descriptor is None:
- desc = getattr(mapper.class_, self.key, None)
- if mapper._is_userland_descriptor(self.key, desc):
- self.descriptor = desc
-
- if self.descriptor is None:
-
- def fset(obj: Any, value: Any) -> None:
- setattr(obj, self.name, value)
-
- def fdel(obj: Any) -> None:
- delattr(obj, self.name)
-
- def fget(obj: Any) -> Any:
- return getattr(obj, self.name)
-
- self.descriptor = property(fget=fget, fset=fset, fdel=fdel)
-
- proxy_attr = attributes.create_proxied_attribute(self.descriptor)(
- self.parent.class_,
- self.key,
- self.descriptor,
- lambda: self._comparator_factory(mapper),
- doc=self.doc,
- original_property=self,
- )
- proxy_attr.impl = _ProxyImpl(self.key)
- mapper.class_manager.instrument_attribute(self.key, proxy_attr)
-
-
-_CompositeAttrType = Union[
- str,
- "Column[_T]",
- "MappedColumn[_T]",
- "InstrumentedAttribute[_T]",
- "Mapped[_T]",
-]
-
-
-_CC = TypeVar("_CC", bound=Any)
-
-
-_composite_getters: weakref.WeakKeyDictionary[
- Type[Any], Callable[[Any], Tuple[Any, ...]]
-] = weakref.WeakKeyDictionary()
-
-
-class CompositeProperty(
- _MapsColumns[_CC], _IntrospectsAnnotations, DescriptorProperty[_CC]
-):
- """Defines a "composite" mapped attribute, representing a collection
- of columns as one attribute.
-
- :class:`.CompositeProperty` is constructed using the :func:`.composite`
- function.
-
- .. seealso::
-
- :ref:`mapper_composite`
-
- """
-
- composite_class: Union[Type[_CC], Callable[..., _CC]]
- attrs: Tuple[_CompositeAttrType[Any], ...]
-
- _generated_composite_accessor: CallableReference[
- Optional[Callable[[_CC], Tuple[Any, ...]]]
- ]
-
- comparator_factory: Type[Comparator[_CC]]
-
- def __init__(
- self,
- _class_or_attr: Union[
- None, Type[_CC], Callable[..., _CC], _CompositeAttrType[Any]
- ] = None,
- *attrs: _CompositeAttrType[Any],
- attribute_options: Optional[_AttributeOptions] = None,
- active_history: bool = False,
- deferred: bool = False,
- group: Optional[str] = None,
- comparator_factory: Optional[Type[Comparator[_CC]]] = None,
- info: Optional[_InfoType] = None,
- **kwargs: Any,
- ):
- super().__init__(attribute_options=attribute_options)
-
- if isinstance(_class_or_attr, (Mapped, str, sql.ColumnElement)):
- self.attrs = (_class_or_attr,) + attrs
- # will initialize within declarative_scan
- self.composite_class = None # type: ignore
- else:
- self.composite_class = _class_or_attr # type: ignore
- self.attrs = attrs
-
- self.active_history = active_history
- self.deferred = deferred
- self.group = group
- self.comparator_factory = (
- comparator_factory
- if comparator_factory is not None
- else self.__class__.Comparator
- )
- self._generated_composite_accessor = None
- if info is not None:
- self.info.update(info)
-
- util.set_creation_order(self)
- self._create_descriptor()
- self._init_accessor()
-
- def instrument_class(self, mapper: Mapper[Any]) -> None:
- super().instrument_class(mapper)
- self._setup_event_handlers()
-
- def _composite_values_from_instance(self, value: _CC) -> Tuple[Any, ...]:
- if self._generated_composite_accessor:
- return self._generated_composite_accessor(value)
- else:
- try:
- accessor = value.__composite_values__
- except AttributeError as ae:
- raise sa_exc.InvalidRequestError(
- f"Composite class {self.composite_class.__name__} is not "
- f"a dataclass and does not define a __composite_values__()"
- " method; can't get state"
- ) from ae
- else:
- return accessor() # type: ignore
-
- def do_init(self) -> None:
- """Initialization which occurs after the :class:`.Composite`
- has been associated with its parent mapper.
-
- """
- self._setup_arguments_on_columns()
-
- _COMPOSITE_FGET = object()
-
- def _create_descriptor(self) -> None:
- """Create the Python descriptor that will serve as
- the access point on instances of the mapped class.
-
- """
-
- def fget(instance: Any) -> Any:
- dict_ = attributes.instance_dict(instance)
- state = attributes.instance_state(instance)
-
- if self.key not in dict_:
- # key not present. Iterate through related
- # attributes, retrieve their values. This
- # ensures they all load.
- values = [
- getattr(instance, key) for key in self._attribute_keys
- ]
-
- # current expected behavior here is that the composite is
- # created on access if the object is persistent or if
- # col attributes have non-None. This would be better
- # if the composite were created unconditionally,
- # but that would be a behavioral change.
- if self.key not in dict_ and (
- state.key is not None or not _none_set.issuperset(values)
- ):
- dict_[self.key] = self.composite_class(*values)
- state.manager.dispatch.refresh(
- state, self._COMPOSITE_FGET, [self.key]
- )
-
- return dict_.get(self.key, None)
-
- def fset(instance: Any, value: Any) -> None:
- dict_ = attributes.instance_dict(instance)
- state = attributes.instance_state(instance)
- attr = state.manager[self.key]
-
- if attr.dispatch._active_history:
- previous = fget(instance)
- else:
- previous = dict_.get(self.key, LoaderCallableStatus.NO_VALUE)
-
- for fn in attr.dispatch.set:
- value = fn(state, value, previous, attr.impl)
- dict_[self.key] = value
- if value is None:
- for key in self._attribute_keys:
- setattr(instance, key, None)
- else:
- for key, value in zip(
- self._attribute_keys,
- self._composite_values_from_instance(value),
- ):
- setattr(instance, key, value)
-
- def fdel(instance: Any) -> None:
- state = attributes.instance_state(instance)
- dict_ = attributes.instance_dict(instance)
- attr = state.manager[self.key]
-
- if attr.dispatch._active_history:
- previous = fget(instance)
- dict_.pop(self.key, None)
- else:
- previous = dict_.pop(self.key, LoaderCallableStatus.NO_VALUE)
-
- attr = state.manager[self.key]
- attr.dispatch.remove(state, previous, attr.impl)
- for key in self._attribute_keys:
- setattr(instance, key, None)
-
- self.descriptor = property(fget, fset, fdel)
-
- @util.preload_module("sqlalchemy.orm.properties")
- def declarative_scan(
- self,
- decl_scan: _ClassScanMapperConfig,
- registry: _RegistryType,
- cls: Type[Any],
- originating_module: Optional[str],
- key: str,
- mapped_container: Optional[Type[Mapped[Any]]],
- annotation: Optional[_AnnotationScanType],
- extracted_mapped_annotation: Optional[_AnnotationScanType],
- is_dataclass_field: bool,
- ) -> None:
- MappedColumn = util.preloaded.orm_properties.MappedColumn
- if (
- self.composite_class is None
- and extracted_mapped_annotation is None
- ):
- self._raise_for_required(key, cls)
- argument = extracted_mapped_annotation
-
- if is_pep593(argument):
- argument = typing_get_args(argument)[0]
-
- if argument and self.composite_class is None:
- if isinstance(argument, str) or is_fwd_ref(
- argument, check_generic=True
- ):
- if originating_module is None:
- str_arg = (
- argument.__forward_arg__
- if hasattr(argument, "__forward_arg__")
- else str(argument)
- )
- raise sa_exc.ArgumentError(
- f"Can't use forward ref {argument} for composite "
- f"class argument; set up the type as Mapped[{str_arg}]"
- )
- argument = de_stringify_annotation(
- cls, argument, originating_module, include_generic=True
- )
-
- self.composite_class = argument
-
- if is_dataclass(self.composite_class):
- self._setup_for_dataclass(registry, cls, originating_module, key)
- else:
- for attr in self.attrs:
- if (
- isinstance(attr, (MappedColumn, schema.Column))
- and attr.name is None
- ):
- raise sa_exc.ArgumentError(
- "Composite class column arguments must be named "
- "unless a dataclass is used"
- )
- self._init_accessor()
-
- def _init_accessor(self) -> None:
- if is_dataclass(self.composite_class) and not hasattr(
- self.composite_class, "__composite_values__"
- ):
- insp = inspect.signature(self.composite_class)
- getter = operator.attrgetter(
- *[p.name for p in insp.parameters.values()]
- )
- if len(insp.parameters) == 1:
- self._generated_composite_accessor = lambda obj: (getter(obj),)
- else:
- self._generated_composite_accessor = getter
-
- if (
- self.composite_class is not None
- and isinstance(self.composite_class, type)
- and self.composite_class not in _composite_getters
- ):
- if self._generated_composite_accessor is not None:
- _composite_getters[self.composite_class] = (
- self._generated_composite_accessor
- )
- elif hasattr(self.composite_class, "__composite_values__"):
- _composite_getters[self.composite_class] = (
- lambda obj: obj.__composite_values__()
- )
-
- @util.preload_module("sqlalchemy.orm.properties")
- @util.preload_module("sqlalchemy.orm.decl_base")
- def _setup_for_dataclass(
- self,
- registry: _RegistryType,
- cls: Type[Any],
- originating_module: Optional[str],
- key: str,
- ) -> None:
- MappedColumn = util.preloaded.orm_properties.MappedColumn
-
- decl_base = util.preloaded.orm_decl_base
-
- insp = inspect.signature(self.composite_class)
- for param, attr in itertools.zip_longest(
- insp.parameters.values(), self.attrs
- ):
- if param is None:
- raise sa_exc.ArgumentError(
- f"number of composite attributes "
- f"{len(self.attrs)} exceeds "
- f"that of the number of attributes in class "
- f"{self.composite_class.__name__} {len(insp.parameters)}"
- )
- if attr is None:
- # fill in missing attr spots with empty MappedColumn
- attr = MappedColumn()
- self.attrs += (attr,)
-
- if isinstance(attr, MappedColumn):
- attr.declarative_scan_for_composite(
- registry,
- cls,
- originating_module,
- key,
- param.name,
- param.annotation,
- )
- elif isinstance(attr, schema.Column):
- decl_base._undefer_column_name(param.name, attr)
-
- @util.memoized_property
- def _comparable_elements(self) -> Sequence[QueryableAttribute[Any]]:
- return [getattr(self.parent.class_, prop.key) for prop in self.props]
-
- @util.memoized_property
- @util.preload_module("orm.properties")
- def props(self) -> Sequence[MapperProperty[Any]]:
- props = []
- MappedColumn = util.preloaded.orm_properties.MappedColumn
-
- for attr in self.attrs:
- if isinstance(attr, str):
- prop = self.parent.get_property(attr, _configure_mappers=False)
- elif isinstance(attr, schema.Column):
- prop = self.parent._columntoproperty[attr]
- elif isinstance(attr, MappedColumn):
- prop = self.parent._columntoproperty[attr.column]
- elif isinstance(attr, attributes.InstrumentedAttribute):
- prop = attr.property
- else:
- prop = None
-
- if not isinstance(prop, MapperProperty):
- raise sa_exc.ArgumentError(
- "Composite expects Column objects or mapped "
- f"attributes/attribute names as arguments, got: {attr!r}"
- )
-
- props.append(prop)
- return props
-
- @util.non_memoized_property
- @util.preload_module("orm.properties")
- def columns(self) -> Sequence[Column[Any]]:
- MappedColumn = util.preloaded.orm_properties.MappedColumn
- return [
- a.column if isinstance(a, MappedColumn) else a
- for a in self.attrs
- if isinstance(a, (schema.Column, MappedColumn))
- ]
-
- @property
- def mapper_property_to_assign(self) -> Optional[MapperProperty[_CC]]:
- return self
-
- @property
- def columns_to_assign(self) -> List[Tuple[schema.Column[Any], int]]:
- return [(c, 0) for c in self.columns if c.table is None]
-
- @util.preload_module("orm.properties")
- def _setup_arguments_on_columns(self) -> None:
- """Propagate configuration arguments made on this composite
- to the target columns, for those that apply.
-
- """
- ColumnProperty = util.preloaded.orm_properties.ColumnProperty
-
- for prop in self.props:
- if not isinstance(prop, ColumnProperty):
- continue
- else:
- cprop = prop
-
- cprop.active_history = self.active_history
- if self.deferred:
- cprop.deferred = self.deferred
- cprop.strategy_key = (("deferred", True), ("instrument", True))
- cprop.group = self.group
-
- def _setup_event_handlers(self) -> None:
- """Establish events that populate/expire the composite attribute."""
-
- def load_handler(
- state: InstanceState[Any], context: ORMCompileState
- ) -> None:
- _load_refresh_handler(state, context, None, is_refresh=False)
-
- def refresh_handler(
- state: InstanceState[Any],
- context: ORMCompileState,
- to_load: Optional[Sequence[str]],
- ) -> None:
- # note this corresponds to sqlalchemy.ext.mutable load_attrs()
-
- if not to_load or (
- {self.key}.union(self._attribute_keys)
- ).intersection(to_load):
- _load_refresh_handler(state, context, to_load, is_refresh=True)
-
- def _load_refresh_handler(
- state: InstanceState[Any],
- context: ORMCompileState,
- to_load: Optional[Sequence[str]],
- is_refresh: bool,
- ) -> None:
- dict_ = state.dict
-
- # if context indicates we are coming from the
- # fget() handler, this already set the value; skip the
- # handler here. (other handlers like mutablecomposite will still
- # want to catch it)
- # there's an insufficiency here in that the fget() handler
- # really should not be using the refresh event and there should
- # be some other event that mutablecomposite can subscribe
- # towards for this.
-
- if (
- not is_refresh or context is self._COMPOSITE_FGET
- ) and self.key in dict_:
- return
-
- # if column elements aren't loaded, skip.
- # __get__() will initiate a load for those
- # columns
- for k in self._attribute_keys:
- if k not in dict_:
- return
-
- dict_[self.key] = self.composite_class(
- *[state.dict[key] for key in self._attribute_keys]
- )
-
- def expire_handler(
- state: InstanceState[Any], keys: Optional[Sequence[str]]
- ) -> None:
- if keys is None or set(self._attribute_keys).intersection(keys):
- state.dict.pop(self.key, None)
-
- def insert_update_handler(
- mapper: Mapper[Any],
- connection: Connection,
- state: InstanceState[Any],
- ) -> None:
- """After an insert or update, some columns may be expired due
- to server side defaults, or re-populated due to client side
- defaults. Pop out the composite value here so that it
- recreates.
-
- """
-
- state.dict.pop(self.key, None)
-
- event.listen(
- self.parent, "after_insert", insert_update_handler, raw=True
- )
- event.listen(
- self.parent, "after_update", insert_update_handler, raw=True
- )
- event.listen(
- self.parent, "load", load_handler, raw=True, propagate=True
- )
- event.listen(
- self.parent, "refresh", refresh_handler, raw=True, propagate=True
- )
- event.listen(
- self.parent, "expire", expire_handler, raw=True, propagate=True
- )
-
- proxy_attr = self.parent.class_manager[self.key]
- proxy_attr.impl.dispatch = proxy_attr.dispatch # type: ignore
- proxy_attr.impl.dispatch._active_history = self.active_history
-
- # TODO: need a deserialize hook here
-
- @util.memoized_property
- def _attribute_keys(self) -> Sequence[str]:
- return [prop.key for prop in self.props]
-
- def _populate_composite_bulk_save_mappings_fn(
- self,
- ) -> Callable[[Dict[str, Any]], None]:
- if self._generated_composite_accessor:
- get_values = self._generated_composite_accessor
- else:
-
- def get_values(val: Any) -> Tuple[Any]:
- return val.__composite_values__() # type: ignore
-
- attrs = [prop.key for prop in self.props]
-
- def populate(dest_dict: Dict[str, Any]) -> None:
- dest_dict.update(
- {
- key: val
- for key, val in zip(
- attrs, get_values(dest_dict.pop(self.key))
- )
- }
- )
-
- return populate
-
- def get_history(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- ) -> History:
- """Provided for userland code that uses attributes.get_history()."""
-
- added: List[Any] = []
- deleted: List[Any] = []
-
- has_history = False
- for prop in self.props:
- key = prop.key
- hist = state.manager[key].impl.get_history(state, dict_)
- if hist.has_changes():
- has_history = True
-
- non_deleted = hist.non_deleted()
- if non_deleted:
- added.extend(non_deleted)
- else:
- added.append(None)
- if hist.deleted:
- deleted.extend(hist.deleted)
- else:
- deleted.append(None)
-
- if has_history:
- return attributes.History(
- [self.composite_class(*added)],
- (),
- [self.composite_class(*deleted)],
- )
- else:
- return attributes.History((), [self.composite_class(*added)], ())
-
- def _comparator_factory(
- self, mapper: Mapper[Any]
- ) -> Composite.Comparator[_CC]:
- return self.comparator_factory(self, mapper)
-
- class CompositeBundle(orm_util.Bundle[_T]):
- def __init__(
- self,
- property_: Composite[_T],
- expr: ClauseList,
- ):
- self.property = property_
- super().__init__(property_.key, *expr)
-
- def create_row_processor(
- self,
- query: Select[Any],
- procs: Sequence[Callable[[Row[Any]], Any]],
- labels: Sequence[str],
- ) -> Callable[[Row[Any]], Any]:
- def proc(row: Row[Any]) -> Any:
- return self.property.composite_class(
- *[proc(row) for proc in procs]
- )
-
- return proc
-
- class Comparator(PropComparator[_PT]):
- """Produce boolean, comparison, and other operators for
- :class:`.Composite` attributes.
-
- See the example in :ref:`composite_operations` for an overview
- of usage , as well as the documentation for :class:`.PropComparator`.
-
- .. seealso::
-
- :class:`.PropComparator`
-
- :class:`.ColumnOperators`
-
- :ref:`types_operators`
-
- :attr:`.TypeEngine.comparator_factory`
-
- """
-
- # https://github.com/python/mypy/issues/4266
- __hash__ = None # type: ignore
-
- prop: RODescriptorReference[Composite[_PT]]
-
- @util.memoized_property
- def clauses(self) -> ClauseList:
- return expression.ClauseList(
- group=False, *self._comparable_elements
- )
-
- def __clause_element__(self) -> CompositeProperty.CompositeBundle[_PT]:
- return self.expression
-
- @util.memoized_property
- def expression(self) -> CompositeProperty.CompositeBundle[_PT]:
- clauses = self.clauses._annotate(
- {
- "parententity": self._parententity,
- "parentmapper": self._parententity,
- "proxy_key": self.prop.key,
- }
- )
- return CompositeProperty.CompositeBundle(self.prop, clauses)
-
- def _bulk_update_tuples(
- self, value: Any
- ) -> Sequence[Tuple[_DMLColumnArgument, Any]]:
- if isinstance(value, BindParameter):
- value = value.value
-
- values: Sequence[Any]
-
- if value is None:
- values = [None for key in self.prop._attribute_keys]
- elif isinstance(self.prop.composite_class, type) and isinstance(
- value, self.prop.composite_class
- ):
- values = self.prop._composite_values_from_instance(value)
- else:
- raise sa_exc.ArgumentError(
- "Can't UPDATE composite attribute %s to %r"
- % (self.prop, value)
- )
-
- return list(zip(self._comparable_elements, values))
-
- @util.memoized_property
- def _comparable_elements(self) -> Sequence[QueryableAttribute[Any]]:
- if self._adapt_to_entity:
- return [
- getattr(self._adapt_to_entity.entity, prop.key)
- for prop in self.prop._comparable_elements
- ]
- else:
- return self.prop._comparable_elements
-
- def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
- return self._compare(operators.eq, other)
-
- def __ne__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
- return self._compare(operators.ne, other)
-
- def __lt__(self, other: Any) -> ColumnElement[bool]:
- return self._compare(operators.lt, other)
-
- def __gt__(self, other: Any) -> ColumnElement[bool]:
- return self._compare(operators.gt, other)
-
- def __le__(self, other: Any) -> ColumnElement[bool]:
- return self._compare(operators.le, other)
-
- def __ge__(self, other: Any) -> ColumnElement[bool]:
- return self._compare(operators.ge, other)
-
- # what might be interesting would be if we create
- # an instance of the composite class itself with
- # the columns as data members, then use "hybrid style" comparison
- # to create these comparisons. then your Point.__eq__() method could
- # be where comparison behavior is defined for SQL also. Likely
- # not a good choice for default behavior though, not clear how it would
- # work w/ dataclasses, etc. also no demand for any of this anyway.
- def _compare(
- self, operator: OperatorType, other: Any
- ) -> ColumnElement[bool]:
- values: Sequence[Any]
- if other is None:
- values = [None] * len(self.prop._comparable_elements)
- else:
- values = self.prop._composite_values_from_instance(other)
- comparisons = [
- operator(a, b)
- for a, b in zip(self.prop._comparable_elements, values)
- ]
- if self._adapt_to_entity:
- assert self.adapter is not None
- comparisons = [self.adapter(x) for x in comparisons]
- return sql.and_(*comparisons)
-
- def __str__(self) -> str:
- return str(self.parent.class_.__name__) + "." + self.key
-
-
-class Composite(CompositeProperty[_T], _DeclarativeMapped[_T]):
- """Declarative-compatible front-end for the :class:`.CompositeProperty`
- class.
-
- Public constructor is the :func:`_orm.composite` function.
-
- .. versionchanged:: 2.0 Added :class:`_orm.Composite` as a Declarative
- compatible subclass of :class:`_orm.CompositeProperty`.
-
- .. seealso::
-
- :ref:`mapper_composite`
-
- """
-
- inherit_cache = True
- """:meta private:"""
-
-
-class ConcreteInheritedProperty(DescriptorProperty[_T]):
- """A 'do nothing' :class:`.MapperProperty` that disables
- an attribute on a concrete subclass that is only present
- on the inherited mapper, not the concrete classes' mapper.
-
- Cases where this occurs include:
-
- * When the superclass mapper is mapped against a
- "polymorphic union", which includes all attributes from
- all subclasses.
- * When a relationship() is configured on an inherited mapper,
- but not on the subclass mapper. Concrete mappers require
- that relationship() is configured explicitly on each
- subclass.
-
- """
-
- def _comparator_factory(
- self, mapper: Mapper[Any]
- ) -> Type[PropComparator[_T]]:
- comparator_callable = None
-
- for m in self.parent.iterate_to_root():
- p = m._props[self.key]
- if getattr(p, "comparator_factory", None) is not None:
- comparator_callable = p.comparator_factory
- break
- assert comparator_callable is not None
- return comparator_callable(p, mapper) # type: ignore
-
- def __init__(self) -> None:
- super().__init__()
-
- def warn() -> NoReturn:
- raise AttributeError(
- "Concrete %s does not implement "
- "attribute %r at the instance level. Add "
- "this property explicitly to %s."
- % (self.parent, self.key, self.parent)
- )
-
- class NoninheritedConcreteProp:
- def __set__(s: Any, obj: Any, value: Any) -> NoReturn:
- warn()
-
- def __delete__(s: Any, obj: Any) -> NoReturn:
- warn()
-
- def __get__(s: Any, obj: Any, owner: Any) -> Any:
- if obj is None:
- return self.descriptor
- warn()
-
- self.descriptor = NoninheritedConcreteProp()
-
-
-class SynonymProperty(DescriptorProperty[_T]):
- """Denote an attribute name as a synonym to a mapped property,
- in that the attribute will mirror the value and expression behavior
- of another attribute.
-
- :class:`.Synonym` is constructed using the :func:`_orm.synonym`
- function.
-
- .. seealso::
-
- :ref:`synonyms` - Overview of synonyms
-
- """
-
- comparator_factory: Optional[Type[PropComparator[_T]]]
-
- def __init__(
- self,
- name: str,
- map_column: Optional[bool] = None,
- descriptor: Optional[Any] = None,
- comparator_factory: Optional[Type[PropComparator[_T]]] = None,
- attribute_options: Optional[_AttributeOptions] = None,
- info: Optional[_InfoType] = None,
- doc: Optional[str] = None,
- ):
- super().__init__(attribute_options=attribute_options)
-
- self.name = name
- self.map_column = map_column
- self.descriptor = descriptor
- self.comparator_factory = comparator_factory
- if doc:
- self.doc = doc
- elif descriptor and descriptor.__doc__:
- self.doc = descriptor.__doc__
- else:
- self.doc = None
- if info:
- self.info.update(info)
-
- util.set_creation_order(self)
-
- if not TYPE_CHECKING:
-
- @property
- def uses_objects(self) -> bool:
- return getattr(self.parent.class_, self.name).impl.uses_objects
-
- # TODO: when initialized, check _proxied_object,
- # emit a warning if its not a column-based property
-
- @util.memoized_property
- def _proxied_object(
- self,
- ) -> Union[MapperProperty[_T], SQLORMOperations[_T]]:
- attr = getattr(self.parent.class_, self.name)
- if not hasattr(attr, "property") or not isinstance(
- attr.property, MapperProperty
- ):
- # attribute is a non-MapperProprerty proxy such as
- # hybrid or association proxy
- if isinstance(attr, attributes.QueryableAttribute):
- return attr.comparator
- elif isinstance(attr, SQLORMOperations):
- # assocaition proxy comes here
- return attr
-
- raise sa_exc.InvalidRequestError(
- """synonym() attribute "%s.%s" only supports """
- """ORM mapped attributes, got %r"""
- % (self.parent.class_.__name__, self.name, attr)
- )
- return attr.property
-
- def _comparator_factory(self, mapper: Mapper[Any]) -> SQLORMOperations[_T]:
- prop = self._proxied_object
-
- if isinstance(prop, MapperProperty):
- if self.comparator_factory:
- comp = self.comparator_factory(prop, mapper)
- else:
- comp = prop.comparator_factory(prop, mapper)
- return comp
- else:
- return prop
-
- def get_history(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- ) -> History:
- attr: QueryableAttribute[Any] = getattr(self.parent.class_, self.name)
- return attr.impl.get_history(state, dict_, passive=passive)
-
- @util.preload_module("sqlalchemy.orm.properties")
- def set_parent(self, parent: Mapper[Any], init: bool) -> None:
- properties = util.preloaded.orm_properties
-
- if self.map_column:
- # implement the 'map_column' option.
- if self.key not in parent.persist_selectable.c:
- raise sa_exc.ArgumentError(
- "Can't compile synonym '%s': no column on table "
- "'%s' named '%s'"
- % (
- self.name,
- parent.persist_selectable.description,
- self.key,
- )
- )
- elif (
- parent.persist_selectable.c[self.key]
- in parent._columntoproperty
- and parent._columntoproperty[
- parent.persist_selectable.c[self.key]
- ].key
- == self.name
- ):
- raise sa_exc.ArgumentError(
- "Can't call map_column=True for synonym %r=%r, "
- "a ColumnProperty already exists keyed to the name "
- "%r for column %r"
- % (self.key, self.name, self.name, self.key)
- )
- p: ColumnProperty[Any] = properties.ColumnProperty(
- parent.persist_selectable.c[self.key]
- )
- parent._configure_property(self.name, p, init=init, setparent=True)
- p._mapped_by_synonym = self.key
-
- self.parent = parent
-
-
-class Synonym(SynonymProperty[_T], _DeclarativeMapped[_T]):
- """Declarative front-end for the :class:`.SynonymProperty` class.
-
- Public constructor is the :func:`_orm.synonym` function.
-
- .. versionchanged:: 2.0 Added :class:`_orm.Synonym` as a Declarative
- compatible subclass for :class:`_orm.SynonymProperty`
-
- .. seealso::
-
- :ref:`synonyms` - Overview of synonyms
-
- """
-
- inherit_cache = True
- """:meta private:"""
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/dynamic.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/dynamic.py
deleted file mode 100644
index 7496e5c..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/dynamic.py
+++ /dev/null
@@ -1,298 +0,0 @@
-# orm/dynamic.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
-
-
-"""Dynamic collection API.
-
-Dynamic collections act like Query() objects for read operations and support
-basic add/delete mutation.
-
-.. legacy:: the "dynamic" loader is a legacy feature, superseded by the
- "write_only" loader.
-
-
-"""
-
-from __future__ import annotations
-
-from typing import Any
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from . import attributes
-from . import exc as orm_exc
-from . import relationships
-from . import util as orm_util
-from .base import PassiveFlag
-from .query import Query
-from .session import object_session
-from .writeonly import AbstractCollectionWriter
-from .writeonly import WriteOnlyAttributeImpl
-from .writeonly import WriteOnlyHistory
-from .writeonly import WriteOnlyLoader
-from .. import util
-from ..engine import result
-
-
-if TYPE_CHECKING:
- from . import QueryableAttribute
- from .mapper import Mapper
- from .relationships import _RelationshipOrderByArg
- from .session import Session
- from .state import InstanceState
- from .util import AliasedClass
- from ..event import _Dispatch
- from ..sql.elements import ColumnElement
-
-_T = TypeVar("_T", bound=Any)
-
-
-class DynamicCollectionHistory(WriteOnlyHistory[_T]):
- def __init__(
- self,
- attr: DynamicAttributeImpl,
- state: InstanceState[_T],
- passive: PassiveFlag,
- apply_to: Optional[DynamicCollectionHistory[_T]] = None,
- ) -> None:
- if apply_to:
- coll = AppenderQuery(attr, state).autoflush(False)
- self.unchanged_items = util.OrderedIdentitySet(coll)
- self.added_items = apply_to.added_items
- self.deleted_items = apply_to.deleted_items
- self._reconcile_collection = True
- else:
- self.deleted_items = util.OrderedIdentitySet()
- self.added_items = util.OrderedIdentitySet()
- self.unchanged_items = util.OrderedIdentitySet()
- self._reconcile_collection = False
-
-
-class DynamicAttributeImpl(WriteOnlyAttributeImpl):
- _supports_dynamic_iteration = True
- collection_history_cls = DynamicCollectionHistory[Any]
- query_class: Type[AppenderMixin[Any]] # type: ignore[assignment]
-
- def __init__(
- self,
- class_: Union[Type[Any], AliasedClass[Any]],
- key: str,
- dispatch: _Dispatch[QueryableAttribute[Any]],
- target_mapper: Mapper[_T],
- order_by: _RelationshipOrderByArg,
- query_class: Optional[Type[AppenderMixin[_T]]] = None,
- **kw: Any,
- ) -> None:
- attributes.AttributeImpl.__init__(
- self, class_, key, None, dispatch, **kw
- )
- self.target_mapper = target_mapper
- if order_by:
- self.order_by = tuple(order_by)
- if not query_class:
- self.query_class = AppenderQuery
- elif AppenderMixin in query_class.mro():
- self.query_class = query_class
- else:
- self.query_class = mixin_user_query(query_class)
-
-
-@relationships.RelationshipProperty.strategy_for(lazy="dynamic")
-class DynaLoader(WriteOnlyLoader):
- impl_class = DynamicAttributeImpl
-
-
-class AppenderMixin(AbstractCollectionWriter[_T]):
- """A mixin that expects to be mixing in a Query class with
- AbstractAppender.
-
-
- """
-
- query_class: Optional[Type[Query[_T]]] = None
- _order_by_clauses: Tuple[ColumnElement[Any], ...]
-
- def __init__(
- self, attr: DynamicAttributeImpl, state: InstanceState[_T]
- ) -> None:
- Query.__init__(
- self, # type: ignore[arg-type]
- attr.target_mapper,
- None,
- )
- super().__init__(attr, state)
-
- @property
- def session(self) -> Optional[Session]:
- sess = object_session(self.instance)
- if sess is not None and sess.autoflush and self.instance in sess:
- sess.flush()
- if not orm_util.has_identity(self.instance):
- return None
- else:
- return sess
-
- @session.setter
- def session(self, session: Session) -> None:
- self.sess = session
-
- def _iter(self) -> Union[result.ScalarResult[_T], result.Result[_T]]:
- sess = self.session
- if sess is None:
- state = attributes.instance_state(self.instance)
- if state.detached:
- util.warn(
- "Instance %s is detached, dynamic relationship cannot "
- "return a correct result. This warning will become "
- "a DetachedInstanceError in a future release."
- % (orm_util.state_str(state))
- )
-
- return result.IteratorResult(
- result.SimpleResultMetaData([self.attr.class_.__name__]),
- self.attr._get_collection_history( # type: ignore[arg-type]
- attributes.instance_state(self.instance),
- PassiveFlag.PASSIVE_NO_INITIALIZE,
- ).added_items,
- _source_supports_scalars=True,
- ).scalars()
- else:
- return self._generate(sess)._iter()
-
- if TYPE_CHECKING:
-
- def __iter__(self) -> Iterator[_T]: ...
-
- def __getitem__(self, index: Any) -> Union[_T, List[_T]]:
- sess = self.session
- if sess is None:
- return self.attr._get_collection_history(
- attributes.instance_state(self.instance),
- PassiveFlag.PASSIVE_NO_INITIALIZE,
- ).indexed(index)
- else:
- return self._generate(sess).__getitem__(index) # type: ignore[no-any-return] # noqa: E501
-
- def count(self) -> int:
- sess = self.session
- if sess is None:
- return len(
- self.attr._get_collection_history(
- attributes.instance_state(self.instance),
- PassiveFlag.PASSIVE_NO_INITIALIZE,
- ).added_items
- )
- else:
- return self._generate(sess).count()
-
- def _generate(
- self,
- sess: Optional[Session] = None,
- ) -> Query[_T]:
- # note we're returning an entirely new Query class instance
- # here without any assignment capabilities; the class of this
- # query is determined by the session.
- instance = self.instance
- if sess is None:
- sess = object_session(instance)
- if sess is None:
- raise orm_exc.DetachedInstanceError(
- "Parent instance %s is not bound to a Session, and no "
- "contextual session is established; lazy load operation "
- "of attribute '%s' cannot proceed"
- % (orm_util.instance_str(instance), self.attr.key)
- )
-
- if self.query_class:
- query = self.query_class(self.attr.target_mapper, session=sess)
- else:
- query = sess.query(self.attr.target_mapper)
-
- query._where_criteria = self._where_criteria
- query._from_obj = self._from_obj
- query._order_by_clauses = self._order_by_clauses
-
- return query
-
- def add_all(self, iterator: Iterable[_T]) -> None:
- """Add an iterable of items to this :class:`_orm.AppenderQuery`.
-
- The given items will be persisted to the database in terms of
- the parent instance's collection on the next flush.
-
- This method is provided to assist in delivering forwards-compatibility
- with the :class:`_orm.WriteOnlyCollection` collection class.
-
- .. versionadded:: 2.0
-
- """
- self._add_all_impl(iterator)
-
- def add(self, item: _T) -> None:
- """Add an item to this :class:`_orm.AppenderQuery`.
-
- The given item will be persisted to the database in terms of
- the parent instance's collection on the next flush.
-
- This method is provided to assist in delivering forwards-compatibility
- with the :class:`_orm.WriteOnlyCollection` collection class.
-
- .. versionadded:: 2.0
-
- """
- self._add_all_impl([item])
-
- def extend(self, iterator: Iterable[_T]) -> None:
- """Add an iterable of items to this :class:`_orm.AppenderQuery`.
-
- The given items will be persisted to the database in terms of
- the parent instance's collection on the next flush.
-
- """
- self._add_all_impl(iterator)
-
- def append(self, item: _T) -> None:
- """Append an item to this :class:`_orm.AppenderQuery`.
-
- The given item will be persisted to the database in terms of
- the parent instance's collection on the next flush.
-
- """
- self._add_all_impl([item])
-
- def remove(self, item: _T) -> None:
- """Remove an item from this :class:`_orm.AppenderQuery`.
-
- The given item will be removed from the parent instance's collection on
- the next flush.
-
- """
- self._remove_impl(item)
-
-
-class AppenderQuery(AppenderMixin[_T], Query[_T]): # type: ignore[misc]
- """A dynamic query that supports basic collection storage operations.
-
- Methods on :class:`.AppenderQuery` include all methods of
- :class:`_orm.Query`, plus additional methods used for collection
- persistence.
-
-
- """
-
-
-def mixin_user_query(cls: Any) -> type[AppenderMixin[Any]]:
- """Return a new class with AppenderQuery functionality layered over."""
- name = "Appender" + cls.__name__
- return type(name, (AppenderMixin, cls), {"query_class": cls})
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/evaluator.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/evaluator.py
deleted file mode 100644
index f264454..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/evaluator.py
+++ /dev/null
@@ -1,368 +0,0 @@
-# orm/evaluator.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
-# mypy: ignore-errors
-
-"""Evaluation functions used **INTERNALLY** by ORM DML use cases.
-
-
-This module is **private, for internal use by SQLAlchemy**.
-
-.. versionchanged:: 2.0.4 renamed ``EvaluatorCompiler`` to
- ``_EvaluatorCompiler``.
-
-"""
-
-
-from __future__ import annotations
-
-from typing import Type
-
-from . import exc as orm_exc
-from .base import LoaderCallableStatus
-from .base import PassiveFlag
-from .. import exc
-from .. import inspect
-from ..sql import and_
-from ..sql import operators
-from ..sql.sqltypes import Integer
-from ..sql.sqltypes import Numeric
-from ..util import warn_deprecated
-
-
-class UnevaluatableError(exc.InvalidRequestError):
- pass
-
-
-class _NoObject(operators.ColumnOperators):
- def operate(self, *arg, **kw):
- return None
-
- def reverse_operate(self, *arg, **kw):
- return None
-
-
-class _ExpiredObject(operators.ColumnOperators):
- def operate(self, *arg, **kw):
- return self
-
- def reverse_operate(self, *arg, **kw):
- return self
-
-
-_NO_OBJECT = _NoObject()
-_EXPIRED_OBJECT = _ExpiredObject()
-
-
-class _EvaluatorCompiler:
- def __init__(self, target_cls=None):
- self.target_cls = target_cls
-
- def process(self, clause, *clauses):
- if clauses:
- clause = and_(clause, *clauses)
-
- meth = getattr(self, f"visit_{clause.__visit_name__}", None)
- if not meth:
- raise UnevaluatableError(
- f"Cannot evaluate {type(clause).__name__}"
- )
- return meth(clause)
-
- def visit_grouping(self, clause):
- return self.process(clause.element)
-
- def visit_null(self, clause):
- return lambda obj: None
-
- def visit_false(self, clause):
- return lambda obj: False
-
- def visit_true(self, clause):
- return lambda obj: True
-
- def visit_column(self, clause):
- try:
- parentmapper = clause._annotations["parentmapper"]
- except KeyError as ke:
- raise UnevaluatableError(
- f"Cannot evaluate column: {clause}"
- ) from ke
-
- if self.target_cls and not issubclass(
- self.target_cls, parentmapper.class_
- ):
- raise UnevaluatableError(
- "Can't evaluate criteria against "
- f"alternate class {parentmapper.class_}"
- )
-
- parentmapper._check_configure()
-
- # we'd like to use "proxy_key" annotation to get the "key", however
- # in relationship primaryjoin cases proxy_key is sometimes deannotated
- # and sometimes apparently not present in the first place (?).
- # While I can stop it from being deannotated (though need to see if
- # this breaks other things), not sure right now about cases where it's
- # not there in the first place. can fix at some later point.
- # key = clause._annotations["proxy_key"]
-
- # for now, use the old way
- try:
- key = parentmapper._columntoproperty[clause].key
- except orm_exc.UnmappedColumnError as err:
- raise UnevaluatableError(
- f"Cannot evaluate expression: {err}"
- ) from err
-
- # note this used to fall back to a simple `getattr(obj, key)` evaluator
- # if impl was None; as of #8656, we ensure mappers are configured
- # so that impl is available
- impl = parentmapper.class_manager[key].impl
-
- def get_corresponding_attr(obj):
- if obj is None:
- return _NO_OBJECT
- state = inspect(obj)
- dict_ = state.dict
-
- value = impl.get(
- state, dict_, passive=PassiveFlag.PASSIVE_NO_FETCH
- )
- if value is LoaderCallableStatus.PASSIVE_NO_RESULT:
- return _EXPIRED_OBJECT
- return value
-
- return get_corresponding_attr
-
- def visit_tuple(self, clause):
- return self.visit_clauselist(clause)
-
- def visit_expression_clauselist(self, clause):
- return self.visit_clauselist(clause)
-
- def visit_clauselist(self, clause):
- evaluators = [self.process(clause) for clause in clause.clauses]
-
- dispatch = (
- f"visit_{clause.operator.__name__.rstrip('_')}_clauselist_op"
- )
- meth = getattr(self, dispatch, None)
- if meth:
- return meth(clause.operator, evaluators, clause)
- else:
- raise UnevaluatableError(
- f"Cannot evaluate clauselist with operator {clause.operator}"
- )
-
- def visit_binary(self, clause):
- eval_left = self.process(clause.left)
- eval_right = self.process(clause.right)
-
- dispatch = f"visit_{clause.operator.__name__.rstrip('_')}_binary_op"
- meth = getattr(self, dispatch, None)
- if meth:
- return meth(clause.operator, eval_left, eval_right, clause)
- else:
- raise UnevaluatableError(
- f"Cannot evaluate {type(clause).__name__} with "
- f"operator {clause.operator}"
- )
-
- def visit_or_clauselist_op(self, operator, evaluators, clause):
- def evaluate(obj):
- has_null = False
- for sub_evaluate in evaluators:
- value = sub_evaluate(obj)
- if value is _EXPIRED_OBJECT:
- return _EXPIRED_OBJECT
- elif value:
- return True
- has_null = has_null or value is None
- if has_null:
- return None
- return False
-
- return evaluate
-
- def visit_and_clauselist_op(self, operator, evaluators, clause):
- def evaluate(obj):
- for sub_evaluate in evaluators:
- value = sub_evaluate(obj)
- if value is _EXPIRED_OBJECT:
- return _EXPIRED_OBJECT
-
- if not value:
- if value is None or value is _NO_OBJECT:
- return None
- return False
- return True
-
- return evaluate
-
- def visit_comma_op_clauselist_op(self, operator, evaluators, clause):
- def evaluate(obj):
- values = []
- for sub_evaluate in evaluators:
- value = sub_evaluate(obj)
- if value is _EXPIRED_OBJECT:
- return _EXPIRED_OBJECT
- elif value is None or value is _NO_OBJECT:
- return None
- values.append(value)
- return tuple(values)
-
- return evaluate
-
- def visit_custom_op_binary_op(
- self, operator, eval_left, eval_right, clause
- ):
- if operator.python_impl:
- return self._straight_evaluate(
- operator, eval_left, eval_right, clause
- )
- else:
- raise UnevaluatableError(
- f"Custom operator {operator.opstring!r} can't be evaluated "
- "in Python unless it specifies a callable using "
- "`.python_impl`."
- )
-
- def visit_is_binary_op(self, operator, eval_left, eval_right, clause):
- def evaluate(obj):
- left_val = eval_left(obj)
- right_val = eval_right(obj)
- if left_val is _EXPIRED_OBJECT or right_val is _EXPIRED_OBJECT:
- return _EXPIRED_OBJECT
- return left_val == right_val
-
- return evaluate
-
- def visit_is_not_binary_op(self, operator, eval_left, eval_right, clause):
- def evaluate(obj):
- left_val = eval_left(obj)
- right_val = eval_right(obj)
- if left_val is _EXPIRED_OBJECT or right_val is _EXPIRED_OBJECT:
- return _EXPIRED_OBJECT
- return left_val != right_val
-
- return evaluate
-
- def _straight_evaluate(self, operator, eval_left, eval_right, clause):
- def evaluate(obj):
- left_val = eval_left(obj)
- right_val = eval_right(obj)
- if left_val is _EXPIRED_OBJECT or right_val is _EXPIRED_OBJECT:
- return _EXPIRED_OBJECT
- elif left_val is None or right_val is None:
- return None
-
- return operator(eval_left(obj), eval_right(obj))
-
- return evaluate
-
- def _straight_evaluate_numeric_only(
- self, operator, eval_left, eval_right, clause
- ):
- if clause.left.type._type_affinity not in (
- Numeric,
- Integer,
- ) or clause.right.type._type_affinity not in (Numeric, Integer):
- raise UnevaluatableError(
- f'Cannot evaluate math operator "{operator.__name__}" for '
- f"datatypes {clause.left.type}, {clause.right.type}"
- )
-
- return self._straight_evaluate(operator, eval_left, eval_right, clause)
-
- visit_add_binary_op = _straight_evaluate_numeric_only
- visit_mul_binary_op = _straight_evaluate_numeric_only
- visit_sub_binary_op = _straight_evaluate_numeric_only
- visit_mod_binary_op = _straight_evaluate_numeric_only
- visit_truediv_binary_op = _straight_evaluate_numeric_only
- visit_lt_binary_op = _straight_evaluate
- visit_le_binary_op = _straight_evaluate
- visit_ne_binary_op = _straight_evaluate
- visit_gt_binary_op = _straight_evaluate
- visit_ge_binary_op = _straight_evaluate
- visit_eq_binary_op = _straight_evaluate
-
- def visit_in_op_binary_op(self, operator, eval_left, eval_right, clause):
- return self._straight_evaluate(
- lambda a, b: a in b if a is not _NO_OBJECT else None,
- eval_left,
- eval_right,
- clause,
- )
-
- def visit_not_in_op_binary_op(
- self, operator, eval_left, eval_right, clause
- ):
- return self._straight_evaluate(
- lambda a, b: a not in b if a is not _NO_OBJECT else None,
- eval_left,
- eval_right,
- clause,
- )
-
- def visit_concat_op_binary_op(
- self, operator, eval_left, eval_right, clause
- ):
- return self._straight_evaluate(
- lambda a, b: a + b, eval_left, eval_right, clause
- )
-
- def visit_startswith_op_binary_op(
- self, operator, eval_left, eval_right, clause
- ):
- return self._straight_evaluate(
- lambda a, b: a.startswith(b), eval_left, eval_right, clause
- )
-
- def visit_endswith_op_binary_op(
- self, operator, eval_left, eval_right, clause
- ):
- return self._straight_evaluate(
- lambda a, b: a.endswith(b), eval_left, eval_right, clause
- )
-
- def visit_unary(self, clause):
- eval_inner = self.process(clause.element)
- if clause.operator is operators.inv:
-
- def evaluate(obj):
- value = eval_inner(obj)
- if value is _EXPIRED_OBJECT:
- return _EXPIRED_OBJECT
- elif value is None:
- return None
- return not value
-
- return evaluate
- raise UnevaluatableError(
- f"Cannot evaluate {type(clause).__name__} "
- f"with operator {clause.operator}"
- )
-
- def visit_bindparam(self, clause):
- if clause.callable:
- val = clause.callable()
- else:
- val = clause.value
- return lambda obj: val
-
-
-def __getattr__(name: str) -> Type[_EvaluatorCompiler]:
- if name == "EvaluatorCompiler":
- warn_deprecated(
- "Direct use of 'EvaluatorCompiler' is not supported, and this "
- "name will be removed in a future release. "
- "'_EvaluatorCompiler' is for internal use only",
- "2.0",
- )
- return _EvaluatorCompiler
- else:
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/events.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/events.py
deleted file mode 100644
index 1cd51bf..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/events.py
+++ /dev/null
@@ -1,3259 +0,0 @@
-# orm/events.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
-
-"""ORM event interfaces.
-
-"""
-from __future__ import annotations
-
-from typing import Any
-from typing import Callable
-from typing import Collection
-from typing import Dict
-from typing import Generic
-from typing import Iterable
-from typing import Optional
-from typing import Sequence
-from typing import Set
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-import weakref
-
-from . import instrumentation
-from . import interfaces
-from . import mapperlib
-from .attributes import QueryableAttribute
-from .base import _mapper_or_none
-from .base import NO_KEY
-from .instrumentation import ClassManager
-from .instrumentation import InstrumentationFactory
-from .query import BulkDelete
-from .query import BulkUpdate
-from .query import Query
-from .scoping import scoped_session
-from .session import Session
-from .session import sessionmaker
-from .. import event
-from .. import exc
-from .. import util
-from ..event import EventTarget
-from ..event.registry import _ET
-from ..util.compat import inspect_getfullargspec
-
-if TYPE_CHECKING:
- from weakref import ReferenceType
-
- from ._typing import _InstanceDict
- from ._typing import _InternalEntityType
- from ._typing import _O
- from ._typing import _T
- from .attributes import Event
- from .base import EventConstants
- from .session import ORMExecuteState
- from .session import SessionTransaction
- from .unitofwork import UOWTransaction
- from ..engine import Connection
- from ..event.base import _Dispatch
- from ..event.base import _HasEventsDispatch
- from ..event.registry import _EventKey
- from ..orm.collections import CollectionAdapter
- from ..orm.context import QueryContext
- from ..orm.decl_api import DeclarativeAttributeIntercept
- from ..orm.decl_api import DeclarativeMeta
- from ..orm.mapper import Mapper
- from ..orm.state import InstanceState
-
-_KT = TypeVar("_KT", bound=Any)
-_ET2 = TypeVar("_ET2", bound=EventTarget)
-
-
-class InstrumentationEvents(event.Events[InstrumentationFactory]):
- """Events related to class instrumentation events.
-
- The listeners here support being established against
- any new style class, that is any object that is a subclass
- of 'type'. Events will then be fired off for events
- against that class. If the "propagate=True" flag is passed
- to event.listen(), the event will fire off for subclasses
- of that class as well.
-
- The Python ``type`` builtin is also accepted as a target,
- which when used has the effect of events being emitted
- for all classes.
-
- Note the "propagate" flag here is defaulted to ``True``,
- unlike the other class level events where it defaults
- to ``False``. This means that new subclasses will also
- be the subject of these events, when a listener
- is established on a superclass.
-
- """
-
- _target_class_doc = "SomeBaseClass"
- _dispatch_target = InstrumentationFactory
-
- @classmethod
- def _accept_with(
- cls,
- target: Union[
- InstrumentationFactory,
- Type[InstrumentationFactory],
- ],
- identifier: str,
- ) -> Optional[
- Union[
- InstrumentationFactory,
- Type[InstrumentationFactory],
- ]
- ]:
- if isinstance(target, type):
- return _InstrumentationEventsHold(target) # type: ignore [return-value] # noqa: E501
- else:
- return None
-
- @classmethod
- def _listen(
- cls, event_key: _EventKey[_T], propagate: bool = True, **kw: Any
- ) -> None:
- target, identifier, fn = (
- event_key.dispatch_target,
- event_key.identifier,
- event_key._listen_fn,
- )
-
- def listen(target_cls: type, *arg: Any) -> Optional[Any]:
- listen_cls = target()
-
- # if weakref were collected, however this is not something
- # that normally happens. it was occurring during test teardown
- # between mapper/registry/instrumentation_manager, however this
- # interaction was changed to not rely upon the event system.
- if listen_cls is None:
- return None
-
- if propagate and issubclass(target_cls, listen_cls):
- return fn(target_cls, *arg)
- elif not propagate and target_cls is listen_cls:
- return fn(target_cls, *arg)
- else:
- return None
-
- def remove(ref: ReferenceType[_T]) -> None:
- key = event.registry._EventKey( # type: ignore [type-var]
- None,
- identifier,
- listen,
- instrumentation._instrumentation_factory,
- )
- getattr(
- instrumentation._instrumentation_factory.dispatch, identifier
- ).remove(key)
-
- target = weakref.ref(target.class_, remove)
-
- event_key.with_dispatch_target(
- instrumentation._instrumentation_factory
- ).with_wrapper(listen).base_listen(**kw)
-
- @classmethod
- def _clear(cls) -> None:
- super()._clear()
- instrumentation._instrumentation_factory.dispatch._clear()
-
- def class_instrument(self, cls: ClassManager[_O]) -> None:
- """Called after the given class is instrumented.
-
- To get at the :class:`.ClassManager`, use
- :func:`.manager_of_class`.
-
- """
-
- def class_uninstrument(self, cls: ClassManager[_O]) -> None:
- """Called before the given class is uninstrumented.
-
- To get at the :class:`.ClassManager`, use
- :func:`.manager_of_class`.
-
- """
-
- def attribute_instrument(
- self, cls: ClassManager[_O], key: _KT, inst: _O
- ) -> None:
- """Called when an attribute is instrumented."""
-
-
-class _InstrumentationEventsHold:
- """temporary marker object used to transfer from _accept_with() to
- _listen() on the InstrumentationEvents class.
-
- """
-
- def __init__(self, class_: type) -> None:
- self.class_ = class_
-
- dispatch = event.dispatcher(InstrumentationEvents)
-
-
-class InstanceEvents(event.Events[ClassManager[Any]]):
- """Define events specific to object lifecycle.
-
- e.g.::
-
- from sqlalchemy import event
-
- def my_load_listener(target, context):
- print("on load!")
-
- event.listen(SomeClass, 'load', my_load_listener)
-
- Available targets include:
-
- * mapped classes
- * unmapped superclasses of mapped or to-be-mapped classes
- (using the ``propagate=True`` flag)
- * :class:`_orm.Mapper` objects
- * the :class:`_orm.Mapper` class itself indicates listening for all
- mappers.
-
- Instance events are closely related to mapper events, but
- are more specific to the instance and its instrumentation,
- rather than its system of persistence.
-
- When using :class:`.InstanceEvents`, several modifiers are
- available to the :func:`.event.listen` function.
-
- :param propagate=False: When True, the event listener should
- be applied to all inheriting classes as well as the
- class which is the target of this listener.
- :param raw=False: When True, the "target" argument passed
- to applicable event listener functions will be the
- instance's :class:`.InstanceState` management
- object, rather than the mapped instance itself.
- :param restore_load_context=False: Applies to the
- :meth:`.InstanceEvents.load` and :meth:`.InstanceEvents.refresh`
- events. Restores the loader context of the object when the event
- hook is complete, so that ongoing eager load operations continue
- to target the object appropriately. A warning is emitted if the
- object is moved to a new loader context from within one of these
- events if this flag is not set.
-
- .. versionadded:: 1.3.14
-
-
- """
-
- _target_class_doc = "SomeClass"
-
- _dispatch_target = ClassManager
-
- @classmethod
- def _new_classmanager_instance(
- cls,
- class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
- classmanager: ClassManager[_O],
- ) -> None:
- _InstanceEventsHold.populate(class_, classmanager)
-
- @classmethod
- @util.preload_module("sqlalchemy.orm")
- def _accept_with(
- cls,
- target: Union[
- ClassManager[Any],
- Type[ClassManager[Any]],
- ],
- identifier: str,
- ) -> Optional[Union[ClassManager[Any], Type[ClassManager[Any]]]]:
- orm = util.preloaded.orm
-
- if isinstance(target, ClassManager):
- return target
- elif isinstance(target, mapperlib.Mapper):
- return target.class_manager
- elif target is orm.mapper: # type: ignore [attr-defined]
- util.warn_deprecated(
- "The `sqlalchemy.orm.mapper()` symbol is deprecated and "
- "will be removed in a future release. For the mapper-wide "
- "event target, use the 'sqlalchemy.orm.Mapper' class.",
- "2.0",
- )
- return ClassManager
- elif isinstance(target, type):
- if issubclass(target, mapperlib.Mapper):
- return ClassManager
- else:
- manager = instrumentation.opt_manager_of_class(target)
- if manager:
- return manager
- else:
- return _InstanceEventsHold(target) # type: ignore [return-value] # noqa: E501
- return None
-
- @classmethod
- def _listen(
- cls,
- event_key: _EventKey[ClassManager[Any]],
- raw: bool = False,
- propagate: bool = False,
- restore_load_context: bool = False,
- **kw: Any,
- ) -> None:
- target, fn = (event_key.dispatch_target, event_key._listen_fn)
-
- if not raw or restore_load_context:
-
- def wrap(
- state: InstanceState[_O], *arg: Any, **kw: Any
- ) -> Optional[Any]:
- if not raw:
- target: Any = state.obj()
- else:
- target = state
- if restore_load_context:
- runid = state.runid
- try:
- return fn(target, *arg, **kw)
- finally:
- if restore_load_context:
- state.runid = runid
-
- event_key = event_key.with_wrapper(wrap)
-
- event_key.base_listen(propagate=propagate, **kw)
-
- if propagate:
- for mgr in target.subclass_managers(True):
- event_key.with_dispatch_target(mgr).base_listen(propagate=True)
-
- @classmethod
- def _clear(cls) -> None:
- super()._clear()
- _InstanceEventsHold._clear()
-
- def first_init(self, manager: ClassManager[_O], cls: Type[_O]) -> None:
- """Called when the first instance of a particular mapping is called.
-
- This event is called when the ``__init__`` method of a class
- is called the first time for that particular class. The event
- invokes before ``__init__`` actually proceeds as well as before
- the :meth:`.InstanceEvents.init` event is invoked.
-
- """
-
- def init(self, target: _O, args: Any, kwargs: Any) -> None:
- """Receive an instance when its constructor is called.
-
- This method is only called during a userland construction of
- an object, in conjunction with the object's constructor, e.g.
- its ``__init__`` method. It is not called when an object is
- loaded from the database; see the :meth:`.InstanceEvents.load`
- event in order to intercept a database load.
-
- The event is called before the actual ``__init__`` constructor
- of the object is called. The ``kwargs`` dictionary may be
- modified in-place in order to affect what is passed to
- ``__init__``.
-
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param args: positional arguments passed to the ``__init__`` method.
- This is passed as a tuple and is currently immutable.
- :param kwargs: keyword arguments passed to the ``__init__`` method.
- This structure *can* be altered in place.
-
- .. seealso::
-
- :meth:`.InstanceEvents.init_failure`
-
- :meth:`.InstanceEvents.load`
-
- """
-
- def init_failure(self, target: _O, args: Any, kwargs: Any) -> None:
- """Receive an instance when its constructor has been called,
- and raised an exception.
-
- This method is only called during a userland construction of
- an object, in conjunction with the object's constructor, e.g.
- its ``__init__`` method. It is not called when an object is loaded
- from the database.
-
- The event is invoked after an exception raised by the ``__init__``
- method is caught. After the event
- is invoked, the original exception is re-raised outwards, so that
- the construction of the object still raises an exception. The
- actual exception and stack trace raised should be present in
- ``sys.exc_info()``.
-
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param args: positional arguments that were passed to the ``__init__``
- method.
- :param kwargs: keyword arguments that were passed to the ``__init__``
- method.
-
- .. seealso::
-
- :meth:`.InstanceEvents.init`
-
- :meth:`.InstanceEvents.load`
-
- """
-
- def _sa_event_merge_wo_load(
- self, target: _O, context: QueryContext
- ) -> None:
- """receive an object instance after it was the subject of a merge()
- call, when load=False was passed.
-
- The target would be the already-loaded object in the Session which
- would have had its attributes overwritten by the incoming object. This
- overwrite operation does not use attribute events, instead just
- populating dict directly. Therefore the purpose of this event is so
- that extensions like sqlalchemy.ext.mutable know that object state has
- changed and incoming state needs to be set up for "parents" etc.
-
- This functionality is acceptable to be made public in a later release.
-
- .. versionadded:: 1.4.41
-
- """
-
- def load(self, target: _O, context: QueryContext) -> None:
- """Receive an object instance after it has been created via
- ``__new__``, and after initial attribute population has
- occurred.
-
- This typically occurs when the instance is created based on
- incoming result rows, and is only called once for that
- instance's lifetime.
-
- .. warning::
-
- During a result-row load, this event is invoked when the
- first row received for this instance is processed. When using
- eager loading with collection-oriented attributes, the additional
- rows that are to be loaded / processed in order to load subsequent
- collection items have not occurred yet. This has the effect
- both that collections will not be fully loaded, as well as that
- if an operation occurs within this event handler that emits
- another database load operation for the object, the "loading
- context" for the object can change and interfere with the
- existing eager loaders still in progress.
-
- Examples of what can cause the "loading context" to change within
- the event handler include, but are not necessarily limited to:
-
- * accessing deferred attributes that weren't part of the row,
- will trigger an "undefer" operation and refresh the object
-
- * accessing attributes on a joined-inheritance subclass that
- weren't part of the row, will trigger a refresh operation.
-
- As of SQLAlchemy 1.3.14, a warning is emitted when this occurs. The
- :paramref:`.InstanceEvents.restore_load_context` option may be
- used on the event to prevent this warning; this will ensure that
- the existing loading context is maintained for the object after the
- event is called::
-
- @event.listens_for(
- SomeClass, "load", restore_load_context=True)
- def on_load(instance, context):
- instance.some_unloaded_attribute
-
- .. versionchanged:: 1.3.14 Added
- :paramref:`.InstanceEvents.restore_load_context`
- and :paramref:`.SessionEvents.restore_load_context` flags which
- apply to "on load" events, which will ensure that the loading
- context for an object is restored when the event hook is
- complete; a warning is emitted if the load context of the object
- changes without this flag being set.
-
-
- The :meth:`.InstanceEvents.load` event is also available in a
- class-method decorator format called :func:`_orm.reconstructor`.
-
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param context: the :class:`.QueryContext` corresponding to the
- current :class:`_query.Query` in progress. This argument may be
- ``None`` if the load does not correspond to a :class:`_query.Query`,
- such as during :meth:`.Session.merge`.
-
- .. seealso::
-
- :ref:`mapped_class_load_events`
-
- :meth:`.InstanceEvents.init`
-
- :meth:`.InstanceEvents.refresh`
-
- :meth:`.SessionEvents.loaded_as_persistent`
-
- """
-
- def refresh(
- self, target: _O, context: QueryContext, attrs: Optional[Iterable[str]]
- ) -> None:
- """Receive an object instance after one or more attributes have
- been refreshed from a query.
-
- Contrast this to the :meth:`.InstanceEvents.load` method, which
- is invoked when the object is first loaded from a query.
-
- .. note:: This event is invoked within the loader process before
- eager loaders may have been completed, and the object's state may
- not be complete. Additionally, invoking row-level refresh
- operations on the object will place the object into a new loader
- context, interfering with the existing load context. See the note
- on :meth:`.InstanceEvents.load` for background on making use of the
- :paramref:`.InstanceEvents.restore_load_context` parameter, in
- order to resolve this scenario.
-
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param context: the :class:`.QueryContext` corresponding to the
- current :class:`_query.Query` in progress.
- :param attrs: sequence of attribute names which
- were populated, or None if all column-mapped, non-deferred
- attributes were populated.
-
- .. seealso::
-
- :ref:`mapped_class_load_events`
-
- :meth:`.InstanceEvents.load`
-
- """
-
- def refresh_flush(
- self,
- target: _O,
- flush_context: UOWTransaction,
- attrs: Optional[Iterable[str]],
- ) -> None:
- """Receive an object instance after one or more attributes that
- contain a column-level default or onupdate handler have been refreshed
- during persistence of the object's state.
-
- This event is the same as :meth:`.InstanceEvents.refresh` except
- it is invoked within the unit of work flush process, and includes
- only non-primary-key columns that have column level default or
- onupdate handlers, including Python callables as well as server side
- defaults and triggers which may be fetched via the RETURNING clause.
-
- .. note::
-
- While the :meth:`.InstanceEvents.refresh_flush` event is triggered
- for an object that was INSERTed as well as for an object that was
- UPDATEd, the event is geared primarily towards the UPDATE process;
- it is mostly an internal artifact that INSERT actions can also
- trigger this event, and note that **primary key columns for an
- INSERTed row are explicitly omitted** from this event. In order to
- intercept the newly INSERTed state of an object, the
- :meth:`.SessionEvents.pending_to_persistent` and
- :meth:`.MapperEvents.after_insert` are better choices.
-
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param flush_context: Internal :class:`.UOWTransaction` object
- which handles the details of the flush.
- :param attrs: sequence of attribute names which
- were populated.
-
- .. seealso::
-
- :ref:`mapped_class_load_events`
-
- :ref:`orm_server_defaults`
-
- :ref:`metadata_defaults_toplevel`
-
- """
-
- def expire(self, target: _O, attrs: Optional[Iterable[str]]) -> None:
- """Receive an object instance after its attributes or some subset
- have been expired.
-
- 'keys' is a list of attribute names. If None, the entire
- state was expired.
-
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param attrs: sequence of attribute
- names which were expired, or None if all attributes were
- expired.
-
- """
-
- def pickle(self, target: _O, state_dict: _InstanceDict) -> None:
- """Receive an object instance when its associated state is
- being pickled.
-
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param state_dict: the dictionary returned by
- :class:`.InstanceState.__getstate__`, containing the state
- to be pickled.
-
- """
-
- def unpickle(self, target: _O, state_dict: _InstanceDict) -> None:
- """Receive an object instance after its associated state has
- been unpickled.
-
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param state_dict: the dictionary sent to
- :class:`.InstanceState.__setstate__`, containing the state
- dictionary which was pickled.
-
- """
-
-
-class _EventsHold(event.RefCollection[_ET]):
- """Hold onto listeners against unmapped, uninstrumented classes.
-
- Establish _listen() for that class' mapper/instrumentation when
- those objects are created for that class.
-
- """
-
- all_holds: weakref.WeakKeyDictionary[Any, Any]
-
- def __init__(
- self,
- class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
- ) -> None:
- self.class_ = class_
-
- @classmethod
- def _clear(cls) -> None:
- cls.all_holds.clear()
-
- class HoldEvents(Generic[_ET2]):
- _dispatch_target: Optional[Type[_ET2]] = None
-
- @classmethod
- def _listen(
- cls,
- event_key: _EventKey[_ET2],
- raw: bool = False,
- propagate: bool = False,
- retval: bool = False,
- **kw: Any,
- ) -> None:
- target = event_key.dispatch_target
-
- if target.class_ in target.all_holds:
- collection = target.all_holds[target.class_]
- else:
- collection = target.all_holds[target.class_] = {}
-
- event.registry._stored_in_collection(event_key, target)
- collection[event_key._key] = (
- event_key,
- raw,
- propagate,
- retval,
- kw,
- )
-
- if propagate:
- stack = list(target.class_.__subclasses__())
- while stack:
- subclass = stack.pop(0)
- stack.extend(subclass.__subclasses__())
- subject = target.resolve(subclass)
- if subject is not None:
- # we are already going through __subclasses__()
- # so leave generic propagate flag False
- event_key.with_dispatch_target(subject).listen(
- raw=raw, propagate=False, retval=retval, **kw
- )
-
- def remove(self, event_key: _EventKey[_ET]) -> None:
- target = event_key.dispatch_target
-
- if isinstance(target, _EventsHold):
- collection = target.all_holds[target.class_]
- del collection[event_key._key]
-
- @classmethod
- def populate(
- cls,
- class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
- subject: Union[ClassManager[_O], Mapper[_O]],
- ) -> None:
- for subclass in class_.__mro__:
- if subclass in cls.all_holds:
- collection = cls.all_holds[subclass]
- for (
- event_key,
- raw,
- propagate,
- retval,
- kw,
- ) in collection.values():
- if propagate or subclass is class_:
- # since we can't be sure in what order different
- # classes in a hierarchy are triggered with
- # populate(), we rely upon _EventsHold for all event
- # assignment, instead of using the generic propagate
- # flag.
- event_key.with_dispatch_target(subject).listen(
- raw=raw, propagate=False, retval=retval, **kw
- )
-
-
-class _InstanceEventsHold(_EventsHold[_ET]):
- all_holds: weakref.WeakKeyDictionary[Any, Any] = (
- weakref.WeakKeyDictionary()
- )
-
- def resolve(self, class_: Type[_O]) -> Optional[ClassManager[_O]]:
- return instrumentation.opt_manager_of_class(class_)
-
- class HoldInstanceEvents(_EventsHold.HoldEvents[_ET], InstanceEvents): # type: ignore [misc] # noqa: E501
- pass
-
- dispatch = event.dispatcher(HoldInstanceEvents)
-
-
-class MapperEvents(event.Events[mapperlib.Mapper[Any]]):
- """Define events specific to mappings.
-
- e.g.::
-
- from sqlalchemy import event
-
- def my_before_insert_listener(mapper, connection, target):
- # execute a stored procedure upon INSERT,
- # apply the value to the row to be inserted
- target.calculated_value = connection.execute(
- text("select my_special_function(%d)" % target.special_number)
- ).scalar()
-
- # associate the listener function with SomeClass,
- # to execute during the "before_insert" hook
- event.listen(
- SomeClass, 'before_insert', my_before_insert_listener)
-
- Available targets include:
-
- * mapped classes
- * unmapped superclasses of mapped or to-be-mapped classes
- (using the ``propagate=True`` flag)
- * :class:`_orm.Mapper` objects
- * the :class:`_orm.Mapper` class itself indicates listening for all
- mappers.
-
- Mapper events provide hooks into critical sections of the
- mapper, including those related to object instrumentation,
- object loading, and object persistence. In particular, the
- persistence methods :meth:`~.MapperEvents.before_insert`,
- and :meth:`~.MapperEvents.before_update` are popular
- places to augment the state being persisted - however, these
- methods operate with several significant restrictions. The
- user is encouraged to evaluate the
- :meth:`.SessionEvents.before_flush` and
- :meth:`.SessionEvents.after_flush` methods as more
- flexible and user-friendly hooks in which to apply
- additional database state during a flush.
-
- When using :class:`.MapperEvents`, several modifiers are
- available to the :func:`.event.listen` function.
-
- :param propagate=False: When True, the event listener should
- be applied to all inheriting mappers and/or the mappers of
- inheriting classes, as well as any
- mapper which is the target of this listener.
- :param raw=False: When True, the "target" argument passed
- to applicable event listener functions will be the
- instance's :class:`.InstanceState` management
- object, rather than the mapped instance itself.
- :param retval=False: when True, the user-defined event function
- must have a return value, the purpose of which is either to
- control subsequent event propagation, or to otherwise alter
- the operation in progress by the mapper. Possible return
- values are:
-
- * ``sqlalchemy.orm.interfaces.EXT_CONTINUE`` - continue event
- processing normally.
- * ``sqlalchemy.orm.interfaces.EXT_STOP`` - cancel all subsequent
- event handlers in the chain.
- * other values - the return value specified by specific listeners.
-
- """
-
- _target_class_doc = "SomeClass"
- _dispatch_target = mapperlib.Mapper
-
- @classmethod
- def _new_mapper_instance(
- cls,
- class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
- mapper: Mapper[_O],
- ) -> None:
- _MapperEventsHold.populate(class_, mapper)
-
- @classmethod
- @util.preload_module("sqlalchemy.orm")
- def _accept_with(
- cls,
- target: Union[mapperlib.Mapper[Any], Type[mapperlib.Mapper[Any]]],
- identifier: str,
- ) -> Optional[Union[mapperlib.Mapper[Any], Type[mapperlib.Mapper[Any]]]]:
- orm = util.preloaded.orm
-
- if target is orm.mapper: # type: ignore [attr-defined]
- util.warn_deprecated(
- "The `sqlalchemy.orm.mapper()` symbol is deprecated and "
- "will be removed in a future release. For the mapper-wide "
- "event target, use the 'sqlalchemy.orm.Mapper' class.",
- "2.0",
- )
- return mapperlib.Mapper
- elif isinstance(target, type):
- if issubclass(target, mapperlib.Mapper):
- return target
- else:
- mapper = _mapper_or_none(target)
- if mapper is not None:
- return mapper
- else:
- return _MapperEventsHold(target)
- else:
- return target
-
- @classmethod
- def _listen(
- cls,
- event_key: _EventKey[_ET],
- raw: bool = False,
- retval: bool = False,
- propagate: bool = False,
- **kw: Any,
- ) -> None:
- target, identifier, fn = (
- event_key.dispatch_target,
- event_key.identifier,
- event_key._listen_fn,
- )
-
- if (
- identifier in ("before_configured", "after_configured")
- and target is not mapperlib.Mapper
- ):
- util.warn(
- "'before_configured' and 'after_configured' ORM events "
- "only invoke with the Mapper class "
- "as the target."
- )
-
- if not raw or not retval:
- if not raw:
- meth = getattr(cls, identifier)
- try:
- target_index = (
- inspect_getfullargspec(meth)[0].index("target") - 1
- )
- except ValueError:
- target_index = None
-
- def wrap(*arg: Any, **kw: Any) -> Any:
- if not raw and target_index is not None:
- arg = list(arg) # type: ignore [assignment]
- arg[target_index] = arg[target_index].obj() # type: ignore [index] # noqa: E501
- if not retval:
- fn(*arg, **kw)
- return interfaces.EXT_CONTINUE
- else:
- return fn(*arg, **kw)
-
- event_key = event_key.with_wrapper(wrap)
-
- if propagate:
- for mapper in target.self_and_descendants:
- event_key.with_dispatch_target(mapper).base_listen(
- propagate=True, **kw
- )
- else:
- event_key.base_listen(**kw)
-
- @classmethod
- def _clear(cls) -> None:
- super()._clear()
- _MapperEventsHold._clear()
-
- def instrument_class(self, mapper: Mapper[_O], class_: Type[_O]) -> None:
- r"""Receive a class when the mapper is first constructed,
- before instrumentation is applied to the mapped class.
-
- This event is the earliest phase of mapper construction.
- Most attributes of the mapper are not yet initialized. To
- receive an event within initial mapper construction where basic
- state is available such as the :attr:`_orm.Mapper.attrs` collection,
- the :meth:`_orm.MapperEvents.after_mapper_constructed` event may
- be a better choice.
-
- This listener can either be applied to the :class:`_orm.Mapper`
- class overall, or to any un-mapped class which serves as a base
- for classes that will be mapped (using the ``propagate=True`` flag)::
-
- Base = declarative_base()
-
- @event.listens_for(Base, "instrument_class", propagate=True)
- def on_new_class(mapper, cls_):
- " ... "
-
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param class\_: the mapped class.
-
- .. seealso::
-
- :meth:`_orm.MapperEvents.after_mapper_constructed`
-
- """
-
- def after_mapper_constructed(
- self, mapper: Mapper[_O], class_: Type[_O]
- ) -> None:
- """Receive a class and mapper when the :class:`_orm.Mapper` has been
- fully constructed.
-
- This event is called after the initial constructor for
- :class:`_orm.Mapper` completes. This occurs after the
- :meth:`_orm.MapperEvents.instrument_class` event and after the
- :class:`_orm.Mapper` has done an initial pass of its arguments
- to generate its collection of :class:`_orm.MapperProperty` objects,
- which are accessible via the :meth:`_orm.Mapper.get_property`
- method and the :attr:`_orm.Mapper.iterate_properties` attribute.
-
- This event differs from the
- :meth:`_orm.MapperEvents.before_mapper_configured` event in that it
- is invoked within the constructor for :class:`_orm.Mapper`, rather
- than within the :meth:`_orm.registry.configure` process. Currently,
- this event is the only one which is appropriate for handlers that
- wish to create additional mapped classes in response to the
- construction of this :class:`_orm.Mapper`, which will be part of the
- same configure step when :meth:`_orm.registry.configure` next runs.
-
- .. versionadded:: 2.0.2
-
- .. seealso::
-
- :ref:`examples_versioning` - an example which illustrates the use
- of the :meth:`_orm.MapperEvents.before_mapper_configured`
- event to create new mappers to record change-audit histories on
- objects.
-
- """
-
- def before_mapper_configured(
- self, mapper: Mapper[_O], class_: Type[_O]
- ) -> None:
- """Called right before a specific mapper is to be configured.
-
- This event is intended to allow a specific mapper to be skipped during
- the configure step, by returning the :attr:`.orm.interfaces.EXT_SKIP`
- symbol which indicates to the :func:`.configure_mappers` call that this
- particular mapper (or hierarchy of mappers, if ``propagate=True`` is
- used) should be skipped in the current configuration run. When one or
- more mappers are skipped, the he "new mappers" flag will remain set,
- meaning the :func:`.configure_mappers` function will continue to be
- called when mappers are used, to continue to try to configure all
- available mappers.
-
- In comparison to the other configure-level events,
- :meth:`.MapperEvents.before_configured`,
- :meth:`.MapperEvents.after_configured`, and
- :meth:`.MapperEvents.mapper_configured`, the
- :meth;`.MapperEvents.before_mapper_configured` event provides for a
- meaningful return value when it is registered with the ``retval=True``
- parameter.
-
- .. versionadded:: 1.3
-
- e.g.::
-
- from sqlalchemy.orm import EXT_SKIP
-
- Base = declarative_base()
-
- DontConfigureBase = declarative_base()
-
- @event.listens_for(
- DontConfigureBase,
- "before_mapper_configured", retval=True, propagate=True)
- def dont_configure(mapper, cls):
- return EXT_SKIP
-
-
- .. seealso::
-
- :meth:`.MapperEvents.before_configured`
-
- :meth:`.MapperEvents.after_configured`
-
- :meth:`.MapperEvents.mapper_configured`
-
- """
-
- def mapper_configured(self, mapper: Mapper[_O], class_: Type[_O]) -> None:
- r"""Called when a specific mapper has completed its own configuration
- within the scope of the :func:`.configure_mappers` call.
-
- The :meth:`.MapperEvents.mapper_configured` event is invoked
- for each mapper that is encountered when the
- :func:`_orm.configure_mappers` function proceeds through the current
- list of not-yet-configured mappers.
- :func:`_orm.configure_mappers` is typically invoked
- automatically as mappings are first used, as well as each time
- new mappers have been made available and new mapper use is
- detected.
-
- When the event is called, the mapper should be in its final
- state, but **not including backrefs** that may be invoked from
- other mappers; they might still be pending within the
- configuration operation. Bidirectional relationships that
- are instead configured via the
- :paramref:`.orm.relationship.back_populates` argument
- *will* be fully available, since this style of relationship does not
- rely upon other possibly-not-configured mappers to know that they
- exist.
-
- For an event that is guaranteed to have **all** mappers ready
- to go including backrefs that are defined only on other
- mappings, use the :meth:`.MapperEvents.after_configured`
- event; this event invokes only after all known mappings have been
- fully configured.
-
- The :meth:`.MapperEvents.mapper_configured` event, unlike
- :meth:`.MapperEvents.before_configured` or
- :meth:`.MapperEvents.after_configured`,
- is called for each mapper/class individually, and the mapper is
- passed to the event itself. It also is called exactly once for
- a particular mapper. The event is therefore useful for
- configurational steps that benefit from being invoked just once
- on a specific mapper basis, which don't require that "backref"
- configurations are necessarily ready yet.
-
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param class\_: the mapped class.
-
- .. seealso::
-
- :meth:`.MapperEvents.before_configured`
-
- :meth:`.MapperEvents.after_configured`
-
- :meth:`.MapperEvents.before_mapper_configured`
-
- """
- # TODO: need coverage for this event
-
- def before_configured(self) -> None:
- """Called before a series of mappers have been configured.
-
- The :meth:`.MapperEvents.before_configured` event is invoked
- each time the :func:`_orm.configure_mappers` function is
- invoked, before the function has done any of its work.
- :func:`_orm.configure_mappers` is typically invoked
- automatically as mappings are first used, as well as each time
- new mappers have been made available and new mapper use is
- detected.
-
- This event can **only** be applied to the :class:`_orm.Mapper` class,
- and not to individual mappings or mapped classes. It is only invoked
- for all mappings as a whole::
-
- from sqlalchemy.orm import Mapper
-
- @event.listens_for(Mapper, "before_configured")
- def go():
- ...
-
- Contrast this event to :meth:`.MapperEvents.after_configured`,
- which is invoked after the series of mappers has been configured,
- as well as :meth:`.MapperEvents.before_mapper_configured`
- and :meth:`.MapperEvents.mapper_configured`, which are both invoked
- on a per-mapper basis.
-
- Theoretically this event is called once per
- application, but is actually called any time new mappers
- are to be affected by a :func:`_orm.configure_mappers`
- call. If new mappings are constructed after existing ones have
- already been used, this event will likely be called again. To ensure
- that a particular event is only called once and no further, the
- ``once=True`` argument (new in 0.9.4) can be applied::
-
- from sqlalchemy.orm import mapper
-
- @event.listens_for(mapper, "before_configured", once=True)
- def go():
- ...
-
-
- .. seealso::
-
- :meth:`.MapperEvents.before_mapper_configured`
-
- :meth:`.MapperEvents.mapper_configured`
-
- :meth:`.MapperEvents.after_configured`
-
- """
-
- def after_configured(self) -> None:
- """Called after a series of mappers have been configured.
-
- The :meth:`.MapperEvents.after_configured` event is invoked
- each time the :func:`_orm.configure_mappers` function is
- invoked, after the function has completed its work.
- :func:`_orm.configure_mappers` is typically invoked
- automatically as mappings are first used, as well as each time
- new mappers have been made available and new mapper use is
- detected.
-
- Contrast this event to the :meth:`.MapperEvents.mapper_configured`
- event, which is called on a per-mapper basis while the configuration
- operation proceeds; unlike that event, when this event is invoked,
- all cross-configurations (e.g. backrefs) will also have been made
- available for any mappers that were pending.
- Also contrast to :meth:`.MapperEvents.before_configured`,
- which is invoked before the series of mappers has been configured.
-
- This event can **only** be applied to the :class:`_orm.Mapper` class,
- and not to individual mappings or
- mapped classes. It is only invoked for all mappings as a whole::
-
- from sqlalchemy.orm import Mapper
-
- @event.listens_for(Mapper, "after_configured")
- def go():
- # ...
-
- Theoretically this event is called once per
- application, but is actually called any time new mappers
- have been affected by a :func:`_orm.configure_mappers`
- call. If new mappings are constructed after existing ones have
- already been used, this event will likely be called again. To ensure
- that a particular event is only called once and no further, the
- ``once=True`` argument (new in 0.9.4) can be applied::
-
- from sqlalchemy.orm import mapper
-
- @event.listens_for(mapper, "after_configured", once=True)
- def go():
- # ...
-
- .. seealso::
-
- :meth:`.MapperEvents.before_mapper_configured`
-
- :meth:`.MapperEvents.mapper_configured`
-
- :meth:`.MapperEvents.before_configured`
-
- """
-
- def before_insert(
- self, mapper: Mapper[_O], connection: Connection, target: _O
- ) -> None:
- """Receive an object instance before an INSERT statement
- is emitted corresponding to that instance.
-
- .. note:: this event **only** applies to the
- :ref:`session flush operation <session_flushing>`
- and does **not** apply to the ORM DML operations described at
- :ref:`orm_expression_update_delete`. To intercept ORM
- DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
-
- This event is used to modify local, non-object related
- attributes on the instance before an INSERT occurs, as well
- as to emit additional SQL statements on the given
- connection.
-
- The event is often called for a batch of objects of the
- same class before their INSERT statements are emitted at
- once in a later step. In the extremely rare case that
- this is not desirable, the :class:`_orm.Mapper` object can be
- configured with ``batch=False``, which will cause
- batches of instances to be broken up into individual
- (and more poorly performing) event->persist->event
- steps.
-
- .. warning::
-
- Mapper-level flush events only allow **very limited operations**,
- on attributes local to the row being operated upon only,
- as well as allowing any SQL to be emitted on the given
- :class:`_engine.Connection`. **Please read fully** the notes
- at :ref:`session_persistence_mapper` for guidelines on using
- these methods; generally, the :meth:`.SessionEvents.before_flush`
- method should be preferred for general on-flush changes.
-
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param connection: the :class:`_engine.Connection` being used to
- emit INSERT statements for this instance. This
- provides a handle into the current transaction on the
- target database specific to this instance.
- :param target: the mapped instance being persisted. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :return: No return value is supported by this event.
-
- .. seealso::
-
- :ref:`session_persistence_events`
-
- """
-
- def after_insert(
- self, mapper: Mapper[_O], connection: Connection, target: _O
- ) -> None:
- """Receive an object instance after an INSERT statement
- is emitted corresponding to that instance.
-
- .. note:: this event **only** applies to the
- :ref:`session flush operation <session_flushing>`
- and does **not** apply to the ORM DML operations described at
- :ref:`orm_expression_update_delete`. To intercept ORM
- DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
-
- This event is used to modify in-Python-only
- state on the instance after an INSERT occurs, as well
- as to emit additional SQL statements on the given
- connection.
-
- The event is often called for a batch of objects of the
- same class after their INSERT statements have been
- emitted at once in a previous step. In the extremely
- rare case that this is not desirable, the
- :class:`_orm.Mapper` object can be configured with ``batch=False``,
- which will cause batches of instances to be broken up
- into individual (and more poorly performing)
- event->persist->event steps.
-
- .. warning::
-
- Mapper-level flush events only allow **very limited operations**,
- on attributes local to the row being operated upon only,
- as well as allowing any SQL to be emitted on the given
- :class:`_engine.Connection`. **Please read fully** the notes
- at :ref:`session_persistence_mapper` for guidelines on using
- these methods; generally, the :meth:`.SessionEvents.before_flush`
- method should be preferred for general on-flush changes.
-
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param connection: the :class:`_engine.Connection` being used to
- emit INSERT statements for this instance. This
- provides a handle into the current transaction on the
- target database specific to this instance.
- :param target: the mapped instance being persisted. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :return: No return value is supported by this event.
-
- .. seealso::
-
- :ref:`session_persistence_events`
-
- """
-
- def before_update(
- self, mapper: Mapper[_O], connection: Connection, target: _O
- ) -> None:
- """Receive an object instance before an UPDATE statement
- is emitted corresponding to that instance.
-
- .. note:: this event **only** applies to the
- :ref:`session flush operation <session_flushing>`
- and does **not** apply to the ORM DML operations described at
- :ref:`orm_expression_update_delete`. To intercept ORM
- DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
-
- This event is used to modify local, non-object related
- attributes on the instance before an UPDATE occurs, as well
- as to emit additional SQL statements on the given
- connection.
-
- This method is called for all instances that are
- marked as "dirty", *even those which have no net changes
- to their column-based attributes*. An object is marked
- as dirty when any of its column-based attributes have a
- "set attribute" operation called or when any of its
- collections are modified. If, at update time, no
- column-based attributes have any net changes, no UPDATE
- statement will be issued. This means that an instance
- being sent to :meth:`~.MapperEvents.before_update` is
- *not* a guarantee that an UPDATE statement will be
- issued, although you can affect the outcome here by
- modifying attributes so that a net change in value does
- exist.
-
- To detect if the column-based attributes on the object have net
- changes, and will therefore generate an UPDATE statement, use
- ``object_session(instance).is_modified(instance,
- include_collections=False)``.
-
- The event is often called for a batch of objects of the
- same class before their UPDATE statements are emitted at
- once in a later step. In the extremely rare case that
- this is not desirable, the :class:`_orm.Mapper` can be
- configured with ``batch=False``, which will cause
- batches of instances to be broken up into individual
- (and more poorly performing) event->persist->event
- steps.
-
- .. warning::
-
- Mapper-level flush events only allow **very limited operations**,
- on attributes local to the row being operated upon only,
- as well as allowing any SQL to be emitted on the given
- :class:`_engine.Connection`. **Please read fully** the notes
- at :ref:`session_persistence_mapper` for guidelines on using
- these methods; generally, the :meth:`.SessionEvents.before_flush`
- method should be preferred for general on-flush changes.
-
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param connection: the :class:`_engine.Connection` being used to
- emit UPDATE statements for this instance. This
- provides a handle into the current transaction on the
- target database specific to this instance.
- :param target: the mapped instance being persisted. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :return: No return value is supported by this event.
-
- .. seealso::
-
- :ref:`session_persistence_events`
-
- """
-
- def after_update(
- self, mapper: Mapper[_O], connection: Connection, target: _O
- ) -> None:
- """Receive an object instance after an UPDATE statement
- is emitted corresponding to that instance.
-
- .. note:: this event **only** applies to the
- :ref:`session flush operation <session_flushing>`
- and does **not** apply to the ORM DML operations described at
- :ref:`orm_expression_update_delete`. To intercept ORM
- DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
-
- This event is used to modify in-Python-only
- state on the instance after an UPDATE occurs, as well
- as to emit additional SQL statements on the given
- connection.
-
- This method is called for all instances that are
- marked as "dirty", *even those which have no net changes
- to their column-based attributes*, and for which
- no UPDATE statement has proceeded. An object is marked
- as dirty when any of its column-based attributes have a
- "set attribute" operation called or when any of its
- collections are modified. If, at update time, no
- column-based attributes have any net changes, no UPDATE
- statement will be issued. This means that an instance
- being sent to :meth:`~.MapperEvents.after_update` is
- *not* a guarantee that an UPDATE statement has been
- issued.
-
- To detect if the column-based attributes on the object have net
- changes, and therefore resulted in an UPDATE statement, use
- ``object_session(instance).is_modified(instance,
- include_collections=False)``.
-
- The event is often called for a batch of objects of the
- same class after their UPDATE statements have been emitted at
- once in a previous step. In the extremely rare case that
- this is not desirable, the :class:`_orm.Mapper` can be
- configured with ``batch=False``, which will cause
- batches of instances to be broken up into individual
- (and more poorly performing) event->persist->event
- steps.
-
- .. warning::
-
- Mapper-level flush events only allow **very limited operations**,
- on attributes local to the row being operated upon only,
- as well as allowing any SQL to be emitted on the given
- :class:`_engine.Connection`. **Please read fully** the notes
- at :ref:`session_persistence_mapper` for guidelines on using
- these methods; generally, the :meth:`.SessionEvents.before_flush`
- method should be preferred for general on-flush changes.
-
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param connection: the :class:`_engine.Connection` being used to
- emit UPDATE statements for this instance. This
- provides a handle into the current transaction on the
- target database specific to this instance.
- :param target: the mapped instance being persisted. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :return: No return value is supported by this event.
-
- .. seealso::
-
- :ref:`session_persistence_events`
-
- """
-
- def before_delete(
- self, mapper: Mapper[_O], connection: Connection, target: _O
- ) -> None:
- """Receive an object instance before a DELETE statement
- is emitted corresponding to that instance.
-
- .. note:: this event **only** applies to the
- :ref:`session flush operation <session_flushing>`
- and does **not** apply to the ORM DML operations described at
- :ref:`orm_expression_update_delete`. To intercept ORM
- DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
-
- This event is used to emit additional SQL statements on
- the given connection as well as to perform application
- specific bookkeeping related to a deletion event.
-
- The event is often called for a batch of objects of the
- same class before their DELETE statements are emitted at
- once in a later step.
-
- .. warning::
-
- Mapper-level flush events only allow **very limited operations**,
- on attributes local to the row being operated upon only,
- as well as allowing any SQL to be emitted on the given
- :class:`_engine.Connection`. **Please read fully** the notes
- at :ref:`session_persistence_mapper` for guidelines on using
- these methods; generally, the :meth:`.SessionEvents.before_flush`
- method should be preferred for general on-flush changes.
-
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param connection: the :class:`_engine.Connection` being used to
- emit DELETE statements for this instance. This
- provides a handle into the current transaction on the
- target database specific to this instance.
- :param target: the mapped instance being deleted. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :return: No return value is supported by this event.
-
- .. seealso::
-
- :ref:`session_persistence_events`
-
- """
-
- def after_delete(
- self, mapper: Mapper[_O], connection: Connection, target: _O
- ) -> None:
- """Receive an object instance after a DELETE statement
- has been emitted corresponding to that instance.
-
- .. note:: this event **only** applies to the
- :ref:`session flush operation <session_flushing>`
- and does **not** apply to the ORM DML operations described at
- :ref:`orm_expression_update_delete`. To intercept ORM
- DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
-
- This event is used to emit additional SQL statements on
- the given connection as well as to perform application
- specific bookkeeping related to a deletion event.
-
- The event is often called for a batch of objects of the
- same class after their DELETE statements have been emitted at
- once in a previous step.
-
- .. warning::
-
- Mapper-level flush events only allow **very limited operations**,
- on attributes local to the row being operated upon only,
- as well as allowing any SQL to be emitted on the given
- :class:`_engine.Connection`. **Please read fully** the notes
- at :ref:`session_persistence_mapper` for guidelines on using
- these methods; generally, the :meth:`.SessionEvents.before_flush`
- method should be preferred for general on-flush changes.
-
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param connection: the :class:`_engine.Connection` being used to
- emit DELETE statements for this instance. This
- provides a handle into the current transaction on the
- target database specific to this instance.
- :param target: the mapped instance being deleted. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :return: No return value is supported by this event.
-
- .. seealso::
-
- :ref:`session_persistence_events`
-
- """
-
-
-class _MapperEventsHold(_EventsHold[_ET]):
- all_holds = weakref.WeakKeyDictionary()
-
- def resolve(
- self, class_: Union[Type[_T], _InternalEntityType[_T]]
- ) -> Optional[Mapper[_T]]:
- return _mapper_or_none(class_)
-
- class HoldMapperEvents(_EventsHold.HoldEvents[_ET], MapperEvents): # type: ignore [misc] # noqa: E501
- pass
-
- dispatch = event.dispatcher(HoldMapperEvents)
-
-
-_sessionevents_lifecycle_event_names: Set[str] = set()
-
-
-class SessionEvents(event.Events[Session]):
- """Define events specific to :class:`.Session` lifecycle.
-
- e.g.::
-
- from sqlalchemy import event
- from sqlalchemy.orm import sessionmaker
-
- def my_before_commit(session):
- print("before commit!")
-
- Session = sessionmaker()
-
- event.listen(Session, "before_commit", my_before_commit)
-
- The :func:`~.event.listen` function will accept
- :class:`.Session` objects as well as the return result
- of :class:`~.sessionmaker()` and :class:`~.scoped_session()`.
-
- Additionally, it accepts the :class:`.Session` class which
- will apply listeners to all :class:`.Session` instances
- globally.
-
- :param raw=False: When True, the "target" argument passed
- to applicable event listener functions that work on individual
- objects will be the instance's :class:`.InstanceState` management
- object, rather than the mapped instance itself.
-
- .. versionadded:: 1.3.14
-
- :param restore_load_context=False: Applies to the
- :meth:`.SessionEvents.loaded_as_persistent` event. Restores the loader
- context of the object when the event hook is complete, so that ongoing
- eager load operations continue to target the object appropriately. A
- warning is emitted if the object is moved to a new loader context from
- within this event if this flag is not set.
-
- .. versionadded:: 1.3.14
-
- """
-
- _target_class_doc = "SomeSessionClassOrObject"
-
- _dispatch_target = Session
-
- def _lifecycle_event( # type: ignore [misc]
- fn: Callable[[SessionEvents, Session, Any], None]
- ) -> Callable[[SessionEvents, Session, Any], None]:
- _sessionevents_lifecycle_event_names.add(fn.__name__)
- return fn
-
- @classmethod
- def _accept_with( # type: ignore [return]
- cls, target: Any, identifier: str
- ) -> Union[Session, type]:
- if isinstance(target, scoped_session):
- target = target.session_factory
- if not isinstance(target, sessionmaker) and (
- not isinstance(target, type) or not issubclass(target, Session)
- ):
- raise exc.ArgumentError(
- "Session event listen on a scoped_session "
- "requires that its creation callable "
- "is associated with the Session class."
- )
-
- if isinstance(target, sessionmaker):
- return target.class_
- elif isinstance(target, type):
- if issubclass(target, scoped_session):
- return Session
- elif issubclass(target, Session):
- return target
- elif isinstance(target, Session):
- return target
- elif hasattr(target, "_no_async_engine_events"):
- target._no_async_engine_events()
- else:
- # allows alternate SessionEvents-like-classes to be consulted
- return event.Events._accept_with(target, identifier) # type: ignore [return-value] # noqa: E501
-
- @classmethod
- def _listen(
- cls,
- event_key: Any,
- *,
- raw: bool = False,
- restore_load_context: bool = False,
- **kw: Any,
- ) -> None:
- is_instance_event = (
- event_key.identifier in _sessionevents_lifecycle_event_names
- )
-
- if is_instance_event:
- if not raw or restore_load_context:
- fn = event_key._listen_fn
-
- def wrap(
- session: Session,
- state: InstanceState[_O],
- *arg: Any,
- **kw: Any,
- ) -> Optional[Any]:
- if not raw:
- target = state.obj()
- if target is None:
- # existing behavior is that if the object is
- # garbage collected, no event is emitted
- return None
- else:
- target = state # type: ignore [assignment]
- if restore_load_context:
- runid = state.runid
- try:
- return fn(session, target, *arg, **kw)
- finally:
- if restore_load_context:
- state.runid = runid
-
- event_key = event_key.with_wrapper(wrap)
-
- event_key.base_listen(**kw)
-
- def do_orm_execute(self, orm_execute_state: ORMExecuteState) -> None:
- """Intercept statement executions that occur on behalf of an
- ORM :class:`.Session` object.
-
- This event is invoked for all top-level SQL statements invoked from the
- :meth:`_orm.Session.execute` method, as well as related methods such as
- :meth:`_orm.Session.scalars` and :meth:`_orm.Session.scalar`. As of
- SQLAlchemy 1.4, all ORM queries that run through the
- :meth:`_orm.Session.execute` method as well as related methods
- :meth:`_orm.Session.scalars`, :meth:`_orm.Session.scalar` etc.
- will participate in this event.
- This event hook does **not** apply to the queries that are
- emitted internally within the ORM flush process, i.e. the
- process described at :ref:`session_flushing`.
-
- .. note:: The :meth:`_orm.SessionEvents.do_orm_execute` event hook
- is triggered **for ORM statement executions only**, meaning those
- invoked via the :meth:`_orm.Session.execute` and similar methods on
- the :class:`_orm.Session` object. It does **not** trigger for
- statements that are invoked by SQLAlchemy Core only, i.e. statements
- invoked directly using :meth:`_engine.Connection.execute` or
- otherwise originating from an :class:`_engine.Engine` object without
- any :class:`_orm.Session` involved. To intercept **all** SQL
- executions regardless of whether the Core or ORM APIs are in use,
- see the event hooks at :class:`.ConnectionEvents`, such as
- :meth:`.ConnectionEvents.before_execute` and
- :meth:`.ConnectionEvents.before_cursor_execute`.
-
- Also, this event hook does **not** apply to queries that are
- emitted internally within the ORM flush process,
- i.e. the process described at :ref:`session_flushing`; to
- intercept steps within the flush process, see the event
- hooks described at :ref:`session_persistence_events` as
- well as :ref:`session_persistence_mapper`.
-
- This event is a ``do_`` event, meaning it has the capability to replace
- the operation that the :meth:`_orm.Session.execute` method normally
- performs. The intended use for this includes sharding and
- result-caching schemes which may seek to invoke the same statement
- across multiple database connections, returning a result that is
- merged from each of them, or which don't invoke the statement at all,
- instead returning data from a cache.
-
- The hook intends to replace the use of the
- ``Query._execute_and_instances`` method that could be subclassed prior
- to SQLAlchemy 1.4.
-
- :param orm_execute_state: an instance of :class:`.ORMExecuteState`
- which contains all information about the current execution, as well
- as helper functions used to derive other commonly required
- information. See that object for details.
-
- .. seealso::
-
- :ref:`session_execute_events` - top level documentation on how
- to use :meth:`_orm.SessionEvents.do_orm_execute`
-
- :class:`.ORMExecuteState` - the object passed to the
- :meth:`_orm.SessionEvents.do_orm_execute` event which contains
- all information about the statement to be invoked. It also
- provides an interface to extend the current statement, options,
- and parameters as well as an option that allows programmatic
- invocation of the statement at any point.
-
- :ref:`examples_session_orm_events` - includes examples of using
- :meth:`_orm.SessionEvents.do_orm_execute`
-
- :ref:`examples_caching` - an example of how to integrate
- Dogpile caching with the ORM :class:`_orm.Session` making use
- of the :meth:`_orm.SessionEvents.do_orm_execute` event hook.
-
- :ref:`examples_sharding` - the Horizontal Sharding example /
- extension relies upon the
- :meth:`_orm.SessionEvents.do_orm_execute` event hook to invoke a
- SQL statement on multiple backends and return a merged result.
-
-
- .. versionadded:: 1.4
-
- """
-
- def after_transaction_create(
- self, session: Session, transaction: SessionTransaction
- ) -> None:
- """Execute when a new :class:`.SessionTransaction` is created.
-
- This event differs from :meth:`~.SessionEvents.after_begin`
- in that it occurs for each :class:`.SessionTransaction`
- overall, as opposed to when transactions are begun
- on individual database connections. It is also invoked
- for nested transactions and subtransactions, and is always
- matched by a corresponding
- :meth:`~.SessionEvents.after_transaction_end` event
- (assuming normal operation of the :class:`.Session`).
-
- :param session: the target :class:`.Session`.
- :param transaction: the target :class:`.SessionTransaction`.
-
- To detect if this is the outermost
- :class:`.SessionTransaction`, as opposed to a "subtransaction" or a
- SAVEPOINT, test that the :attr:`.SessionTransaction.parent` attribute
- is ``None``::
-
- @event.listens_for(session, "after_transaction_create")
- def after_transaction_create(session, transaction):
- if transaction.parent is None:
- # work with top-level transaction
-
- To detect if the :class:`.SessionTransaction` is a SAVEPOINT, use the
- :attr:`.SessionTransaction.nested` attribute::
-
- @event.listens_for(session, "after_transaction_create")
- def after_transaction_create(session, transaction):
- if transaction.nested:
- # work with SAVEPOINT transaction
-
-
- .. seealso::
-
- :class:`.SessionTransaction`
-
- :meth:`~.SessionEvents.after_transaction_end`
-
- """
-
- def after_transaction_end(
- self, session: Session, transaction: SessionTransaction
- ) -> None:
- """Execute when the span of a :class:`.SessionTransaction` ends.
-
- This event differs from :meth:`~.SessionEvents.after_commit`
- in that it corresponds to all :class:`.SessionTransaction`
- objects in use, including those for nested transactions
- and subtransactions, and is always matched by a corresponding
- :meth:`~.SessionEvents.after_transaction_create` event.
-
- :param session: the target :class:`.Session`.
- :param transaction: the target :class:`.SessionTransaction`.
-
- To detect if this is the outermost
- :class:`.SessionTransaction`, as opposed to a "subtransaction" or a
- SAVEPOINT, test that the :attr:`.SessionTransaction.parent` attribute
- is ``None``::
-
- @event.listens_for(session, "after_transaction_create")
- def after_transaction_end(session, transaction):
- if transaction.parent is None:
- # work with top-level transaction
-
- To detect if the :class:`.SessionTransaction` is a SAVEPOINT, use the
- :attr:`.SessionTransaction.nested` attribute::
-
- @event.listens_for(session, "after_transaction_create")
- def after_transaction_end(session, transaction):
- if transaction.nested:
- # work with SAVEPOINT transaction
-
-
- .. seealso::
-
- :class:`.SessionTransaction`
-
- :meth:`~.SessionEvents.after_transaction_create`
-
- """
-
- def before_commit(self, session: Session) -> None:
- """Execute before commit is called.
-
- .. note::
-
- The :meth:`~.SessionEvents.before_commit` hook is *not* per-flush,
- that is, the :class:`.Session` can emit SQL to the database
- many times within the scope of a transaction.
- For interception of these events, use the
- :meth:`~.SessionEvents.before_flush`,
- :meth:`~.SessionEvents.after_flush`, or
- :meth:`~.SessionEvents.after_flush_postexec`
- events.
-
- :param session: The target :class:`.Session`.
-
- .. seealso::
-
- :meth:`~.SessionEvents.after_commit`
-
- :meth:`~.SessionEvents.after_begin`
-
- :meth:`~.SessionEvents.after_transaction_create`
-
- :meth:`~.SessionEvents.after_transaction_end`
-
- """
-
- def after_commit(self, session: Session) -> None:
- """Execute after a commit has occurred.
-
- .. note::
-
- The :meth:`~.SessionEvents.after_commit` hook is *not* per-flush,
- that is, the :class:`.Session` can emit SQL to the database
- many times within the scope of a transaction.
- For interception of these events, use the
- :meth:`~.SessionEvents.before_flush`,
- :meth:`~.SessionEvents.after_flush`, or
- :meth:`~.SessionEvents.after_flush_postexec`
- events.
-
- .. note::
-
- The :class:`.Session` is not in an active transaction
- when the :meth:`~.SessionEvents.after_commit` event is invoked,
- and therefore can not emit SQL. To emit SQL corresponding to
- every transaction, use the :meth:`~.SessionEvents.before_commit`
- event.
-
- :param session: The target :class:`.Session`.
-
- .. seealso::
-
- :meth:`~.SessionEvents.before_commit`
-
- :meth:`~.SessionEvents.after_begin`
-
- :meth:`~.SessionEvents.after_transaction_create`
-
- :meth:`~.SessionEvents.after_transaction_end`
-
- """
-
- def after_rollback(self, session: Session) -> None:
- """Execute after a real DBAPI rollback has occurred.
-
- Note that this event only fires when the *actual* rollback against
- the database occurs - it does *not* fire each time the
- :meth:`.Session.rollback` method is called, if the underlying
- DBAPI transaction has already been rolled back. In many
- cases, the :class:`.Session` will not be in
- an "active" state during this event, as the current
- transaction is not valid. To acquire a :class:`.Session`
- which is active after the outermost rollback has proceeded,
- use the :meth:`.SessionEvents.after_soft_rollback` event, checking the
- :attr:`.Session.is_active` flag.
-
- :param session: The target :class:`.Session`.
-
- """
-
- def after_soft_rollback(
- self, session: Session, previous_transaction: SessionTransaction
- ) -> None:
- """Execute after any rollback has occurred, including "soft"
- rollbacks that don't actually emit at the DBAPI level.
-
- This corresponds to both nested and outer rollbacks, i.e.
- the innermost rollback that calls the DBAPI's
- rollback() method, as well as the enclosing rollback
- calls that only pop themselves from the transaction stack.
-
- The given :class:`.Session` can be used to invoke SQL and
- :meth:`.Session.query` operations after an outermost rollback
- by first checking the :attr:`.Session.is_active` flag::
-
- @event.listens_for(Session, "after_soft_rollback")
- def do_something(session, previous_transaction):
- if session.is_active:
- session.execute(text("select * from some_table"))
-
- :param session: The target :class:`.Session`.
- :param previous_transaction: The :class:`.SessionTransaction`
- transactional marker object which was just closed. The current
- :class:`.SessionTransaction` for the given :class:`.Session` is
- available via the :attr:`.Session.transaction` attribute.
-
- """
-
- def before_flush(
- self,
- session: Session,
- flush_context: UOWTransaction,
- instances: Optional[Sequence[_O]],
- ) -> None:
- """Execute before flush process has started.
-
- :param session: The target :class:`.Session`.
- :param flush_context: Internal :class:`.UOWTransaction` object
- which handles the details of the flush.
- :param instances: Usually ``None``, this is the collection of
- objects which can be passed to the :meth:`.Session.flush` method
- (note this usage is deprecated).
-
- .. seealso::
-
- :meth:`~.SessionEvents.after_flush`
-
- :meth:`~.SessionEvents.after_flush_postexec`
-
- :ref:`session_persistence_events`
-
- """
-
- def after_flush(
- self, session: Session, flush_context: UOWTransaction
- ) -> None:
- """Execute after flush has completed, but before commit has been
- called.
-
- Note that the session's state is still in pre-flush, i.e. 'new',
- 'dirty', and 'deleted' lists still show pre-flush state as well
- as the history settings on instance attributes.
-
- .. warning:: This event runs after the :class:`.Session` has emitted
- SQL to modify the database, but **before** it has altered its
- internal state to reflect those changes, including that newly
- inserted objects are placed into the identity map. ORM operations
- emitted within this event such as loads of related items
- may produce new identity map entries that will immediately
- be replaced, sometimes causing confusing results. SQLAlchemy will
- emit a warning for this condition as of version 1.3.9.
-
- :param session: The target :class:`.Session`.
- :param flush_context: Internal :class:`.UOWTransaction` object
- which handles the details of the flush.
-
- .. seealso::
-
- :meth:`~.SessionEvents.before_flush`
-
- :meth:`~.SessionEvents.after_flush_postexec`
-
- :ref:`session_persistence_events`
-
- """
-
- def after_flush_postexec(
- self, session: Session, flush_context: UOWTransaction
- ) -> None:
- """Execute after flush has completed, and after the post-exec
- state occurs.
-
- This will be when the 'new', 'dirty', and 'deleted' lists are in
- their final state. An actual commit() may or may not have
- occurred, depending on whether or not the flush started its own
- transaction or participated in a larger transaction.
-
- :param session: The target :class:`.Session`.
- :param flush_context: Internal :class:`.UOWTransaction` object
- which handles the details of the flush.
-
-
- .. seealso::
-
- :meth:`~.SessionEvents.before_flush`
-
- :meth:`~.SessionEvents.after_flush`
-
- :ref:`session_persistence_events`
-
- """
-
- def after_begin(
- self,
- session: Session,
- transaction: SessionTransaction,
- connection: Connection,
- ) -> None:
- """Execute after a transaction is begun on a connection.
-
- .. note:: This event is called within the process of the
- :class:`_orm.Session` modifying its own internal state.
- To invoke SQL operations within this hook, use the
- :class:`_engine.Connection` provided to the event;
- do not run SQL operations using the :class:`_orm.Session`
- directly.
-
- :param session: The target :class:`.Session`.
- :param transaction: The :class:`.SessionTransaction`.
- :param connection: The :class:`_engine.Connection` object
- which will be used for SQL statements.
-
- .. seealso::
-
- :meth:`~.SessionEvents.before_commit`
-
- :meth:`~.SessionEvents.after_commit`
-
- :meth:`~.SessionEvents.after_transaction_create`
-
- :meth:`~.SessionEvents.after_transaction_end`
-
- """
-
- @_lifecycle_event
- def before_attach(self, session: Session, instance: _O) -> None:
- """Execute before an instance is attached to a session.
-
- This is called before an add, delete or merge causes
- the object to be part of the session.
-
- .. seealso::
-
- :meth:`~.SessionEvents.after_attach`
-
- :ref:`session_lifecycle_events`
-
- """
-
- @_lifecycle_event
- def after_attach(self, session: Session, instance: _O) -> None:
- """Execute after an instance is attached to a session.
-
- This is called after an add, delete or merge.
-
- .. note::
-
- As of 0.8, this event fires off *after* the item
- has been fully associated with the session, which is
- different than previous releases. For event
- handlers that require the object not yet
- be part of session state (such as handlers which
- may autoflush while the target object is not
- yet complete) consider the
- new :meth:`.before_attach` event.
-
- .. seealso::
-
- :meth:`~.SessionEvents.before_attach`
-
- :ref:`session_lifecycle_events`
-
- """
-
- @event._legacy_signature(
- "0.9",
- ["session", "query", "query_context", "result"],
- lambda update_context: (
- update_context.session,
- update_context.query,
- None,
- update_context.result,
- ),
- )
- def after_bulk_update(self, update_context: _O) -> None:
- """Event for after the legacy :meth:`_orm.Query.update` method
- has been called.
-
- .. legacy:: The :meth:`_orm.SessionEvents.after_bulk_update` method
- is a legacy event hook as of SQLAlchemy 2.0. The event
- **does not participate** in :term:`2.0 style` invocations
- using :func:`_dml.update` documented at
- :ref:`orm_queryguide_update_delete_where`. For 2.0 style use,
- the :meth:`_orm.SessionEvents.do_orm_execute` hook will intercept
- these calls.
-
- :param update_context: an "update context" object which contains
- details about the update, including these attributes:
-
- * ``session`` - the :class:`.Session` involved
- * ``query`` -the :class:`_query.Query`
- object that this update operation
- was called upon.
- * ``values`` The "values" dictionary that was passed to
- :meth:`_query.Query.update`.
- * ``result`` the :class:`_engine.CursorResult`
- returned as a result of the
- bulk UPDATE operation.
-
- .. versionchanged:: 1.4 the update_context no longer has a
- ``QueryContext`` object associated with it.
-
- .. seealso::
-
- :meth:`.QueryEvents.before_compile_update`
-
- :meth:`.SessionEvents.after_bulk_delete`
-
- """
-
- @event._legacy_signature(
- "0.9",
- ["session", "query", "query_context", "result"],
- lambda delete_context: (
- delete_context.session,
- delete_context.query,
- None,
- delete_context.result,
- ),
- )
- def after_bulk_delete(self, delete_context: _O) -> None:
- """Event for after the legacy :meth:`_orm.Query.delete` method
- has been called.
-
- .. legacy:: The :meth:`_orm.SessionEvents.after_bulk_delete` method
- is a legacy event hook as of SQLAlchemy 2.0. The event
- **does not participate** in :term:`2.0 style` invocations
- using :func:`_dml.delete` documented at
- :ref:`orm_queryguide_update_delete_where`. For 2.0 style use,
- the :meth:`_orm.SessionEvents.do_orm_execute` hook will intercept
- these calls.
-
- :param delete_context: a "delete context" object which contains
- details about the update, including these attributes:
-
- * ``session`` - the :class:`.Session` involved
- * ``query`` -the :class:`_query.Query`
- object that this update operation
- was called upon.
- * ``result`` the :class:`_engine.CursorResult`
- returned as a result of the
- bulk DELETE operation.
-
- .. versionchanged:: 1.4 the update_context no longer has a
- ``QueryContext`` object associated with it.
-
- .. seealso::
-
- :meth:`.QueryEvents.before_compile_delete`
-
- :meth:`.SessionEvents.after_bulk_update`
-
- """
-
- @_lifecycle_event
- def transient_to_pending(self, session: Session, instance: _O) -> None:
- """Intercept the "transient to pending" transition for a specific
- object.
-
- This event is a specialization of the
- :meth:`.SessionEvents.after_attach` event which is only invoked
- for this specific transition. It is invoked typically during the
- :meth:`.Session.add` call.
-
- :param session: target :class:`.Session`
-
- :param instance: the ORM-mapped instance being operated upon.
-
- .. seealso::
-
- :ref:`session_lifecycle_events`
-
- """
-
- @_lifecycle_event
- def pending_to_transient(self, session: Session, instance: _O) -> None:
- """Intercept the "pending to transient" transition for a specific
- object.
-
- This less common transition occurs when an pending object that has
- not been flushed is evicted from the session; this can occur
- when the :meth:`.Session.rollback` method rolls back the transaction,
- or when the :meth:`.Session.expunge` method is used.
-
- :param session: target :class:`.Session`
-
- :param instance: the ORM-mapped instance being operated upon.
-
- .. seealso::
-
- :ref:`session_lifecycle_events`
-
- """
-
- @_lifecycle_event
- def persistent_to_transient(self, session: Session, instance: _O) -> None:
- """Intercept the "persistent to transient" transition for a specific
- object.
-
- This less common transition occurs when an pending object that has
- has been flushed is evicted from the session; this can occur
- when the :meth:`.Session.rollback` method rolls back the transaction.
-
- :param session: target :class:`.Session`
-
- :param instance: the ORM-mapped instance being operated upon.
-
- .. seealso::
-
- :ref:`session_lifecycle_events`
-
- """
-
- @_lifecycle_event
- def pending_to_persistent(self, session: Session, instance: _O) -> None:
- """Intercept the "pending to persistent"" transition for a specific
- object.
-
- This event is invoked within the flush process, and is
- similar to scanning the :attr:`.Session.new` collection within
- the :meth:`.SessionEvents.after_flush` event. However, in this
- case the object has already been moved to the persistent state
- when the event is called.
-
- :param session: target :class:`.Session`
-
- :param instance: the ORM-mapped instance being operated upon.
-
- .. seealso::
-
- :ref:`session_lifecycle_events`
-
- """
-
- @_lifecycle_event
- def detached_to_persistent(self, session: Session, instance: _O) -> None:
- """Intercept the "detached to persistent" transition for a specific
- object.
-
- This event is a specialization of the
- :meth:`.SessionEvents.after_attach` event which is only invoked
- for this specific transition. It is invoked typically during the
- :meth:`.Session.add` call, as well as during the
- :meth:`.Session.delete` call if the object was not previously
- associated with the
- :class:`.Session` (note that an object marked as "deleted" remains
- in the "persistent" state until the flush proceeds).
-
- .. note::
-
- If the object becomes persistent as part of a call to
- :meth:`.Session.delete`, the object is **not** yet marked as
- deleted when this event is called. To detect deleted objects,
- check the ``deleted`` flag sent to the
- :meth:`.SessionEvents.persistent_to_detached` to event after the
- flush proceeds, or check the :attr:`.Session.deleted` collection
- within the :meth:`.SessionEvents.before_flush` event if deleted
- objects need to be intercepted before the flush.
-
- :param session: target :class:`.Session`
-
- :param instance: the ORM-mapped instance being operated upon.
-
- .. seealso::
-
- :ref:`session_lifecycle_events`
-
- """
-
- @_lifecycle_event
- def loaded_as_persistent(self, session: Session, instance: _O) -> None:
- """Intercept the "loaded as persistent" transition for a specific
- object.
-
- This event is invoked within the ORM loading process, and is invoked
- very similarly to the :meth:`.InstanceEvents.load` event. However,
- the event here is linkable to a :class:`.Session` class or instance,
- rather than to a mapper or class hierarchy, and integrates
- with the other session lifecycle events smoothly. The object
- is guaranteed to be present in the session's identity map when
- this event is called.
-
- .. note:: This event is invoked within the loader process before
- eager loaders may have been completed, and the object's state may
- not be complete. Additionally, invoking row-level refresh
- operations on the object will place the object into a new loader
- context, interfering with the existing load context. See the note
- on :meth:`.InstanceEvents.load` for background on making use of the
- :paramref:`.SessionEvents.restore_load_context` parameter, which
- works in the same manner as that of
- :paramref:`.InstanceEvents.restore_load_context`, in order to
- resolve this scenario.
-
- :param session: target :class:`.Session`
-
- :param instance: the ORM-mapped instance being operated upon.
-
- .. seealso::
-
- :ref:`session_lifecycle_events`
-
- """
-
- @_lifecycle_event
- def persistent_to_deleted(self, session: Session, instance: _O) -> None:
- """Intercept the "persistent to deleted" transition for a specific
- object.
-
- This event is invoked when a persistent object's identity
- is deleted from the database within a flush, however the object
- still remains associated with the :class:`.Session` until the
- transaction completes.
-
- If the transaction is rolled back, the object moves again
- to the persistent state, and the
- :meth:`.SessionEvents.deleted_to_persistent` event is called.
- If the transaction is committed, the object becomes detached,
- which will emit the :meth:`.SessionEvents.deleted_to_detached`
- event.
-
- Note that while the :meth:`.Session.delete` method is the primary
- public interface to mark an object as deleted, many objects
- get deleted due to cascade rules, which are not always determined
- until flush time. Therefore, there's no way to catch
- every object that will be deleted until the flush has proceeded.
- the :meth:`.SessionEvents.persistent_to_deleted` event is therefore
- invoked at the end of a flush.
-
- .. seealso::
-
- :ref:`session_lifecycle_events`
-
- """
-
- @_lifecycle_event
- def deleted_to_persistent(self, session: Session, instance: _O) -> None:
- """Intercept the "deleted to persistent" transition for a specific
- object.
-
- This transition occurs only when an object that's been deleted
- successfully in a flush is restored due to a call to
- :meth:`.Session.rollback`. The event is not called under
- any other circumstances.
-
- .. seealso::
-
- :ref:`session_lifecycle_events`
-
- """
-
- @_lifecycle_event
- def deleted_to_detached(self, session: Session, instance: _O) -> None:
- """Intercept the "deleted to detached" transition for a specific
- object.
-
- This event is invoked when a deleted object is evicted
- from the session. The typical case when this occurs is when
- the transaction for a :class:`.Session` in which the object
- was deleted is committed; the object moves from the deleted
- state to the detached state.
-
- It is also invoked for objects that were deleted in a flush
- when the :meth:`.Session.expunge_all` or :meth:`.Session.close`
- events are called, as well as if the object is individually
- expunged from its deleted state via :meth:`.Session.expunge`.
-
- .. seealso::
-
- :ref:`session_lifecycle_events`
-
- """
-
- @_lifecycle_event
- def persistent_to_detached(self, session: Session, instance: _O) -> None:
- """Intercept the "persistent to detached" transition for a specific
- object.
-
- This event is invoked when a persistent object is evicted
- from the session. There are many conditions that cause this
- to happen, including:
-
- * using a method such as :meth:`.Session.expunge`
- or :meth:`.Session.close`
-
- * Calling the :meth:`.Session.rollback` method, when the object
- was part of an INSERT statement for that session's transaction
-
-
- :param session: target :class:`.Session`
-
- :param instance: the ORM-mapped instance being operated upon.
-
- :param deleted: boolean. If True, indicates this object moved
- to the detached state because it was marked as deleted and flushed.
-
-
- .. seealso::
-
- :ref:`session_lifecycle_events`
-
- """
-
-
-class AttributeEvents(event.Events[QueryableAttribute[Any]]):
- r"""Define events for object attributes.
-
- These are typically defined on the class-bound descriptor for the
- target class.
-
- For example, to register a listener that will receive the
- :meth:`_orm.AttributeEvents.append` event::
-
- from sqlalchemy import event
-
- @event.listens_for(MyClass.collection, 'append', propagate=True)
- def my_append_listener(target, value, initiator):
- print("received append event for target: %s" % target)
-
-
- Listeners have the option to return a possibly modified version of the
- value, when the :paramref:`.AttributeEvents.retval` flag is passed to
- :func:`.event.listen` or :func:`.event.listens_for`, such as below,
- illustrated using the :meth:`_orm.AttributeEvents.set` event::
-
- def validate_phone(target, value, oldvalue, initiator):
- "Strip non-numeric characters from a phone number"
-
- return re.sub(r'\D', '', value)
-
- # setup listener on UserContact.phone attribute, instructing
- # it to use the return value
- listen(UserContact.phone, 'set', validate_phone, retval=True)
-
- A validation function like the above can also raise an exception
- such as :exc:`ValueError` to halt the operation.
-
- The :paramref:`.AttributeEvents.propagate` flag is also important when
- applying listeners to mapped classes that also have mapped subclasses,
- as when using mapper inheritance patterns::
-
-
- @event.listens_for(MySuperClass.attr, 'set', propagate=True)
- def receive_set(target, value, initiator):
- print("value set: %s" % target)
-
- The full list of modifiers available to the :func:`.event.listen`
- and :func:`.event.listens_for` functions are below.
-
- :param active_history=False: When True, indicates that the
- "set" event would like to receive the "old" value being
- replaced unconditionally, even if this requires firing off
- database loads. Note that ``active_history`` can also be
- set directly via :func:`.column_property` and
- :func:`_orm.relationship`.
-
- :param propagate=False: When True, the listener function will
- be established not just for the class attribute given, but
- for attributes of the same name on all current subclasses
- of that class, as well as all future subclasses of that
- class, using an additional listener that listens for
- instrumentation events.
- :param raw=False: When True, the "target" argument to the
- event will be the :class:`.InstanceState` management
- object, rather than the mapped instance itself.
- :param retval=False: when True, the user-defined event
- listening must return the "value" argument from the
- function. This gives the listening function the opportunity
- to change the value that is ultimately used for a "set"
- or "append" event.
-
- """
-
- _target_class_doc = "SomeClass.some_attribute"
- _dispatch_target = QueryableAttribute
-
- @staticmethod
- def _set_dispatch(
- cls: Type[_HasEventsDispatch[Any]], dispatch_cls: Type[_Dispatch[Any]]
- ) -> _Dispatch[Any]:
- dispatch = event.Events._set_dispatch(cls, dispatch_cls)
- dispatch_cls._active_history = False
- return dispatch
-
- @classmethod
- def _accept_with(
- cls,
- target: Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]],
- identifier: str,
- ) -> Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]]:
- # TODO: coverage
- if isinstance(target, interfaces.MapperProperty):
- return getattr(target.parent.class_, target.key)
- else:
- return target
-
- @classmethod
- def _listen( # type: ignore [override]
- cls,
- event_key: _EventKey[QueryableAttribute[Any]],
- active_history: bool = False,
- raw: bool = False,
- retval: bool = False,
- propagate: bool = False,
- include_key: bool = False,
- ) -> None:
- target, fn = event_key.dispatch_target, event_key._listen_fn
-
- if active_history:
- target.dispatch._active_history = True
-
- if not raw or not retval or not include_key:
-
- def wrap(target: InstanceState[_O], *arg: Any, **kw: Any) -> Any:
- if not raw:
- target = target.obj() # type: ignore [assignment]
- if not retval:
- if arg:
- value = arg[0]
- else:
- value = None
- if include_key:
- fn(target, *arg, **kw)
- else:
- fn(target, *arg)
- return value
- else:
- if include_key:
- return fn(target, *arg, **kw)
- else:
- return fn(target, *arg)
-
- event_key = event_key.with_wrapper(wrap)
-
- event_key.base_listen(propagate=propagate)
-
- if propagate:
- manager = instrumentation.manager_of_class(target.class_)
-
- for mgr in manager.subclass_managers(True): # type: ignore [no-untyped-call] # noqa: E501
- event_key.with_dispatch_target(mgr[target.key]).base_listen(
- propagate=True
- )
- if active_history:
- mgr[target.key].dispatch._active_history = True
-
- def append(
- self,
- target: _O,
- value: _T,
- initiator: Event,
- *,
- key: EventConstants = NO_KEY,
- ) -> Optional[_T]:
- """Receive a collection append event.
-
- The append event is invoked for each element as it is appended
- to the collection. This occurs for single-item appends as well
- as for a "bulk replace" operation.
-
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param value: the value being appended. If this listener
- is registered with ``retval=True``, the listener
- function must return this value, or a new value which
- replaces it.
- :param initiator: An instance of :class:`.attributes.Event`
- representing the initiation of the event. May be modified
- from its original value by backref handlers in order to control
- chained event propagation, as well as be inspected for information
- about the source of the event.
- :param key: When the event is established using the
- :paramref:`.AttributeEvents.include_key` parameter set to
- True, this will be the key used in the operation, such as
- ``collection[some_key_or_index] = value``.
- The parameter is not passed
- to the event at all if the the
- :paramref:`.AttributeEvents.include_key`
- was not used to set up the event; this is to allow backwards
- compatibility with existing event handlers that don't include the
- ``key`` parameter.
-
- .. versionadded:: 2.0
-
- :return: if the event was registered with ``retval=True``,
- the given value, or a new effective value, should be returned.
-
- .. seealso::
-
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
-
- :meth:`.AttributeEvents.bulk_replace`
-
- """
-
- def append_wo_mutation(
- self,
- target: _O,
- value: _T,
- initiator: Event,
- *,
- key: EventConstants = NO_KEY,
- ) -> None:
- """Receive a collection append event where the collection was not
- actually mutated.
-
- This event differs from :meth:`_orm.AttributeEvents.append` in that
- it is fired off for de-duplicating collections such as sets and
- dictionaries, when the object already exists in the target collection.
- The event does not have a return value and the identity of the
- given object cannot be changed.
-
- The event is used for cascading objects into a :class:`_orm.Session`
- when the collection has already been mutated via a backref event.
-
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param value: the value that would be appended if the object did not
- already exist in the collection.
- :param initiator: An instance of :class:`.attributes.Event`
- representing the initiation of the event. May be modified
- from its original value by backref handlers in order to control
- chained event propagation, as well as be inspected for information
- about the source of the event.
- :param key: When the event is established using the
- :paramref:`.AttributeEvents.include_key` parameter set to
- True, this will be the key used in the operation, such as
- ``collection[some_key_or_index] = value``.
- The parameter is not passed
- to the event at all if the the
- :paramref:`.AttributeEvents.include_key`
- was not used to set up the event; this is to allow backwards
- compatibility with existing event handlers that don't include the
- ``key`` parameter.
-
- .. versionadded:: 2.0
-
- :return: No return value is defined for this event.
-
- .. versionadded:: 1.4.15
-
- """
-
- def bulk_replace(
- self,
- target: _O,
- values: Iterable[_T],
- initiator: Event,
- *,
- keys: Optional[Iterable[EventConstants]] = None,
- ) -> None:
- """Receive a collection 'bulk replace' event.
-
- This event is invoked for a sequence of values as they are incoming
- to a bulk collection set operation, which can be
- modified in place before the values are treated as ORM objects.
- This is an "early hook" that runs before the bulk replace routine
- attempts to reconcile which objects are already present in the
- collection and which are being removed by the net replace operation.
-
- It is typical that this method be combined with use of the
- :meth:`.AttributeEvents.append` event. When using both of these
- events, note that a bulk replace operation will invoke
- the :meth:`.AttributeEvents.append` event for all new items,
- even after :meth:`.AttributeEvents.bulk_replace` has been invoked
- for the collection as a whole. In order to determine if an
- :meth:`.AttributeEvents.append` event is part of a bulk replace,
- use the symbol :attr:`~.attributes.OP_BULK_REPLACE` to test the
- incoming initiator::
-
- from sqlalchemy.orm.attributes import OP_BULK_REPLACE
-
- @event.listens_for(SomeObject.collection, "bulk_replace")
- def process_collection(target, values, initiator):
- values[:] = [_make_value(value) for value in values]
-
- @event.listens_for(SomeObject.collection, "append", retval=True)
- def process_collection(target, value, initiator):
- # make sure bulk_replace didn't already do it
- if initiator is None or initiator.op is not OP_BULK_REPLACE:
- return _make_value(value)
- else:
- return value
-
- .. versionadded:: 1.2
-
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param value: a sequence (e.g. a list) of the values being set. The
- handler can modify this list in place.
- :param initiator: An instance of :class:`.attributes.Event`
- representing the initiation of the event.
- :param keys: When the event is established using the
- :paramref:`.AttributeEvents.include_key` parameter set to
- True, this will be the sequence of keys used in the operation,
- typically only for a dictionary update. The parameter is not passed
- to the event at all if the the
- :paramref:`.AttributeEvents.include_key`
- was not used to set up the event; this is to allow backwards
- compatibility with existing event handlers that don't include the
- ``key`` parameter.
-
- .. versionadded:: 2.0
-
- .. seealso::
-
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
-
-
- """
-
- def remove(
- self,
- target: _O,
- value: _T,
- initiator: Event,
- *,
- key: EventConstants = NO_KEY,
- ) -> None:
- """Receive a collection remove event.
-
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param value: the value being removed.
- :param initiator: An instance of :class:`.attributes.Event`
- representing the initiation of the event. May be modified
- from its original value by backref handlers in order to control
- chained event propagation.
-
- :param key: When the event is established using the
- :paramref:`.AttributeEvents.include_key` parameter set to
- True, this will be the key used in the operation, such as
- ``del collection[some_key_or_index]``. The parameter is not passed
- to the event at all if the the
- :paramref:`.AttributeEvents.include_key`
- was not used to set up the event; this is to allow backwards
- compatibility with existing event handlers that don't include the
- ``key`` parameter.
-
- .. versionadded:: 2.0
-
- :return: No return value is defined for this event.
-
-
- .. seealso::
-
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
-
- """
-
- def set(
- self, target: _O, value: _T, oldvalue: _T, initiator: Event
- ) -> None:
- """Receive a scalar set event.
-
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param value: the value being set. If this listener
- is registered with ``retval=True``, the listener
- function must return this value, or a new value which
- replaces it.
- :param oldvalue: the previous value being replaced. This
- may also be the symbol ``NEVER_SET`` or ``NO_VALUE``.
- If the listener is registered with ``active_history=True``,
- the previous value of the attribute will be loaded from
- the database if the existing value is currently unloaded
- or expired.
- :param initiator: An instance of :class:`.attributes.Event`
- representing the initiation of the event. May be modified
- from its original value by backref handlers in order to control
- chained event propagation.
-
- :return: if the event was registered with ``retval=True``,
- the given value, or a new effective value, should be returned.
-
- .. seealso::
-
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
-
- """
-
- def init_scalar(
- self, target: _O, value: _T, dict_: Dict[Any, Any]
- ) -> None:
- r"""Receive a scalar "init" event.
-
- This event is invoked when an uninitialized, unpersisted scalar
- attribute is accessed, e.g. read::
-
-
- x = my_object.some_attribute
-
- The ORM's default behavior when this occurs for an un-initialized
- attribute is to return the value ``None``; note this differs from
- Python's usual behavior of raising ``AttributeError``. The
- event here can be used to customize what value is actually returned,
- with the assumption that the event listener would be mirroring
- a default generator that is configured on the Core
- :class:`_schema.Column`
- object as well.
-
- Since a default generator on a :class:`_schema.Column`
- might also produce
- a changing value such as a timestamp, the
- :meth:`.AttributeEvents.init_scalar`
- event handler can also be used to **set** the newly returned value, so
- that a Core-level default generation function effectively fires off
- only once, but at the moment the attribute is accessed on the
- non-persisted object. Normally, no change to the object's state
- is made when an uninitialized attribute is accessed (much older
- SQLAlchemy versions did in fact change the object's state).
-
- If a default generator on a column returned a particular constant,
- a handler might be used as follows::
-
- SOME_CONSTANT = 3.1415926
-
- class MyClass(Base):
- # ...
-
- some_attribute = Column(Numeric, default=SOME_CONSTANT)
-
- @event.listens_for(
- MyClass.some_attribute, "init_scalar",
- retval=True, propagate=True)
- def _init_some_attribute(target, dict_, value):
- dict_['some_attribute'] = SOME_CONSTANT
- return SOME_CONSTANT
-
- Above, we initialize the attribute ``MyClass.some_attribute`` to the
- value of ``SOME_CONSTANT``. The above code includes the following
- features:
-
- * By setting the value ``SOME_CONSTANT`` in the given ``dict_``,
- we indicate that this value is to be persisted to the database.
- This supersedes the use of ``SOME_CONSTANT`` in the default generator
- for the :class:`_schema.Column`. The ``active_column_defaults.py``
- example given at :ref:`examples_instrumentation` illustrates using
- the same approach for a changing default, e.g. a timestamp
- generator. In this particular example, it is not strictly
- necessary to do this since ``SOME_CONSTANT`` would be part of the
- INSERT statement in either case.
-
- * By establishing the ``retval=True`` flag, the value we return
- from the function will be returned by the attribute getter.
- Without this flag, the event is assumed to be a passive observer
- and the return value of our function is ignored.
-
- * The ``propagate=True`` flag is significant if the mapped class
- includes inheriting subclasses, which would also make use of this
- event listener. Without this flag, an inheriting subclass will
- not use our event handler.
-
- In the above example, the attribute set event
- :meth:`.AttributeEvents.set` as well as the related validation feature
- provided by :obj:`_orm.validates` is **not** invoked when we apply our
- value to the given ``dict_``. To have these events to invoke in
- response to our newly generated value, apply the value to the given
- object as a normal attribute set operation::
-
- SOME_CONSTANT = 3.1415926
-
- @event.listens_for(
- MyClass.some_attribute, "init_scalar",
- retval=True, propagate=True)
- def _init_some_attribute(target, dict_, value):
- # will also fire off attribute set events
- target.some_attribute = SOME_CONSTANT
- return SOME_CONSTANT
-
- When multiple listeners are set up, the generation of the value
- is "chained" from one listener to the next by passing the value
- returned by the previous listener that specifies ``retval=True``
- as the ``value`` argument of the next listener.
-
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param value: the value that is to be returned before this event
- listener were invoked. This value begins as the value ``None``,
- however will be the return value of the previous event handler
- function if multiple listeners are present.
- :param dict\_: the attribute dictionary of this mapped object.
- This is normally the ``__dict__`` of the object, but in all cases
- represents the destination that the attribute system uses to get
- at the actual value of this attribute. Placing the value in this
- dictionary has the effect that the value will be used in the
- INSERT statement generated by the unit of work.
-
-
- .. seealso::
-
- :meth:`.AttributeEvents.init_collection` - collection version
- of this event
-
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
-
- :ref:`examples_instrumentation` - see the
- ``active_column_defaults.py`` example.
-
- """
-
- def init_collection(
- self,
- target: _O,
- collection: Type[Collection[Any]],
- collection_adapter: CollectionAdapter,
- ) -> None:
- """Receive a 'collection init' event.
-
- This event is triggered for a collection-based attribute, when
- the initial "empty collection" is first generated for a blank
- attribute, as well as for when the collection is replaced with
- a new one, such as via a set event.
-
- E.g., given that ``User.addresses`` is a relationship-based
- collection, the event is triggered here::
-
- u1 = User()
- u1.addresses.append(a1) # <- new collection
-
- and also during replace operations::
-
- u1.addresses = [a2, a3] # <- new collection
-
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param collection: the new collection. This will always be generated
- from what was specified as
- :paramref:`_orm.relationship.collection_class`, and will always
- be empty.
- :param collection_adapter: the :class:`.CollectionAdapter` that will
- mediate internal access to the collection.
-
- .. seealso::
-
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
-
- :meth:`.AttributeEvents.init_scalar` - "scalar" version of this
- event.
-
- """
-
- def dispose_collection(
- self,
- target: _O,
- collection: Collection[Any],
- collection_adapter: CollectionAdapter,
- ) -> None:
- """Receive a 'collection dispose' event.
-
- This event is triggered for a collection-based attribute when
- a collection is replaced, that is::
-
- u1.addresses.append(a1)
-
- u1.addresses = [a2, a3] # <- old collection is disposed
-
- The old collection received will contain its previous contents.
-
- .. versionchanged:: 1.2 The collection passed to
- :meth:`.AttributeEvents.dispose_collection` will now have its
- contents before the dispose intact; previously, the collection
- would be empty.
-
- .. seealso::
-
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
-
- """
-
- def modified(self, target: _O, initiator: Event) -> None:
- """Receive a 'modified' event.
-
- This event is triggered when the :func:`.attributes.flag_modified`
- function is used to trigger a modify event on an attribute without
- any specific value being set.
-
- .. versionadded:: 1.2
-
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
-
- :param initiator: An instance of :class:`.attributes.Event`
- representing the initiation of the event.
-
- .. seealso::
-
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
-
- """
-
-
-class QueryEvents(event.Events[Query[Any]]):
- """Represent events within the construction of a :class:`_query.Query`
- object.
-
- .. legacy:: The :class:`_orm.QueryEvents` event methods are legacy
- as of SQLAlchemy 2.0, and only apply to direct use of the
- :class:`_orm.Query` object. They are not used for :term:`2.0 style`
- statements. For events to intercept and modify 2.0 style ORM use,
- use the :meth:`_orm.SessionEvents.do_orm_execute` hook.
-
-
- The :class:`_orm.QueryEvents` hooks are now superseded by the
- :meth:`_orm.SessionEvents.do_orm_execute` event hook.
-
- """
-
- _target_class_doc = "SomeQuery"
- _dispatch_target = Query
-
- def before_compile(self, query: Query[Any]) -> None:
- """Receive the :class:`_query.Query`
- object before it is composed into a
- core :class:`_expression.Select` object.
-
- .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile` event
- is superseded by the much more capable
- :meth:`_orm.SessionEvents.do_orm_execute` hook. In version 1.4,
- the :meth:`_orm.QueryEvents.before_compile` event is **no longer
- used** for ORM-level attribute loads, such as loads of deferred
- or expired attributes as well as relationship loaders. See the
- new examples in :ref:`examples_session_orm_events` which
- illustrate new ways of intercepting and modifying ORM queries
- for the most common purpose of adding arbitrary filter criteria.
-
-
- This event is intended to allow changes to the query given::
-
- @event.listens_for(Query, "before_compile", retval=True)
- def no_deleted(query):
- for desc in query.column_descriptions:
- if desc['type'] is User:
- entity = desc['entity']
- query = query.filter(entity.deleted == False)
- return query
-
- The event should normally be listened with the ``retval=True``
- parameter set, so that the modified query may be returned.
-
- The :meth:`.QueryEvents.before_compile` event by default
- will disallow "baked" queries from caching a query, if the event
- hook returns a new :class:`_query.Query` object.
- This affects both direct
- use of the baked query extension as well as its operation within
- lazy loaders and eager loaders for relationships. In order to
- re-establish the query being cached, apply the event adding the
- ``bake_ok`` flag::
-
- @event.listens_for(
- Query, "before_compile", retval=True, bake_ok=True)
- def my_event(query):
- for desc in query.column_descriptions:
- if desc['type'] is User:
- entity = desc['entity']
- query = query.filter(entity.deleted == False)
- return query
-
- When ``bake_ok`` is set to True, the event hook will only be invoked
- once, and not called for subsequent invocations of a particular query
- that is being cached.
-
- .. versionadded:: 1.3.11 - added the "bake_ok" flag to the
- :meth:`.QueryEvents.before_compile` event and disallowed caching via
- the "baked" extension from occurring for event handlers that
- return a new :class:`_query.Query` object if this flag is not set.
-
- .. seealso::
-
- :meth:`.QueryEvents.before_compile_update`
-
- :meth:`.QueryEvents.before_compile_delete`
-
- :ref:`baked_with_before_compile`
-
- """
-
- def before_compile_update(
- self, query: Query[Any], update_context: BulkUpdate
- ) -> None:
- """Allow modifications to the :class:`_query.Query` object within
- :meth:`_query.Query.update`.
-
- .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_update`
- event is superseded by the much more capable
- :meth:`_orm.SessionEvents.do_orm_execute` hook.
-
- Like the :meth:`.QueryEvents.before_compile` event, if the event
- is to be used to alter the :class:`_query.Query` object, it should
- be configured with ``retval=True``, and the modified
- :class:`_query.Query` object returned, as in ::
-
- @event.listens_for(Query, "before_compile_update", retval=True)
- def no_deleted(query, update_context):
- for desc in query.column_descriptions:
- if desc['type'] is User:
- entity = desc['entity']
- query = query.filter(entity.deleted == False)
-
- update_context.values['timestamp'] = datetime.utcnow()
- return query
-
- The ``.values`` dictionary of the "update context" object can also
- be modified in place as illustrated above.
-
- :param query: a :class:`_query.Query` instance; this is also
- the ``.query`` attribute of the given "update context"
- object.
-
- :param update_context: an "update context" object which is
- the same kind of object as described in
- :paramref:`.QueryEvents.after_bulk_update.update_context`.
- The object has a ``.values`` attribute in an UPDATE context which is
- the dictionary of parameters passed to :meth:`_query.Query.update`.
- This
- dictionary can be modified to alter the VALUES clause of the
- resulting UPDATE statement.
-
- .. versionadded:: 1.2.17
-
- .. seealso::
-
- :meth:`.QueryEvents.before_compile`
-
- :meth:`.QueryEvents.before_compile_delete`
-
-
- """
-
- def before_compile_delete(
- self, query: Query[Any], delete_context: BulkDelete
- ) -> None:
- """Allow modifications to the :class:`_query.Query` object within
- :meth:`_query.Query.delete`.
-
- .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_delete`
- event is superseded by the much more capable
- :meth:`_orm.SessionEvents.do_orm_execute` hook.
-
- Like the :meth:`.QueryEvents.before_compile` event, this event
- should be configured with ``retval=True``, and the modified
- :class:`_query.Query` object returned, as in ::
-
- @event.listens_for(Query, "before_compile_delete", retval=True)
- def no_deleted(query, delete_context):
- for desc in query.column_descriptions:
- if desc['type'] is User:
- entity = desc['entity']
- query = query.filter(entity.deleted == False)
- return query
-
- :param query: a :class:`_query.Query` instance; this is also
- the ``.query`` attribute of the given "delete context"
- object.
-
- :param delete_context: a "delete context" object which is
- the same kind of object as described in
- :paramref:`.QueryEvents.after_bulk_delete.delete_context`.
-
- .. versionadded:: 1.2.17
-
- .. seealso::
-
- :meth:`.QueryEvents.before_compile`
-
- :meth:`.QueryEvents.before_compile_update`
-
-
- """
-
- @classmethod
- def _listen(
- cls,
- event_key: _EventKey[_ET],
- retval: bool = False,
- bake_ok: bool = False,
- **kw: Any,
- ) -> None:
- fn = event_key._listen_fn
-
- if not retval:
-
- def wrap(*arg: Any, **kw: Any) -> Any:
- if not retval:
- query = arg[0]
- fn(*arg, **kw)
- return query
- else:
- return fn(*arg, **kw)
-
- event_key = event_key.with_wrapper(wrap)
- else:
- # don't assume we can apply an attribute to the callable
- def wrap(*arg: Any, **kw: Any) -> Any:
- return fn(*arg, **kw)
-
- event_key = event_key.with_wrapper(wrap)
-
- wrap._bake_ok = bake_ok # type: ignore [attr-defined]
-
- event_key.base_listen(**kw)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/exc.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/exc.py
deleted file mode 100644
index 39dd540..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/exc.py
+++ /dev/null
@@ -1,228 +0,0 @@
-# orm/exc.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
-
-"""SQLAlchemy ORM exceptions."""
-
-from __future__ import annotations
-
-from typing import Any
-from typing import Optional
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-
-from .util import _mapper_property_as_plain_name
-from .. import exc as sa_exc
-from .. import util
-from ..exc import MultipleResultsFound # noqa
-from ..exc import NoResultFound # noqa
-
-if TYPE_CHECKING:
- from .interfaces import LoaderStrategy
- from .interfaces import MapperProperty
- from .state import InstanceState
-
-_T = TypeVar("_T", bound=Any)
-
-NO_STATE = (AttributeError, KeyError)
-"""Exception types that may be raised by instrumentation implementations."""
-
-
-class StaleDataError(sa_exc.SQLAlchemyError):
- """An operation encountered database state that is unaccounted for.
-
- Conditions which cause this to happen include:
-
- * A flush may have attempted to update or delete rows
- and an unexpected number of rows were matched during
- the UPDATE or DELETE statement. Note that when
- version_id_col is used, rows in UPDATE or DELETE statements
- are also matched against the current known version
- identifier.
-
- * A mapped object with version_id_col was refreshed,
- and the version number coming back from the database does
- not match that of the object itself.
-
- * A object is detached from its parent object, however
- the object was previously attached to a different parent
- identity which was garbage collected, and a decision
- cannot be made if the new parent was really the most
- recent "parent".
-
- """
-
-
-ConcurrentModificationError = StaleDataError
-
-
-class FlushError(sa_exc.SQLAlchemyError):
- """A invalid condition was detected during flush()."""
-
-
-class UnmappedError(sa_exc.InvalidRequestError):
- """Base for exceptions that involve expected mappings not present."""
-
-
-class ObjectDereferencedError(sa_exc.SQLAlchemyError):
- """An operation cannot complete due to an object being garbage
- collected.
-
- """
-
-
-class DetachedInstanceError(sa_exc.SQLAlchemyError):
- """An attempt to access unloaded attributes on a
- mapped instance that is detached."""
-
- code = "bhk3"
-
-
-class UnmappedInstanceError(UnmappedError):
- """An mapping operation was requested for an unknown instance."""
-
- @util.preload_module("sqlalchemy.orm.base")
- def __init__(self, obj: object, msg: Optional[str] = None):
- base = util.preloaded.orm_base
-
- if not msg:
- try:
- base.class_mapper(type(obj))
- name = _safe_cls_name(type(obj))
- msg = (
- "Class %r is mapped, but this instance lacks "
- "instrumentation. This occurs when the instance "
- "is created before sqlalchemy.orm.mapper(%s) "
- "was called." % (name, name)
- )
- except UnmappedClassError:
- msg = f"Class '{_safe_cls_name(type(obj))}' is not mapped"
- if isinstance(obj, type):
- msg += (
- "; was a class (%s) supplied where an instance was "
- "required?" % _safe_cls_name(obj)
- )
- UnmappedError.__init__(self, msg)
-
- def __reduce__(self) -> Any:
- return self.__class__, (None, self.args[0])
-
-
-class UnmappedClassError(UnmappedError):
- """An mapping operation was requested for an unknown class."""
-
- def __init__(self, cls: Type[_T], msg: Optional[str] = None):
- if not msg:
- msg = _default_unmapped(cls)
- UnmappedError.__init__(self, msg)
-
- def __reduce__(self) -> Any:
- return self.__class__, (None, self.args[0])
-
-
-class ObjectDeletedError(sa_exc.InvalidRequestError):
- """A refresh operation failed to retrieve the database
- row corresponding to an object's known primary key identity.
-
- A refresh operation proceeds when an expired attribute is
- accessed on an object, or when :meth:`_query.Query.get` is
- used to retrieve an object which is, upon retrieval, detected
- as expired. A SELECT is emitted for the target row
- based on primary key; if no row is returned, this
- exception is raised.
-
- The true meaning of this exception is simply that
- no row exists for the primary key identifier associated
- with a persistent object. The row may have been
- deleted, or in some cases the primary key updated
- to a new value, outside of the ORM's management of the target
- object.
-
- """
-
- @util.preload_module("sqlalchemy.orm.base")
- def __init__(self, state: InstanceState[Any], msg: Optional[str] = None):
- base = util.preloaded.orm_base
-
- if not msg:
- msg = (
- "Instance '%s' has been deleted, or its "
- "row is otherwise not present." % base.state_str(state)
- )
-
- sa_exc.InvalidRequestError.__init__(self, msg)
-
- def __reduce__(self) -> Any:
- return self.__class__, (None, self.args[0])
-
-
-class UnmappedColumnError(sa_exc.InvalidRequestError):
- """Mapping operation was requested on an unknown column."""
-
-
-class LoaderStrategyException(sa_exc.InvalidRequestError):
- """A loader strategy for an attribute does not exist."""
-
- def __init__(
- self,
- applied_to_property_type: Type[Any],
- requesting_property: MapperProperty[Any],
- applies_to: Optional[Type[MapperProperty[Any]]],
- actual_strategy_type: Optional[Type[LoaderStrategy]],
- strategy_key: Tuple[Any, ...],
- ):
- if actual_strategy_type is None:
- sa_exc.InvalidRequestError.__init__(
- self,
- "Can't find strategy %s for %s"
- % (strategy_key, requesting_property),
- )
- else:
- assert applies_to is not None
- sa_exc.InvalidRequestError.__init__(
- self,
- 'Can\'t apply "%s" strategy to property "%s", '
- 'which is a "%s"; this loader strategy is intended '
- 'to be used with a "%s".'
- % (
- util.clsname_as_plain_name(actual_strategy_type),
- requesting_property,
- _mapper_property_as_plain_name(applied_to_property_type),
- _mapper_property_as_plain_name(applies_to),
- ),
- )
-
-
-def _safe_cls_name(cls: Type[Any]) -> str:
- cls_name: Optional[str]
- try:
- cls_name = ".".join((cls.__module__, cls.__name__))
- except AttributeError:
- cls_name = getattr(cls, "__name__", None)
- if cls_name is None:
- cls_name = repr(cls)
- return cls_name
-
-
-@util.preload_module("sqlalchemy.orm.base")
-def _default_unmapped(cls: Type[Any]) -> Optional[str]:
- base = util.preloaded.orm_base
-
- try:
- mappers = base.manager_of_class(cls).mappers # type: ignore
- except (
- UnmappedClassError,
- TypeError,
- ) + NO_STATE:
- mappers = {}
- name = _safe_cls_name(cls)
-
- if not mappers:
- return f"Class '{name}' is not mapped"
- else:
- return None
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/identity.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/identity.py
deleted file mode 100644
index 23682f7..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/identity.py
+++ /dev/null
@@ -1,302 +0,0 @@
-# orm/identity.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
-
-from __future__ import annotations
-
-from typing import Any
-from typing import cast
-from typing import Dict
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import NoReturn
-from typing import Optional
-from typing import Set
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import TypeVar
-import weakref
-
-from . import util as orm_util
-from .. import exc as sa_exc
-
-if TYPE_CHECKING:
- from ._typing import _IdentityKeyType
- from .state import InstanceState
-
-
-_T = TypeVar("_T", bound=Any)
-
-_O = TypeVar("_O", bound=object)
-
-
-class IdentityMap:
- _wr: weakref.ref[IdentityMap]
-
- _dict: Dict[_IdentityKeyType[Any], Any]
- _modified: Set[InstanceState[Any]]
-
- def __init__(self) -> None:
- self._dict = {}
- self._modified = set()
- self._wr = weakref.ref(self)
-
- def _kill(self) -> None:
- self._add_unpresent = _killed # type: ignore
-
- def all_states(self) -> List[InstanceState[Any]]:
- raise NotImplementedError()
-
- def contains_state(self, state: InstanceState[Any]) -> bool:
- raise NotImplementedError()
-
- def __contains__(self, key: _IdentityKeyType[Any]) -> bool:
- raise NotImplementedError()
-
- def safe_discard(self, state: InstanceState[Any]) -> None:
- raise NotImplementedError()
-
- def __getitem__(self, key: _IdentityKeyType[_O]) -> _O:
- raise NotImplementedError()
-
- def get(
- self, key: _IdentityKeyType[_O], default: Optional[_O] = None
- ) -> Optional[_O]:
- raise NotImplementedError()
-
- def fast_get_state(
- self, key: _IdentityKeyType[_O]
- ) -> Optional[InstanceState[_O]]:
- raise NotImplementedError()
-
- def keys(self) -> Iterable[_IdentityKeyType[Any]]:
- return self._dict.keys()
-
- def values(self) -> Iterable[object]:
- raise NotImplementedError()
-
- def replace(self, state: InstanceState[_O]) -> Optional[InstanceState[_O]]:
- raise NotImplementedError()
-
- def add(self, state: InstanceState[Any]) -> bool:
- raise NotImplementedError()
-
- def _fast_discard(self, state: InstanceState[Any]) -> None:
- raise NotImplementedError()
-
- def _add_unpresent(
- self, state: InstanceState[Any], key: _IdentityKeyType[Any]
- ) -> None:
- """optional inlined form of add() which can assume item isn't present
- in the map"""
- self.add(state)
-
- def _manage_incoming_state(self, state: InstanceState[Any]) -> None:
- state._instance_dict = self._wr
-
- if state.modified:
- self._modified.add(state)
-
- def _manage_removed_state(self, state: InstanceState[Any]) -> None:
- del state._instance_dict
- if state.modified:
- self._modified.discard(state)
-
- def _dirty_states(self) -> Set[InstanceState[Any]]:
- return self._modified
-
- def check_modified(self) -> bool:
- """return True if any InstanceStates present have been marked
- as 'modified'.
-
- """
- return bool(self._modified)
-
- def has_key(self, key: _IdentityKeyType[Any]) -> bool:
- return key in self
-
- def __len__(self) -> int:
- return len(self._dict)
-
-
-class WeakInstanceDict(IdentityMap):
- _dict: Dict[_IdentityKeyType[Any], InstanceState[Any]]
-
- def __getitem__(self, key: _IdentityKeyType[_O]) -> _O:
- state = cast("InstanceState[_O]", self._dict[key])
- o = state.obj()
- if o is None:
- raise KeyError(key)
- return o
-
- def __contains__(self, key: _IdentityKeyType[Any]) -> bool:
- try:
- if key in self._dict:
- state = self._dict[key]
- o = state.obj()
- else:
- return False
- except KeyError:
- return False
- else:
- return o is not None
-
- def contains_state(self, state: InstanceState[Any]) -> bool:
- if state.key in self._dict:
- if TYPE_CHECKING:
- assert state.key is not None
- try:
- return self._dict[state.key] is state
- except KeyError:
- return False
- else:
- return False
-
- def replace(
- self, state: InstanceState[Any]
- ) -> Optional[InstanceState[Any]]:
- assert state.key is not None
- if state.key in self._dict:
- try:
- existing = existing_non_none = self._dict[state.key]
- except KeyError:
- # catch gc removed the key after we just checked for it
- existing = None
- else:
- if existing_non_none is not state:
- self._manage_removed_state(existing_non_none)
- else:
- return None
- else:
- existing = None
-
- self._dict[state.key] = state
- self._manage_incoming_state(state)
- return existing
-
- def add(self, state: InstanceState[Any]) -> bool:
- key = state.key
- assert key is not None
- # inline of self.__contains__
- if key in self._dict:
- try:
- existing_state = self._dict[key]
- except KeyError:
- # catch gc removed the key after we just checked for it
- pass
- else:
- if existing_state is not state:
- o = existing_state.obj()
- if o is not None:
- raise sa_exc.InvalidRequestError(
- "Can't attach instance "
- "%s; another instance with key %s is already "
- "present in this session."
- % (orm_util.state_str(state), state.key)
- )
- else:
- return False
- self._dict[key] = state
- self._manage_incoming_state(state)
- return True
-
- def _add_unpresent(
- self, state: InstanceState[Any], key: _IdentityKeyType[Any]
- ) -> None:
- # inlined form of add() called by loading.py
- self._dict[key] = state
- state._instance_dict = self._wr
-
- def fast_get_state(
- self, key: _IdentityKeyType[_O]
- ) -> Optional[InstanceState[_O]]:
- return self._dict.get(key)
-
- def get(
- self, key: _IdentityKeyType[_O], default: Optional[_O] = None
- ) -> Optional[_O]:
- if key not in self._dict:
- return default
- try:
- state = cast("InstanceState[_O]", self._dict[key])
- except KeyError:
- # catch gc removed the key after we just checked for it
- return default
- else:
- o = state.obj()
- if o is None:
- return default
- return o
-
- def items(self) -> List[Tuple[_IdentityKeyType[Any], InstanceState[Any]]]:
- values = self.all_states()
- result = []
- for state in values:
- value = state.obj()
- key = state.key
- assert key is not None
- if value is not None:
- result.append((key, value))
- return result
-
- def values(self) -> List[object]:
- values = self.all_states()
- result = []
- for state in values:
- value = state.obj()
- if value is not None:
- result.append(value)
-
- return result
-
- def __iter__(self) -> Iterator[_IdentityKeyType[Any]]:
- return iter(self.keys())
-
- def all_states(self) -> List[InstanceState[Any]]:
- return list(self._dict.values())
-
- def _fast_discard(self, state: InstanceState[Any]) -> None:
- # used by InstanceState for state being
- # GC'ed, inlines _managed_removed_state
- key = state.key
- assert key is not None
- try:
- st = self._dict[key]
- except KeyError:
- # catch gc removed the key after we just checked for it
- pass
- else:
- if st is state:
- self._dict.pop(key, None)
-
- def discard(self, state: InstanceState[Any]) -> None:
- self.safe_discard(state)
-
- def safe_discard(self, state: InstanceState[Any]) -> None:
- key = state.key
- if key in self._dict:
- assert key is not None
- try:
- st = self._dict[key]
- except KeyError:
- # catch gc removed the key after we just checked for it
- pass
- else:
- if st is state:
- self._dict.pop(key, None)
- self._manage_removed_state(state)
-
-
-def _killed(state: InstanceState[Any], key: _IdentityKeyType[Any]) -> NoReturn:
- # external function to avoid creating cycles when assigned to
- # the IdentityMap
- raise sa_exc.InvalidRequestError(
- "Object %s cannot be converted to 'persistent' state, as this "
- "identity map is no longer valid. Has the owning Session "
- "been closed?" % orm_util.state_str(state),
- code="lkrp",
- )
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/instrumentation.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/instrumentation.py
deleted file mode 100644
index e9fe843..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/instrumentation.py
+++ /dev/null
@@ -1,754 +0,0 @@
-# orm/instrumentation.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
-# mypy: allow-untyped-defs, allow-untyped-calls
-
-"""Defines SQLAlchemy's system of class instrumentation.
-
-This module is usually not directly visible to user applications, but
-defines a large part of the ORM's interactivity.
-
-instrumentation.py deals with registration of end-user classes
-for state tracking. It interacts closely with state.py
-and attributes.py which establish per-instance and per-class-attribute
-instrumentation, respectively.
-
-The class instrumentation system can be customized on a per-class
-or global basis using the :mod:`sqlalchemy.ext.instrumentation`
-module, which provides the means to build and specify
-alternate instrumentation forms.
-
-.. versionchanged: 0.8
- The instrumentation extension system was moved out of the
- ORM and into the external :mod:`sqlalchemy.ext.instrumentation`
- package. When that package is imported, it installs
- itself within sqlalchemy.orm so that its more comprehensive
- resolution mechanics take effect.
-
-"""
-
-
-from __future__ import annotations
-
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Collection
-from typing import Dict
-from typing import Generic
-from typing import Iterable
-from typing import List
-from typing import Optional
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-import weakref
-
-from . import base
-from . import collections
-from . import exc
-from . import interfaces
-from . import state
-from ._typing import _O
-from .attributes import _is_collection_attribute_impl
-from .. import util
-from ..event import EventTarget
-from ..util import HasMemoized
-from ..util.typing import Literal
-from ..util.typing import Protocol
-
-if TYPE_CHECKING:
- from ._typing import _RegistryType
- from .attributes import AttributeImpl
- from .attributes import QueryableAttribute
- from .collections import _AdaptedCollectionProtocol
- from .collections import _CollectionFactoryType
- from .decl_base import _MapperConfig
- from .events import InstanceEvents
- from .mapper import Mapper
- from .state import InstanceState
- from ..event import dispatcher
-
-_T = TypeVar("_T", bound=Any)
-DEL_ATTR = util.symbol("DEL_ATTR")
-
-
-class _ExpiredAttributeLoaderProto(Protocol):
- def __call__(
- self,
- state: state.InstanceState[Any],
- toload: Set[str],
- passive: base.PassiveFlag,
- ) -> None: ...
-
-
-class _ManagerFactory(Protocol):
- def __call__(self, class_: Type[_O]) -> ClassManager[_O]: ...
-
-
-class ClassManager(
- HasMemoized,
- Dict[str, "QueryableAttribute[Any]"],
- Generic[_O],
- EventTarget,
-):
- """Tracks state information at the class level."""
-
- dispatch: dispatcher[ClassManager[_O]]
-
- MANAGER_ATTR = base.DEFAULT_MANAGER_ATTR
- STATE_ATTR = base.DEFAULT_STATE_ATTR
-
- _state_setter = staticmethod(util.attrsetter(STATE_ATTR))
-
- expired_attribute_loader: _ExpiredAttributeLoaderProto
- "previously known as deferred_scalar_loader"
-
- init_method: Optional[Callable[..., None]]
- original_init: Optional[Callable[..., None]] = None
-
- factory: Optional[_ManagerFactory]
-
- declarative_scan: Optional[weakref.ref[_MapperConfig]] = None
-
- registry: _RegistryType
-
- if not TYPE_CHECKING:
- # starts as None during setup
- registry = None
-
- class_: Type[_O]
-
- _bases: List[ClassManager[Any]]
-
- @property
- @util.deprecated(
- "1.4",
- message="The ClassManager.deferred_scalar_loader attribute is now "
- "named expired_attribute_loader",
- )
- def deferred_scalar_loader(self):
- return self.expired_attribute_loader
-
- @deferred_scalar_loader.setter
- @util.deprecated(
- "1.4",
- message="The ClassManager.deferred_scalar_loader attribute is now "
- "named expired_attribute_loader",
- )
- def deferred_scalar_loader(self, obj):
- self.expired_attribute_loader = obj
-
- def __init__(self, class_):
- self.class_ = class_
- self.info = {}
- self.new_init = None
- self.local_attrs = {}
- self.originals = {}
- self._finalized = False
- self.factory = None
- self.init_method = None
-
- self._bases = [
- mgr
- for mgr in cast(
- "List[Optional[ClassManager[Any]]]",
- [
- opt_manager_of_class(base)
- for base in self.class_.__bases__
- if isinstance(base, type)
- ],
- )
- if mgr is not None
- ]
-
- for base_ in self._bases:
- self.update(base_)
-
- cast(
- "InstanceEvents", self.dispatch._events
- )._new_classmanager_instance(class_, self)
-
- for basecls in class_.__mro__:
- mgr = opt_manager_of_class(basecls)
- if mgr is not None:
- self.dispatch._update(mgr.dispatch)
-
- self.manage()
-
- if "__del__" in class_.__dict__:
- util.warn(
- "__del__() method on class %s will "
- "cause unreachable cycles and memory leaks, "
- "as SQLAlchemy instrumentation often creates "
- "reference cycles. Please remove this method." % class_
- )
-
- def _update_state(
- self,
- finalize: bool = False,
- mapper: Optional[Mapper[_O]] = None,
- registry: Optional[_RegistryType] = None,
- declarative_scan: Optional[_MapperConfig] = None,
- expired_attribute_loader: Optional[
- _ExpiredAttributeLoaderProto
- ] = None,
- init_method: Optional[Callable[..., None]] = None,
- ) -> None:
- if mapper:
- self.mapper = mapper #
- if registry:
- registry._add_manager(self)
- if declarative_scan:
- self.declarative_scan = weakref.ref(declarative_scan)
- if expired_attribute_loader:
- self.expired_attribute_loader = expired_attribute_loader
-
- if init_method:
- assert not self._finalized, (
- "class is already instrumented, "
- "init_method %s can't be applied" % init_method
- )
- self.init_method = init_method
-
- if not self._finalized:
- self.original_init = (
- self.init_method
- if self.init_method is not None
- and self.class_.__init__ is object.__init__
- else self.class_.__init__
- )
-
- if finalize and not self._finalized:
- self._finalize()
-
- def _finalize(self) -> None:
- if self._finalized:
- return
- self._finalized = True
-
- self._instrument_init()
-
- _instrumentation_factory.dispatch.class_instrument(self.class_)
-
- def __hash__(self) -> int: # type: ignore[override]
- return id(self)
-
- def __eq__(self, other: Any) -> bool:
- return other is self
-
- @property
- def is_mapped(self) -> bool:
- return "mapper" in self.__dict__
-
- @HasMemoized.memoized_attribute
- def _all_key_set(self):
- return frozenset(self)
-
- @HasMemoized.memoized_attribute
- def _collection_impl_keys(self):
- return frozenset(
- [attr.key for attr in self.values() if attr.impl.collection]
- )
-
- @HasMemoized.memoized_attribute
- def _scalar_loader_impls(self):
- return frozenset(
- [
- attr.impl
- for attr in self.values()
- if attr.impl.accepts_scalar_loader
- ]
- )
-
- @HasMemoized.memoized_attribute
- def _loader_impls(self):
- return frozenset([attr.impl for attr in self.values()])
-
- @util.memoized_property
- def mapper(self) -> Mapper[_O]:
- # raises unless self.mapper has been assigned
- raise exc.UnmappedClassError(self.class_)
-
- def _all_sqla_attributes(self, exclude=None):
- """return an iterator of all classbound attributes that are
- implement :class:`.InspectionAttr`.
-
- This includes :class:`.QueryableAttribute` as well as extension
- types such as :class:`.hybrid_property` and
- :class:`.AssociationProxy`.
-
- """
-
- found: Dict[str, Any] = {}
-
- # constraints:
- # 1. yield keys in cls.__dict__ order
- # 2. if a subclass has the same key as a superclass, include that
- # key as part of the ordering of the superclass, because an
- # overridden key is usually installed by the mapper which is going
- # on a different ordering
- # 3. don't use getattr() as this fires off descriptors
-
- for supercls in self.class_.__mro__[0:-1]:
- inherits = supercls.__mro__[1]
- for key in supercls.__dict__:
- found.setdefault(key, supercls)
- if key in inherits.__dict__:
- continue
- val = found[key].__dict__[key]
- if (
- isinstance(val, interfaces.InspectionAttr)
- and val.is_attribute
- ):
- yield key, val
-
- def _get_class_attr_mro(self, key, default=None):
- """return an attribute on the class without tripping it."""
-
- for supercls in self.class_.__mro__:
- if key in supercls.__dict__:
- return supercls.__dict__[key]
- else:
- return default
-
- def _attr_has_impl(self, key: str) -> bool:
- """Return True if the given attribute is fully initialized.
-
- i.e. has an impl.
- """
-
- return key in self and self[key].impl is not None
-
- def _subclass_manager(self, cls: Type[_T]) -> ClassManager[_T]:
- """Create a new ClassManager for a subclass of this ClassManager's
- class.
-
- This is called automatically when attributes are instrumented so that
- the attributes can be propagated to subclasses against their own
- class-local manager, without the need for mappers etc. to have already
- pre-configured managers for the full class hierarchy. Mappers
- can post-configure the auto-generated ClassManager when needed.
-
- """
- return register_class(cls, finalize=False)
-
- def _instrument_init(self):
- self.new_init = _generate_init(self.class_, self, self.original_init)
- self.install_member("__init__", self.new_init)
-
- @util.memoized_property
- def _state_constructor(self) -> Type[state.InstanceState[_O]]:
- self.dispatch.first_init(self, self.class_)
- return state.InstanceState
-
- def manage(self):
- """Mark this instance as the manager for its class."""
-
- setattr(self.class_, self.MANAGER_ATTR, self)
-
- @util.hybridmethod
- def manager_getter(self):
- return _default_manager_getter
-
- @util.hybridmethod
- def state_getter(self):
- """Return a (instance) -> InstanceState callable.
-
- "state getter" callables should raise either KeyError or
- AttributeError if no InstanceState could be found for the
- instance.
- """
-
- return _default_state_getter
-
- @util.hybridmethod
- def dict_getter(self):
- return _default_dict_getter
-
- def instrument_attribute(
- self,
- key: str,
- inst: QueryableAttribute[Any],
- propagated: bool = False,
- ) -> None:
- if propagated:
- if key in self.local_attrs:
- return # don't override local attr with inherited attr
- else:
- self.local_attrs[key] = inst
- self.install_descriptor(key, inst)
- self._reset_memoizations()
- self[key] = inst
-
- for cls in self.class_.__subclasses__():
- manager = self._subclass_manager(cls)
- manager.instrument_attribute(key, inst, True)
-
- def subclass_managers(self, recursive):
- for cls in self.class_.__subclasses__():
- mgr = opt_manager_of_class(cls)
- if mgr is not None and mgr is not self:
- yield mgr
- if recursive:
- yield from mgr.subclass_managers(True)
-
- def post_configure_attribute(self, key):
- _instrumentation_factory.dispatch.attribute_instrument(
- self.class_, key, self[key]
- )
-
- def uninstrument_attribute(self, key, propagated=False):
- if key not in self:
- return
- if propagated:
- if key in self.local_attrs:
- return # don't get rid of local attr
- else:
- del self.local_attrs[key]
- self.uninstall_descriptor(key)
- self._reset_memoizations()
- del self[key]
- for cls in self.class_.__subclasses__():
- manager = opt_manager_of_class(cls)
- if manager:
- manager.uninstrument_attribute(key, True)
-
- def unregister(self) -> None:
- """remove all instrumentation established by this ClassManager."""
-
- for key in list(self.originals):
- self.uninstall_member(key)
-
- self.mapper = None
- self.dispatch = None # type: ignore
- self.new_init = None
- self.info.clear()
-
- for key in list(self):
- if key in self.local_attrs:
- self.uninstrument_attribute(key)
-
- if self.MANAGER_ATTR in self.class_.__dict__:
- delattr(self.class_, self.MANAGER_ATTR)
-
- def install_descriptor(
- self, key: str, inst: QueryableAttribute[Any]
- ) -> None:
- if key in (self.STATE_ATTR, self.MANAGER_ATTR):
- raise KeyError(
- "%r: requested attribute name conflicts with "
- "instrumentation attribute of the same name." % key
- )
- setattr(self.class_, key, inst)
-
- def uninstall_descriptor(self, key: str) -> None:
- delattr(self.class_, key)
-
- def install_member(self, key: str, implementation: Any) -> None:
- if key in (self.STATE_ATTR, self.MANAGER_ATTR):
- raise KeyError(
- "%r: requested attribute name conflicts with "
- "instrumentation attribute of the same name." % key
- )
- self.originals.setdefault(key, self.class_.__dict__.get(key, DEL_ATTR))
- setattr(self.class_, key, implementation)
-
- def uninstall_member(self, key: str) -> None:
- original = self.originals.pop(key, None)
- if original is not DEL_ATTR:
- setattr(self.class_, key, original)
- else:
- delattr(self.class_, key)
-
- def instrument_collection_class(
- self, key: str, collection_class: Type[Collection[Any]]
- ) -> _CollectionFactoryType:
- return collections.prepare_instrumentation(collection_class)
-
- def initialize_collection(
- self,
- key: str,
- state: InstanceState[_O],
- factory: _CollectionFactoryType,
- ) -> Tuple[collections.CollectionAdapter, _AdaptedCollectionProtocol]:
- user_data = factory()
- impl = self.get_impl(key)
- assert _is_collection_attribute_impl(impl)
- adapter = collections.CollectionAdapter(impl, state, user_data)
- return adapter, user_data
-
- def is_instrumented(self, key: str, search: bool = False) -> bool:
- if search:
- return key in self
- else:
- return key in self.local_attrs
-
- def get_impl(self, key: str) -> AttributeImpl:
- return self[key].impl
-
- @property
- def attributes(self) -> Iterable[Any]:
- return iter(self.values())
-
- # InstanceState management
-
- def new_instance(self, state: Optional[InstanceState[_O]] = None) -> _O:
- # here, we would prefer _O to be bound to "object"
- # so that mypy sees that __new__ is present. currently
- # it's bound to Any as there were other problems not having
- # it that way but these can be revisited
- instance = self.class_.__new__(self.class_)
- if state is None:
- state = self._state_constructor(instance, self)
- self._state_setter(instance, state)
- return instance
-
- def setup_instance(
- self, instance: _O, state: Optional[InstanceState[_O]] = None
- ) -> None:
- if state is None:
- state = self._state_constructor(instance, self)
- self._state_setter(instance, state)
-
- def teardown_instance(self, instance: _O) -> None:
- delattr(instance, self.STATE_ATTR)
-
- def _serialize(
- self, state: InstanceState[_O], state_dict: Dict[str, Any]
- ) -> _SerializeManager:
- return _SerializeManager(state, state_dict)
-
- def _new_state_if_none(
- self, instance: _O
- ) -> Union[Literal[False], InstanceState[_O]]:
- """Install a default InstanceState if none is present.
-
- A private convenience method used by the __init__ decorator.
-
- """
- if hasattr(instance, self.STATE_ATTR):
- return False
- elif self.class_ is not instance.__class__ and self.is_mapped:
- # this will create a new ClassManager for the
- # subclass, without a mapper. This is likely a
- # user error situation but allow the object
- # to be constructed, so that it is usable
- # in a non-ORM context at least.
- return self._subclass_manager(
- instance.__class__
- )._new_state_if_none(instance)
- else:
- state = self._state_constructor(instance, self)
- self._state_setter(instance, state)
- return state
-
- def has_state(self, instance: _O) -> bool:
- return hasattr(instance, self.STATE_ATTR)
-
- def has_parent(
- self, state: InstanceState[_O], key: str, optimistic: bool = False
- ) -> bool:
- """TODO"""
- return self.get_impl(key).hasparent(state, optimistic=optimistic)
-
- def __bool__(self) -> bool:
- """All ClassManagers are non-zero regardless of attribute state."""
- return True
-
- def __repr__(self) -> str:
- return "<%s of %r at %x>" % (
- self.__class__.__name__,
- self.class_,
- id(self),
- )
-
-
-class _SerializeManager:
- """Provide serialization of a :class:`.ClassManager`.
-
- The :class:`.InstanceState` uses ``__init__()`` on serialize
- and ``__call__()`` on deserialize.
-
- """
-
- def __init__(self, state: state.InstanceState[Any], d: Dict[str, Any]):
- self.class_ = state.class_
- manager = state.manager
- manager.dispatch.pickle(state, d)
-
- def __call__(self, state, inst, state_dict):
- state.manager = manager = opt_manager_of_class(self.class_)
- if manager is None:
- raise exc.UnmappedInstanceError(
- inst,
- "Cannot deserialize object of type %r - "
- "no mapper() has "
- "been configured for this class within the current "
- "Python process!" % self.class_,
- )
- elif manager.is_mapped and not manager.mapper.configured:
- manager.mapper._check_configure()
-
- # setup _sa_instance_state ahead of time so that
- # unpickle events can access the object normally.
- # see [ticket:2362]
- if inst is not None:
- manager.setup_instance(inst, state)
- manager.dispatch.unpickle(state, state_dict)
-
-
-class InstrumentationFactory(EventTarget):
- """Factory for new ClassManager instances."""
-
- dispatch: dispatcher[InstrumentationFactory]
-
- def create_manager_for_cls(self, class_: Type[_O]) -> ClassManager[_O]:
- assert class_ is not None
- assert opt_manager_of_class(class_) is None
-
- # give a more complicated subclass
- # a chance to do what it wants here
- manager, factory = self._locate_extended_factory(class_)
-
- if factory is None:
- factory = ClassManager
- manager = ClassManager(class_)
- else:
- assert manager is not None
-
- self._check_conflicts(class_, factory)
-
- manager.factory = factory
-
- return manager
-
- def _locate_extended_factory(
- self, class_: Type[_O]
- ) -> Tuple[Optional[ClassManager[_O]], Optional[_ManagerFactory]]:
- """Overridden by a subclass to do an extended lookup."""
- return None, None
-
- def _check_conflicts(
- self, class_: Type[_O], factory: Callable[[Type[_O]], ClassManager[_O]]
- ) -> None:
- """Overridden by a subclass to test for conflicting factories."""
-
- def unregister(self, class_: Type[_O]) -> None:
- manager = manager_of_class(class_)
- manager.unregister()
- self.dispatch.class_uninstrument(class_)
-
-
-# this attribute is replaced by sqlalchemy.ext.instrumentation
-# when imported.
-_instrumentation_factory = InstrumentationFactory()
-
-# these attributes are replaced by sqlalchemy.ext.instrumentation
-# when a non-standard InstrumentationManager class is first
-# used to instrument a class.
-instance_state = _default_state_getter = base.instance_state
-
-instance_dict = _default_dict_getter = base.instance_dict
-
-manager_of_class = _default_manager_getter = base.manager_of_class
-opt_manager_of_class = _default_opt_manager_getter = base.opt_manager_of_class
-
-
-def register_class(
- class_: Type[_O],
- finalize: bool = True,
- mapper: Optional[Mapper[_O]] = None,
- registry: Optional[_RegistryType] = None,
- declarative_scan: Optional[_MapperConfig] = None,
- expired_attribute_loader: Optional[_ExpiredAttributeLoaderProto] = None,
- init_method: Optional[Callable[..., None]] = None,
-) -> ClassManager[_O]:
- """Register class instrumentation.
-
- Returns the existing or newly created class manager.
-
- """
-
- manager = opt_manager_of_class(class_)
- if manager is None:
- manager = _instrumentation_factory.create_manager_for_cls(class_)
- manager._update_state(
- mapper=mapper,
- registry=registry,
- declarative_scan=declarative_scan,
- expired_attribute_loader=expired_attribute_loader,
- init_method=init_method,
- finalize=finalize,
- )
-
- return manager
-
-
-def unregister_class(class_):
- """Unregister class instrumentation."""
-
- _instrumentation_factory.unregister(class_)
-
-
-def is_instrumented(instance, key):
- """Return True if the given attribute on the given instance is
- instrumented by the attributes package.
-
- This function may be used regardless of instrumentation
- applied directly to the class, i.e. no descriptors are required.
-
- """
- return manager_of_class(instance.__class__).is_instrumented(
- key, search=True
- )
-
-
-def _generate_init(class_, class_manager, original_init):
- """Build an __init__ decorator that triggers ClassManager events."""
-
- # TODO: we should use the ClassManager's notion of the
- # original '__init__' method, once ClassManager is fixed
- # to always reference that.
-
- if original_init is None:
- original_init = class_.__init__
-
- # Go through some effort here and don't change the user's __init__
- # calling signature, including the unlikely case that it has
- # a return value.
- # FIXME: need to juggle local names to avoid constructor argument
- # clashes.
- func_body = """\
-def __init__(%(apply_pos)s):
- new_state = class_manager._new_state_if_none(%(self_arg)s)
- if new_state:
- return new_state._initialize_instance(%(apply_kw)s)
- else:
- return original_init(%(apply_kw)s)
-"""
- func_vars = util.format_argspec_init(original_init, grouped=False)
- func_text = func_body % func_vars
-
- func_defaults = getattr(original_init, "__defaults__", None)
- func_kw_defaults = getattr(original_init, "__kwdefaults__", None)
-
- env = locals().copy()
- env["__name__"] = __name__
- exec(func_text, env)
- __init__ = env["__init__"]
- __init__.__doc__ = original_init.__doc__
- __init__._sa_original_init = original_init
-
- if func_defaults:
- __init__.__defaults__ = func_defaults
- if func_kw_defaults:
- __init__.__kwdefaults__ = func_kw_defaults
-
- return __init__
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/interfaces.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/interfaces.py
deleted file mode 100644
index 36336e7..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/interfaces.py
+++ /dev/null
@@ -1,1469 +0,0 @@
-# orm/interfaces.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
-
-"""
-
-Contains various base classes used throughout the ORM.
-
-Defines some key base classes prominent within the internals.
-
-This module and the classes within are mostly private, though some attributes
-are exposed when inspecting mappings.
-
-"""
-
-from __future__ import annotations
-
-import collections
-import dataclasses
-import typing
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import ClassVar
-from typing import Dict
-from typing import Generic
-from typing import Iterator
-from typing import List
-from typing import NamedTuple
-from typing import NoReturn
-from typing import Optional
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from . import exc as orm_exc
-from . import path_registry
-from .base import _MappedAttribute as _MappedAttribute
-from .base import EXT_CONTINUE as EXT_CONTINUE # noqa: F401
-from .base import EXT_SKIP as EXT_SKIP # noqa: F401
-from .base import EXT_STOP as EXT_STOP # noqa: F401
-from .base import InspectionAttr as InspectionAttr # noqa: F401
-from .base import InspectionAttrInfo as InspectionAttrInfo
-from .base import MANYTOMANY as MANYTOMANY # noqa: F401
-from .base import MANYTOONE as MANYTOONE # noqa: F401
-from .base import NO_KEY as NO_KEY # noqa: F401
-from .base import NO_VALUE as NO_VALUE # noqa: F401
-from .base import NotExtension as NotExtension # noqa: F401
-from .base import ONETOMANY as ONETOMANY # noqa: F401
-from .base import RelationshipDirection as RelationshipDirection # noqa: F401
-from .base import SQLORMOperations
-from .. import ColumnElement
-from .. import exc as sa_exc
-from .. import inspection
-from .. import util
-from ..sql import operators
-from ..sql import roles
-from ..sql import visitors
-from ..sql.base import _NoArg
-from ..sql.base import ExecutableOption
-from ..sql.cache_key import HasCacheKey
-from ..sql.operators import ColumnOperators
-from ..sql.schema import Column
-from ..sql.type_api import TypeEngine
-from ..util import warn_deprecated
-from ..util.typing import RODescriptorReference
-from ..util.typing import TypedDict
-
-if typing.TYPE_CHECKING:
- from ._typing import _EntityType
- from ._typing import _IdentityKeyType
- from ._typing import _InstanceDict
- from ._typing import _InternalEntityType
- from ._typing import _ORMAdapterProto
- from .attributes import InstrumentedAttribute
- from .base import Mapped
- from .context import _MapperEntity
- from .context import ORMCompileState
- from .context import QueryContext
- from .decl_api import RegistryType
- from .decl_base import _ClassScanMapperConfig
- from .loading import _PopulatorDict
- from .mapper import Mapper
- from .path_registry import AbstractEntityRegistry
- from .query import Query
- from .session import Session
- from .state import InstanceState
- from .strategy_options import _LoadElement
- from .util import AliasedInsp
- from .util import ORMAdapter
- from ..engine.result import Result
- from ..sql._typing import _ColumnExpressionArgument
- from ..sql._typing import _ColumnsClauseArgument
- from ..sql._typing import _DMLColumnArgument
- from ..sql._typing import _InfoType
- from ..sql.operators import OperatorType
- from ..sql.visitors import _TraverseInternalsType
- from ..util.typing import _AnnotationScanType
-
-_StrategyKey = Tuple[Any, ...]
-
-_T = TypeVar("_T", bound=Any)
-_T_co = TypeVar("_T_co", bound=Any, covariant=True)
-
-_TLS = TypeVar("_TLS", bound="Type[LoaderStrategy]")
-
-
-class ORMStatementRole(roles.StatementRole):
- __slots__ = ()
- _role_name = (
- "Executable SQL or text() construct, including ORM aware objects"
- )
-
-
-class ORMColumnsClauseRole(
- roles.ColumnsClauseRole, roles.TypedColumnsClauseRole[_T]
-):
- __slots__ = ()
- _role_name = "ORM mapped entity, aliased entity, or Column expression"
-
-
-class ORMEntityColumnsClauseRole(ORMColumnsClauseRole[_T]):
- __slots__ = ()
- _role_name = "ORM mapped or aliased entity"
-
-
-class ORMFromClauseRole(roles.StrictFromClauseRole):
- __slots__ = ()
- _role_name = "ORM mapped entity, aliased entity, or FROM expression"
-
-
-class ORMColumnDescription(TypedDict):
- name: str
- # TODO: add python_type and sql_type here; combining them
- # into "type" is a bad idea
- type: Union[Type[Any], TypeEngine[Any]]
- aliased: bool
- expr: _ColumnsClauseArgument[Any]
- entity: Optional[_ColumnsClauseArgument[Any]]
-
-
-class _IntrospectsAnnotations:
- __slots__ = ()
-
- @classmethod
- def _mapper_property_name(cls) -> str:
- return cls.__name__
-
- def found_in_pep593_annotated(self) -> Any:
- """return a copy of this object to use in declarative when the
- object is found inside of an Annotated object."""
-
- raise NotImplementedError(
- f"Use of the {self._mapper_property_name()!r} "
- "construct inside of an Annotated object is not yet supported."
- )
-
- def declarative_scan(
- self,
- decl_scan: _ClassScanMapperConfig,
- registry: RegistryType,
- cls: Type[Any],
- originating_module: Optional[str],
- key: str,
- mapped_container: Optional[Type[Mapped[Any]]],
- annotation: Optional[_AnnotationScanType],
- extracted_mapped_annotation: Optional[_AnnotationScanType],
- is_dataclass_field: bool,
- ) -> None:
- """Perform class-specific initializaton at early declarative scanning
- time.
-
- .. versionadded:: 2.0
-
- """
-
- def _raise_for_required(self, key: str, cls: Type[Any]) -> NoReturn:
- raise sa_exc.ArgumentError(
- f"Python typing annotation is required for attribute "
- f'"{cls.__name__}.{key}" when primary argument(s) for '
- f'"{self._mapper_property_name()}" '
- "construct are None or not present"
- )
-
-
-class _AttributeOptions(NamedTuple):
- """define Python-local attribute behavior options common to all
- :class:`.MapperProperty` objects.
-
- Currently this includes dataclass-generation arguments.
-
- .. versionadded:: 2.0
-
- """
-
- dataclasses_init: Union[_NoArg, bool]
- dataclasses_repr: Union[_NoArg, bool]
- dataclasses_default: Union[_NoArg, Any]
- dataclasses_default_factory: Union[_NoArg, Callable[[], Any]]
- dataclasses_compare: Union[_NoArg, bool]
- dataclasses_kw_only: Union[_NoArg, bool]
-
- def _as_dataclass_field(self, key: str) -> Any:
- """Return a ``dataclasses.Field`` object given these arguments."""
-
- kw: Dict[str, Any] = {}
- if self.dataclasses_default_factory is not _NoArg.NO_ARG:
- kw["default_factory"] = self.dataclasses_default_factory
- if self.dataclasses_default is not _NoArg.NO_ARG:
- kw["default"] = self.dataclasses_default
- if self.dataclasses_init is not _NoArg.NO_ARG:
- kw["init"] = self.dataclasses_init
- if self.dataclasses_repr is not _NoArg.NO_ARG:
- kw["repr"] = self.dataclasses_repr
- if self.dataclasses_compare is not _NoArg.NO_ARG:
- kw["compare"] = self.dataclasses_compare
- if self.dataclasses_kw_only is not _NoArg.NO_ARG:
- kw["kw_only"] = self.dataclasses_kw_only
-
- if "default" in kw and callable(kw["default"]):
- # callable defaults are ambiguous. deprecate them in favour of
- # insert_default or default_factory. #9936
- warn_deprecated(
- f"Callable object passed to the ``default`` parameter for "
- f"attribute {key!r} in a ORM-mapped Dataclasses context is "
- "ambiguous, "
- "and this use will raise an error in a future release. "
- "If this callable is intended to produce Core level INSERT "
- "default values for an underlying ``Column``, use "
- "the ``mapped_column.insert_default`` parameter instead. "
- "To establish this callable as providing a default value "
- "for instances of the dataclass itself, use the "
- "``default_factory`` dataclasses parameter.",
- "2.0",
- )
-
- if (
- "init" in kw
- and not kw["init"]
- and "default" in kw
- and not callable(kw["default"]) # ignore callable defaults. #9936
- and "default_factory" not in kw # illegal but let dc.field raise
- ):
- # fix for #9879
- default = kw.pop("default")
- kw["default_factory"] = lambda: default
-
- return dataclasses.field(**kw)
-
- @classmethod
- def _get_arguments_for_make_dataclass(
- cls,
- key: str,
- annotation: _AnnotationScanType,
- mapped_container: Optional[Any],
- elem: _T,
- ) -> Union[
- Tuple[str, _AnnotationScanType],
- Tuple[str, _AnnotationScanType, dataclasses.Field[Any]],
- ]:
- """given attribute key, annotation, and value from a class, return
- the argument tuple we would pass to dataclasses.make_dataclass()
- for this attribute.
-
- """
- if isinstance(elem, _DCAttributeOptions):
- dc_field = elem._attribute_options._as_dataclass_field(key)
-
- return (key, annotation, dc_field)
- elif elem is not _NoArg.NO_ARG:
- # why is typing not erroring on this?
- return (key, annotation, elem)
- elif mapped_container is not None:
- # it's Mapped[], but there's no "element", which means declarative
- # did not actually do anything for this field. this shouldn't
- # happen.
- # previously, this would occur because _scan_attributes would
- # skip a field that's on an already mapped superclass, but it
- # would still include it in the annotations, leading
- # to issue #8718
-
- assert False, "Mapped[] received without a mapping declaration"
-
- else:
- # plain dataclass field, not mapped. Is only possible
- # if __allow_unmapped__ is set up. I can see this mode causing
- # problems...
- return (key, annotation)
-
-
-_DEFAULT_ATTRIBUTE_OPTIONS = _AttributeOptions(
- _NoArg.NO_ARG,
- _NoArg.NO_ARG,
- _NoArg.NO_ARG,
- _NoArg.NO_ARG,
- _NoArg.NO_ARG,
- _NoArg.NO_ARG,
-)
-
-_DEFAULT_READONLY_ATTRIBUTE_OPTIONS = _AttributeOptions(
- False,
- _NoArg.NO_ARG,
- _NoArg.NO_ARG,
- _NoArg.NO_ARG,
- _NoArg.NO_ARG,
- _NoArg.NO_ARG,
-)
-
-
-class _DCAttributeOptions:
- """mixin for descriptors or configurational objects that include dataclass
- field options.
-
- This includes :class:`.MapperProperty`, :class:`._MapsColumn` within
- the ORM, but also includes :class:`.AssociationProxy` within ext.
- Can in theory be used for other descriptors that serve a similar role
- as association proxy. (*maybe* hybrids, not sure yet.)
-
- """
-
- __slots__ = ()
-
- _attribute_options: _AttributeOptions
- """behavioral options for ORM-enabled Python attributes
-
- .. versionadded:: 2.0
-
- """
-
- _has_dataclass_arguments: bool
-
-
-class _MapsColumns(_DCAttributeOptions, _MappedAttribute[_T]):
- """interface for declarative-capable construct that delivers one or more
- Column objects to the declarative process to be part of a Table.
- """
-
- __slots__ = ()
-
- @property
- def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]:
- """return a MapperProperty to be assigned to the declarative mapping"""
- raise NotImplementedError()
-
- @property
- def columns_to_assign(self) -> List[Tuple[Column[_T], int]]:
- """A list of Column objects that should be declaratively added to the
- new Table object.
-
- """
- raise NotImplementedError()
-
-
-# NOTE: MapperProperty needs to extend _MappedAttribute so that declarative
-# typing works, i.e. "Mapped[A] = relationship()". This introduces an
-# inconvenience which is that all the MapperProperty objects are treated
-# as descriptors by typing tools, which are misled by this as assignment /
-# access to a descriptor attribute wants to move through __get__.
-# Therefore, references to MapperProperty as an instance variable, such
-# as in PropComparator, may have some special typing workarounds such as the
-# use of sqlalchemy.util.typing.DescriptorReference to avoid mis-interpretation
-# by typing tools
-@inspection._self_inspects
-class MapperProperty(
- HasCacheKey,
- _DCAttributeOptions,
- _MappedAttribute[_T],
- InspectionAttrInfo,
- util.MemoizedSlots,
-):
- """Represent a particular class attribute mapped by :class:`_orm.Mapper`.
-
- The most common occurrences of :class:`.MapperProperty` are the
- mapped :class:`_schema.Column`, which is represented in a mapping as
- an instance of :class:`.ColumnProperty`,
- and a reference to another class produced by :func:`_orm.relationship`,
- represented in the mapping as an instance of
- :class:`.Relationship`.
-
- """
-
- __slots__ = (
- "_configure_started",
- "_configure_finished",
- "_attribute_options",
- "_has_dataclass_arguments",
- "parent",
- "key",
- "info",
- "doc",
- )
-
- _cache_key_traversal: _TraverseInternalsType = [
- ("parent", visitors.ExtendedInternalTraversal.dp_has_cache_key),
- ("key", visitors.ExtendedInternalTraversal.dp_string),
- ]
-
- if not TYPE_CHECKING:
- cascade = None
-
- is_property = True
- """Part of the InspectionAttr interface; states this object is a
- mapper property.
-
- """
-
- comparator: PropComparator[_T]
- """The :class:`_orm.PropComparator` instance that implements SQL
- expression construction on behalf of this mapped attribute."""
-
- key: str
- """name of class attribute"""
-
- parent: Mapper[Any]
- """the :class:`.Mapper` managing this property."""
-
- _is_relationship = False
-
- _links_to_entity: bool
- """True if this MapperProperty refers to a mapped entity.
-
- Should only be True for Relationship, False for all others.
-
- """
-
- doc: Optional[str]
- """optional documentation string"""
-
- info: _InfoType
- """Info dictionary associated with the object, allowing user-defined
- data to be associated with this :class:`.InspectionAttr`.
-
- The dictionary is generated when first accessed. Alternatively,
- it can be specified as a constructor argument to the
- :func:`.column_property`, :func:`_orm.relationship`, or :func:`.composite`
- functions.
-
- .. seealso::
-
- :attr:`.QueryableAttribute.info`
-
- :attr:`.SchemaItem.info`
-
- """
-
- def _memoized_attr_info(self) -> _InfoType:
- """Info dictionary associated with the object, allowing user-defined
- data to be associated with this :class:`.InspectionAttr`.
-
- The dictionary is generated when first accessed. Alternatively,
- it can be specified as a constructor argument to the
- :func:`.column_property`, :func:`_orm.relationship`, or
- :func:`.composite`
- functions.
-
- .. seealso::
-
- :attr:`.QueryableAttribute.info`
-
- :attr:`.SchemaItem.info`
-
- """
- return {}
-
- def setup(
- self,
- context: ORMCompileState,
- query_entity: _MapperEntity,
- path: AbstractEntityRegistry,
- adapter: Optional[ORMAdapter],
- **kwargs: Any,
- ) -> None:
- """Called by Query for the purposes of constructing a SQL statement.
-
- Each MapperProperty associated with the target mapper processes the
- statement referenced by the query context, adding columns and/or
- criterion as appropriate.
-
- """
-
- def create_row_processor(
- self,
- context: ORMCompileState,
- query_entity: _MapperEntity,
- path: AbstractEntityRegistry,
- mapper: Mapper[Any],
- result: Result[Any],
- adapter: Optional[ORMAdapter],
- populators: _PopulatorDict,
- ) -> None:
- """Produce row processing functions and append to the given
- set of populators lists.
-
- """
-
- def cascade_iterator(
- self,
- type_: str,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- visited_states: Set[InstanceState[Any]],
- halt_on: Optional[Callable[[InstanceState[Any]], bool]] = None,
- ) -> Iterator[
- Tuple[object, Mapper[Any], InstanceState[Any], _InstanceDict]
- ]:
- """Iterate through instances related to the given instance for
- a particular 'cascade', starting with this MapperProperty.
-
- Return an iterator3-tuples (instance, mapper, state).
-
- Note that the 'cascade' collection on this MapperProperty is
- checked first for the given type before cascade_iterator is called.
-
- This method typically only applies to Relationship.
-
- """
-
- return iter(())
-
- def set_parent(self, parent: Mapper[Any], init: bool) -> None:
- """Set the parent mapper that references this MapperProperty.
-
- This method is overridden by some subclasses to perform extra
- setup when the mapper is first known.
-
- """
- self.parent = parent
-
- def instrument_class(self, mapper: Mapper[Any]) -> None:
- """Hook called by the Mapper to the property to initiate
- instrumentation of the class attribute managed by this
- MapperProperty.
-
- The MapperProperty here will typically call out to the
- attributes module to set up an InstrumentedAttribute.
-
- This step is the first of two steps to set up an InstrumentedAttribute,
- and is called early in the mapper setup process.
-
- The second step is typically the init_class_attribute step,
- called from StrategizedProperty via the post_instrument_class()
- hook. This step assigns additional state to the InstrumentedAttribute
- (specifically the "impl") which has been determined after the
- MapperProperty has determined what kind of persistence
- management it needs to do (e.g. scalar, object, collection, etc).
-
- """
-
- def __init__(
- self,
- attribute_options: Optional[_AttributeOptions] = None,
- _assume_readonly_dc_attributes: bool = False,
- ) -> None:
- self._configure_started = False
- self._configure_finished = False
-
- if _assume_readonly_dc_attributes:
- default_attrs = _DEFAULT_READONLY_ATTRIBUTE_OPTIONS
- else:
- default_attrs = _DEFAULT_ATTRIBUTE_OPTIONS
-
- if attribute_options and attribute_options != default_attrs:
- self._has_dataclass_arguments = True
- self._attribute_options = attribute_options
- else:
- self._has_dataclass_arguments = False
- self._attribute_options = default_attrs
-
- def init(self) -> None:
- """Called after all mappers are created to assemble
- relationships between mappers and perform other post-mapper-creation
- initialization steps.
-
-
- """
- self._configure_started = True
- self.do_init()
- self._configure_finished = True
-
- @property
- def class_attribute(self) -> InstrumentedAttribute[_T]:
- """Return the class-bound descriptor corresponding to this
- :class:`.MapperProperty`.
-
- This is basically a ``getattr()`` call::
-
- return getattr(self.parent.class_, self.key)
-
- I.e. if this :class:`.MapperProperty` were named ``addresses``,
- and the class to which it is mapped is ``User``, this sequence
- is possible::
-
- >>> from sqlalchemy import inspect
- >>> mapper = inspect(User)
- >>> addresses_property = mapper.attrs.addresses
- >>> addresses_property.class_attribute is User.addresses
- True
- >>> User.addresses.property is addresses_property
- True
-
-
- """
-
- return getattr(self.parent.class_, self.key) # type: ignore
-
- def do_init(self) -> None:
- """Perform subclass-specific initialization post-mapper-creation
- steps.
-
- This is a template method called by the ``MapperProperty``
- object's init() method.
-
- """
-
- def post_instrument_class(self, mapper: Mapper[Any]) -> None:
- """Perform instrumentation adjustments that need to occur
- after init() has completed.
-
- The given Mapper is the Mapper invoking the operation, which
- may not be the same Mapper as self.parent in an inheritance
- scenario; however, Mapper will always at least be a sub-mapper of
- self.parent.
-
- This method is typically used by StrategizedProperty, which delegates
- it to LoaderStrategy.init_class_attribute() to perform final setup
- on the class-bound InstrumentedAttribute.
-
- """
-
- def merge(
- self,
- session: Session,
- source_state: InstanceState[Any],
- source_dict: _InstanceDict,
- dest_state: InstanceState[Any],
- dest_dict: _InstanceDict,
- load: bool,
- _recursive: Dict[Any, object],
- _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
- ) -> None:
- """Merge the attribute represented by this ``MapperProperty``
- from source to destination object.
-
- """
-
- def __repr__(self) -> str:
- return "<%s at 0x%x; %s>" % (
- self.__class__.__name__,
- id(self),
- getattr(self, "key", "no key"),
- )
-
-
-@inspection._self_inspects
-class PropComparator(SQLORMOperations[_T_co], Generic[_T_co], ColumnOperators):
- r"""Defines SQL operations for ORM mapped attributes.
-
- SQLAlchemy allows for operators to
- be redefined at both the Core and ORM level. :class:`.PropComparator`
- is the base class of operator redefinition for ORM-level operations,
- including those of :class:`.ColumnProperty`,
- :class:`.Relationship`, and :class:`.Composite`.
-
- User-defined subclasses of :class:`.PropComparator` may be created. The
- built-in Python comparison and math operator methods, such as
- :meth:`.operators.ColumnOperators.__eq__`,
- :meth:`.operators.ColumnOperators.__lt__`, and
- :meth:`.operators.ColumnOperators.__add__`, can be overridden to provide
- new operator behavior. The custom :class:`.PropComparator` is passed to
- the :class:`.MapperProperty` instance via the ``comparator_factory``
- argument. In each case,
- the appropriate subclass of :class:`.PropComparator` should be used::
-
- # definition of custom PropComparator subclasses
-
- from sqlalchemy.orm.properties import \
- ColumnProperty,\
- Composite,\
- Relationship
-
- class MyColumnComparator(ColumnProperty.Comparator):
- def __eq__(self, other):
- return self.__clause_element__() == other
-
- class MyRelationshipComparator(Relationship.Comparator):
- def any(self, expression):
- "define the 'any' operation"
- # ...
-
- class MyCompositeComparator(Composite.Comparator):
- def __gt__(self, other):
- "redefine the 'greater than' operation"
-
- return sql.and_(*[a>b for a, b in
- zip(self.__clause_element__().clauses,
- other.__composite_values__())])
-
-
- # application of custom PropComparator subclasses
-
- from sqlalchemy.orm import column_property, relationship, composite
- from sqlalchemy import Column, String
-
- class SomeMappedClass(Base):
- some_column = column_property(Column("some_column", String),
- comparator_factory=MyColumnComparator)
-
- some_relationship = relationship(SomeOtherClass,
- comparator_factory=MyRelationshipComparator)
-
- some_composite = composite(
- Column("a", String), Column("b", String),
- comparator_factory=MyCompositeComparator
- )
-
- Note that for column-level operator redefinition, it's usually
- simpler to define the operators at the Core level, using the
- :attr:`.TypeEngine.comparator_factory` attribute. See
- :ref:`types_operators` for more detail.
-
- .. seealso::
-
- :class:`.ColumnProperty.Comparator`
-
- :class:`.Relationship.Comparator`
-
- :class:`.Composite.Comparator`
-
- :class:`.ColumnOperators`
-
- :ref:`types_operators`
-
- :attr:`.TypeEngine.comparator_factory`
-
- """
-
- __slots__ = "prop", "_parententity", "_adapt_to_entity"
-
- __visit_name__ = "orm_prop_comparator"
-
- _parententity: _InternalEntityType[Any]
- _adapt_to_entity: Optional[AliasedInsp[Any]]
- prop: RODescriptorReference[MapperProperty[_T_co]]
-
- def __init__(
- self,
- prop: MapperProperty[_T],
- parentmapper: _InternalEntityType[Any],
- adapt_to_entity: Optional[AliasedInsp[Any]] = None,
- ):
- self.prop = prop
- self._parententity = adapt_to_entity or parentmapper
- self._adapt_to_entity = adapt_to_entity
-
- @util.non_memoized_property
- def property(self) -> MapperProperty[_T_co]:
- """Return the :class:`.MapperProperty` associated with this
- :class:`.PropComparator`.
-
-
- Return values here will commonly be instances of
- :class:`.ColumnProperty` or :class:`.Relationship`.
-
-
- """
- return self.prop
-
- def __clause_element__(self) -> roles.ColumnsClauseRole:
- raise NotImplementedError("%r" % self)
-
- def _bulk_update_tuples(
- self, value: Any
- ) -> Sequence[Tuple[_DMLColumnArgument, Any]]:
- """Receive a SQL expression that represents a value in the SET
- clause of an UPDATE statement.
-
- Return a tuple that can be passed to a :class:`_expression.Update`
- construct.
-
- """
-
- return [(cast("_DMLColumnArgument", self.__clause_element__()), value)]
-
- def adapt_to_entity(
- self, adapt_to_entity: AliasedInsp[Any]
- ) -> PropComparator[_T_co]:
- """Return a copy of this PropComparator which will use the given
- :class:`.AliasedInsp` to produce corresponding expressions.
- """
- return self.__class__(self.prop, self._parententity, adapt_to_entity)
-
- @util.ro_non_memoized_property
- def _parentmapper(self) -> Mapper[Any]:
- """legacy; this is renamed to _parententity to be
- compatible with QueryableAttribute."""
- return self._parententity.mapper
-
- def _criterion_exists(
- self,
- criterion: Optional[_ColumnExpressionArgument[bool]] = None,
- **kwargs: Any,
- ) -> ColumnElement[Any]:
- return self.prop.comparator._criterion_exists(criterion, **kwargs)
-
- @util.ro_non_memoized_property
- def adapter(self) -> Optional[_ORMAdapterProto]:
- """Produce a callable that adapts column expressions
- to suit an aliased version of this comparator.
-
- """
- if self._adapt_to_entity is None:
- return None
- else:
- return self._adapt_to_entity._orm_adapt_element
-
- @util.ro_non_memoized_property
- def info(self) -> _InfoType:
- return self.prop.info
-
- @staticmethod
- def _any_op(a: Any, b: Any, **kwargs: Any) -> Any:
- return a.any(b, **kwargs)
-
- @staticmethod
- def _has_op(left: Any, other: Any, **kwargs: Any) -> Any:
- return left.has(other, **kwargs)
-
- @staticmethod
- def _of_type_op(a: Any, class_: Any) -> Any:
- return a.of_type(class_)
-
- any_op = cast(operators.OperatorType, _any_op)
- has_op = cast(operators.OperatorType, _has_op)
- of_type_op = cast(operators.OperatorType, _of_type_op)
-
- if typing.TYPE_CHECKING:
-
- def operate(
- self, op: OperatorType, *other: Any, **kwargs: Any
- ) -> ColumnElement[Any]: ...
-
- def reverse_operate(
- self, op: OperatorType, other: Any, **kwargs: Any
- ) -> ColumnElement[Any]: ...
-
- def of_type(self, class_: _EntityType[Any]) -> PropComparator[_T_co]:
- r"""Redefine this object in terms of a polymorphic subclass,
- :func:`_orm.with_polymorphic` construct, or :func:`_orm.aliased`
- construct.
-
- Returns a new PropComparator from which further criterion can be
- evaluated.
-
- e.g.::
-
- query.join(Company.employees.of_type(Engineer)).\
- filter(Engineer.name=='foo')
-
- :param \class_: a class or mapper indicating that criterion will be
- against this specific subclass.
-
- .. seealso::
-
- :ref:`orm_queryguide_joining_relationships_aliased` - in the
- :ref:`queryguide_toplevel`
-
- :ref:`inheritance_of_type`
-
- """
-
- return self.operate(PropComparator.of_type_op, class_) # type: ignore
-
- def and_(
- self, *criteria: _ColumnExpressionArgument[bool]
- ) -> PropComparator[bool]:
- """Add additional criteria to the ON clause that's represented by this
- relationship attribute.
-
- E.g.::
-
-
- stmt = select(User).join(
- User.addresses.and_(Address.email_address != 'foo')
- )
-
- stmt = select(User).options(
- joinedload(User.addresses.and_(Address.email_address != 'foo'))
- )
-
- .. versionadded:: 1.4
-
- .. seealso::
-
- :ref:`orm_queryguide_join_on_augmented`
-
- :ref:`loader_option_criteria`
-
- :func:`.with_loader_criteria`
-
- """
- return self.operate(operators.and_, *criteria) # type: ignore
-
- def any(
- self,
- criterion: Optional[_ColumnExpressionArgument[bool]] = None,
- **kwargs: Any,
- ) -> ColumnElement[bool]:
- r"""Return a SQL expression representing true if this element
- references a member which meets the given criterion.
-
- The usual implementation of ``any()`` is
- :meth:`.Relationship.Comparator.any`.
-
- :param criterion: an optional ClauseElement formulated against the
- member class' table or attributes.
-
- :param \**kwargs: key/value pairs corresponding to member class
- attribute names which will be compared via equality to the
- corresponding values.
-
- """
-
- return self.operate(PropComparator.any_op, criterion, **kwargs)
-
- def has(
- self,
- criterion: Optional[_ColumnExpressionArgument[bool]] = None,
- **kwargs: Any,
- ) -> ColumnElement[bool]:
- r"""Return a SQL expression representing true if this element
- references a member which meets the given criterion.
-
- The usual implementation of ``has()`` is
- :meth:`.Relationship.Comparator.has`.
-
- :param criterion: an optional ClauseElement formulated against the
- member class' table or attributes.
-
- :param \**kwargs: key/value pairs corresponding to member class
- attribute names which will be compared via equality to the
- corresponding values.
-
- """
-
- return self.operate(PropComparator.has_op, criterion, **kwargs)
-
-
-class StrategizedProperty(MapperProperty[_T]):
- """A MapperProperty which uses selectable strategies to affect
- loading behavior.
-
- There is a single strategy selected by default. Alternate
- strategies can be selected at Query time through the usage of
- ``StrategizedOption`` objects via the Query.options() method.
-
- The mechanics of StrategizedProperty are used for every Query
- invocation for every mapped attribute participating in that Query,
- to determine first how the attribute will be rendered in SQL
- and secondly how the attribute will retrieve a value from a result
- row and apply it to a mapped object. The routines here are very
- performance-critical.
-
- """
-
- __slots__ = (
- "_strategies",
- "strategy",
- "_wildcard_token",
- "_default_path_loader_key",
- "strategy_key",
- )
- inherit_cache = True
- strategy_wildcard_key: ClassVar[str]
-
- strategy_key: _StrategyKey
-
- _strategies: Dict[_StrategyKey, LoaderStrategy]
-
- def _memoized_attr__wildcard_token(self) -> Tuple[str]:
- return (
- f"{self.strategy_wildcard_key}:{path_registry._WILDCARD_TOKEN}",
- )
-
- def _memoized_attr__default_path_loader_key(
- self,
- ) -> Tuple[str, Tuple[str]]:
- return (
- "loader",
- (f"{self.strategy_wildcard_key}:{path_registry._DEFAULT_TOKEN}",),
- )
-
- def _get_context_loader(
- self, context: ORMCompileState, path: AbstractEntityRegistry
- ) -> Optional[_LoadElement]:
- load: Optional[_LoadElement] = None
-
- search_path = path[self]
-
- # search among: exact match, "attr.*", "default" strategy
- # if any.
- for path_key in (
- search_path._loader_key,
- search_path._wildcard_path_loader_key,
- search_path._default_path_loader_key,
- ):
- if path_key in context.attributes:
- load = context.attributes[path_key]
- break
-
- # note that if strategy_options.Load is placing non-actionable
- # objects in the context like defaultload(), we would
- # need to continue the loop here if we got such an
- # option as below.
- # if load.strategy or load.local_opts:
- # break
-
- return load
-
- def _get_strategy(self, key: _StrategyKey) -> LoaderStrategy:
- try:
- return self._strategies[key]
- except KeyError:
- pass
-
- # run outside to prevent transfer of exception context
- cls = self._strategy_lookup(self, *key)
- # this previously was setting self._strategies[cls], that's
- # a bad idea; should use strategy key at all times because every
- # strategy has multiple keys at this point
- self._strategies[key] = strategy = cls(self, key)
- return strategy
-
- def setup(
- self,
- context: ORMCompileState,
- query_entity: _MapperEntity,
- path: AbstractEntityRegistry,
- adapter: Optional[ORMAdapter],
- **kwargs: Any,
- ) -> None:
- loader = self._get_context_loader(context, path)
- if loader and loader.strategy:
- strat = self._get_strategy(loader.strategy)
- else:
- strat = self.strategy
- strat.setup_query(
- context, query_entity, path, loader, adapter, **kwargs
- )
-
- def create_row_processor(
- self,
- context: ORMCompileState,
- query_entity: _MapperEntity,
- path: AbstractEntityRegistry,
- mapper: Mapper[Any],
- result: Result[Any],
- adapter: Optional[ORMAdapter],
- populators: _PopulatorDict,
- ) -> None:
- loader = self._get_context_loader(context, path)
- if loader and loader.strategy:
- strat = self._get_strategy(loader.strategy)
- else:
- strat = self.strategy
- strat.create_row_processor(
- context,
- query_entity,
- path,
- loader,
- mapper,
- result,
- adapter,
- populators,
- )
-
- def do_init(self) -> None:
- self._strategies = {}
- self.strategy = self._get_strategy(self.strategy_key)
-
- def post_instrument_class(self, mapper: Mapper[Any]) -> None:
- if (
- not self.parent.non_primary
- and not mapper.class_manager._attr_has_impl(self.key)
- ):
- self.strategy.init_class_attribute(mapper)
-
- _all_strategies: collections.defaultdict[
- Type[MapperProperty[Any]], Dict[_StrategyKey, Type[LoaderStrategy]]
- ] = collections.defaultdict(dict)
-
- @classmethod
- def strategy_for(cls, **kw: Any) -> Callable[[_TLS], _TLS]:
- def decorate(dec_cls: _TLS) -> _TLS:
- # ensure each subclass of the strategy has its
- # own _strategy_keys collection
- if "_strategy_keys" not in dec_cls.__dict__:
- dec_cls._strategy_keys = []
- key = tuple(sorted(kw.items()))
- cls._all_strategies[cls][key] = dec_cls
- dec_cls._strategy_keys.append(key)
- return dec_cls
-
- return decorate
-
- @classmethod
- def _strategy_lookup(
- cls, requesting_property: MapperProperty[Any], *key: Any
- ) -> Type[LoaderStrategy]:
- requesting_property.parent._with_polymorphic_mappers
-
- for prop_cls in cls.__mro__:
- if prop_cls in cls._all_strategies:
- if TYPE_CHECKING:
- assert issubclass(prop_cls, MapperProperty)
- strategies = cls._all_strategies[prop_cls]
- try:
- return strategies[key]
- except KeyError:
- pass
-
- for property_type, strats in cls._all_strategies.items():
- if key in strats:
- intended_property_type = property_type
- actual_strategy = strats[key]
- break
- else:
- intended_property_type = None
- actual_strategy = None
-
- raise orm_exc.LoaderStrategyException(
- cls,
- requesting_property,
- intended_property_type,
- actual_strategy,
- key,
- )
-
-
-class ORMOption(ExecutableOption):
- """Base class for option objects that are passed to ORM queries.
-
- These options may be consumed by :meth:`.Query.options`,
- :meth:`.Select.options`, or in a more general sense by any
- :meth:`.Executable.options` method. They are interpreted at
- statement compile time or execution time in modern use. The
- deprecated :class:`.MapperOption` is consumed at ORM query construction
- time.
-
- .. versionadded:: 1.4
-
- """
-
- __slots__ = ()
-
- _is_legacy_option = False
-
- propagate_to_loaders = False
- """if True, indicate this option should be carried along
- to "secondary" SELECT statements that occur for relationship
- lazy loaders as well as attribute load / refresh operations.
-
- """
-
- _is_core = False
-
- _is_user_defined = False
-
- _is_compile_state = False
-
- _is_criteria_option = False
-
- _is_strategy_option = False
-
- def _adapt_cached_option_to_uncached_option(
- self, context: QueryContext, uncached_opt: ORMOption
- ) -> ORMOption:
- """adapt this option to the "uncached" version of itself in a
- loader strategy context.
-
- given "self" which is an option from a cached query, as well as the
- corresponding option from the uncached version of the same query,
- return the option we should use in a new query, in the context of a
- loader strategy being asked to load related rows on behalf of that
- cached query, which is assumed to be building a new query based on
- entities passed to us from the cached query.
-
- Currently this routine chooses between "self" and "uncached" without
- manufacturing anything new. If the option is itself a loader strategy
- option which has a path, that path needs to match to the entities being
- passed to us by the cached query, so the :class:`_orm.Load` subclass
- overrides this to return "self". For all other options, we return the
- uncached form which may have changing state, such as a
- with_loader_criteria() option which will very often have new state.
-
- This routine could in the future involve
- generating a new option based on both inputs if use cases arise,
- such as if with_loader_criteria() needed to match up to
- ``AliasedClass`` instances given in the parent query.
-
- However, longer term it might be better to restructure things such that
- ``AliasedClass`` entities are always matched up on their cache key,
- instead of identity, in things like paths and such, so that this whole
- issue of "the uncached option does not match the entities" goes away.
- However this would make ``PathRegistry`` more complicated and difficult
- to debug as well as potentially less performant in that it would be
- hashing enormous cache keys rather than a simple AliasedInsp. UNLESS,
- we could get cache keys overall to be reliably hashed into something
- like an md5 key.
-
- .. versionadded:: 1.4.41
-
- """
- if uncached_opt is not None:
- return uncached_opt
- else:
- return self
-
-
-class CompileStateOption(HasCacheKey, ORMOption):
- """base for :class:`.ORMOption` classes that affect the compilation of
- a SQL query and therefore need to be part of the cache key.
-
- .. note:: :class:`.CompileStateOption` is generally non-public and
- should not be used as a base class for user-defined options; instead,
- use :class:`.UserDefinedOption`, which is easier to use as it does not
- interact with ORM compilation internals or caching.
-
- :class:`.CompileStateOption` defines an internal attribute
- ``_is_compile_state=True`` which has the effect of the ORM compilation
- routines for SELECT and other statements will call upon these options when
- a SQL string is being compiled. As such, these classes implement
- :class:`.HasCacheKey` and need to provide robust ``_cache_key_traversal``
- structures.
-
- The :class:`.CompileStateOption` class is used to implement the ORM
- :class:`.LoaderOption` and :class:`.CriteriaOption` classes.
-
- .. versionadded:: 1.4.28
-
-
- """
-
- __slots__ = ()
-
- _is_compile_state = True
-
- def process_compile_state(self, compile_state: ORMCompileState) -> None:
- """Apply a modification to a given :class:`.ORMCompileState`.
-
- This method is part of the implementation of a particular
- :class:`.CompileStateOption` and is only invoked internally
- when an ORM query is compiled.
-
- """
-
- def process_compile_state_replaced_entities(
- self,
- compile_state: ORMCompileState,
- mapper_entities: Sequence[_MapperEntity],
- ) -> None:
- """Apply a modification to a given :class:`.ORMCompileState`,
- given entities that were replaced by with_only_columns() or
- with_entities().
-
- This method is part of the implementation of a particular
- :class:`.CompileStateOption` and is only invoked internally
- when an ORM query is compiled.
-
- .. versionadded:: 1.4.19
-
- """
-
-
-class LoaderOption(CompileStateOption):
- """Describe a loader modification to an ORM statement at compilation time.
-
- .. versionadded:: 1.4
-
- """
-
- __slots__ = ()
-
- def process_compile_state_replaced_entities(
- self,
- compile_state: ORMCompileState,
- mapper_entities: Sequence[_MapperEntity],
- ) -> None:
- self.process_compile_state(compile_state)
-
-
-class CriteriaOption(CompileStateOption):
- """Describe a WHERE criteria modification to an ORM statement at
- compilation time.
-
- .. versionadded:: 1.4
-
- """
-
- __slots__ = ()
-
- _is_criteria_option = True
-
- def get_global_criteria(self, attributes: Dict[str, Any]) -> None:
- """update additional entity criteria options in the given
- attributes dictionary.
-
- """
-
-
-class UserDefinedOption(ORMOption):
- """Base class for a user-defined option that can be consumed from the
- :meth:`.SessionEvents.do_orm_execute` event hook.
-
- """
-
- __slots__ = ("payload",)
-
- _is_legacy_option = False
-
- _is_user_defined = True
-
- propagate_to_loaders = False
- """if True, indicate this option should be carried along
- to "secondary" Query objects produced during lazy loads
- or refresh operations.
-
- """
-
- def __init__(self, payload: Optional[Any] = None):
- self.payload = payload
-
-
-@util.deprecated_cls(
- "1.4",
- "The :class:`.MapperOption class is deprecated and will be removed "
- "in a future release. For "
- "modifications to queries on a per-execution basis, use the "
- ":class:`.UserDefinedOption` class to establish state within a "
- ":class:`.Query` or other Core statement, then use the "
- ":meth:`.SessionEvents.before_orm_execute` hook to consume them.",
- constructor=None,
-)
-class MapperOption(ORMOption):
- """Describe a modification to a Query"""
-
- __slots__ = ()
-
- _is_legacy_option = True
-
- propagate_to_loaders = False
- """if True, indicate this option should be carried along
- to "secondary" Query objects produced during lazy loads
- or refresh operations.
-
- """
-
- def process_query(self, query: Query[Any]) -> None:
- """Apply a modification to the given :class:`_query.Query`."""
-
- def process_query_conditionally(self, query: Query[Any]) -> None:
- """same as process_query(), except that this option may not
- apply to the given query.
-
- This is typically applied during a lazy load or scalar refresh
- operation to propagate options stated in the original Query to the
- new Query being used for the load. It occurs for those options that
- specify propagate_to_loaders=True.
-
- """
-
- self.process_query(query)
-
-
-class LoaderStrategy:
- """Describe the loading behavior of a StrategizedProperty object.
-
- The ``LoaderStrategy`` interacts with the querying process in three
- ways:
-
- * it controls the configuration of the ``InstrumentedAttribute``
- placed on a class to handle the behavior of the attribute. this
- may involve setting up class-level callable functions to fire
- off a select operation when the attribute is first accessed
- (i.e. a lazy load)
-
- * it processes the ``QueryContext`` at statement construction time,
- where it can modify the SQL statement that is being produced.
- For example, simple column attributes will add their represented
- column to the list of selected columns, a joined eager loader
- may establish join clauses to add to the statement.
-
- * It produces "row processor" functions at result fetching time.
- These "row processor" functions populate a particular attribute
- on a particular mapped instance.
-
- """
-
- __slots__ = (
- "parent_property",
- "is_class_level",
- "parent",
- "key",
- "strategy_key",
- "strategy_opts",
- )
-
- _strategy_keys: ClassVar[List[_StrategyKey]]
-
- def __init__(
- self, parent: MapperProperty[Any], strategy_key: _StrategyKey
- ):
- self.parent_property = parent
- self.is_class_level = False
- self.parent = self.parent_property.parent
- self.key = self.parent_property.key
- self.strategy_key = strategy_key
- self.strategy_opts = dict(strategy_key)
-
- def init_class_attribute(self, mapper: Mapper[Any]) -> None:
- pass
-
- def setup_query(
- self,
- compile_state: ORMCompileState,
- query_entity: _MapperEntity,
- path: AbstractEntityRegistry,
- loadopt: Optional[_LoadElement],
- adapter: Optional[ORMAdapter],
- **kwargs: Any,
- ) -> None:
- """Establish column and other state for a given QueryContext.
-
- This method fulfills the contract specified by MapperProperty.setup().
-
- StrategizedProperty delegates its setup() method
- directly to this method.
-
- """
-
- def create_row_processor(
- self,
- context: ORMCompileState,
- query_entity: _MapperEntity,
- path: AbstractEntityRegistry,
- loadopt: Optional[_LoadElement],
- mapper: Mapper[Any],
- result: Result[Any],
- adapter: Optional[ORMAdapter],
- populators: _PopulatorDict,
- ) -> None:
- """Establish row processing functions for a given QueryContext.
-
- This method fulfills the contract specified by
- MapperProperty.create_row_processor().
-
- StrategizedProperty delegates its create_row_processor() method
- directly to this method.
-
- """
-
- def __str__(self) -> str:
- return str(self.parent_property)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/loading.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/loading.py
deleted file mode 100644
index 4e2cb82..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/loading.py
+++ /dev/null
@@ -1,1665 +0,0 @@
-# orm/loading.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
-# mypy: ignore-errors
-
-
-"""private module containing functions used to convert database
-rows into object instances and associated state.
-
-the functions here are called primarily by Query, Mapper,
-as well as some of the attribute loading strategies.
-
-"""
-
-from __future__ import annotations
-
-from typing import Any
-from typing import Dict
-from typing import Iterable
-from typing import List
-from typing import Mapping
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from . import attributes
-from . import exc as orm_exc
-from . import path_registry
-from .base import _DEFER_FOR_STATE
-from .base import _RAISE_FOR_STATE
-from .base import _SET_DEFERRED_EXPIRED
-from .base import PassiveFlag
-from .context import FromStatement
-from .context import ORMCompileState
-from .context import QueryContext
-from .util import _none_set
-from .util import state_str
-from .. import exc as sa_exc
-from .. import util
-from ..engine import result_tuple
-from ..engine.result import ChunkedIteratorResult
-from ..engine.result import FrozenResult
-from ..engine.result import SimpleResultMetaData
-from ..sql import select
-from ..sql import util as sql_util
-from ..sql.selectable import ForUpdateArg
-from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
-from ..sql.selectable import SelectState
-from ..util import EMPTY_DICT
-
-if TYPE_CHECKING:
- from ._typing import _IdentityKeyType
- from .base import LoaderCallableStatus
- from .interfaces import ORMOption
- from .mapper import Mapper
- from .query import Query
- from .session import Session
- from .state import InstanceState
- from ..engine.cursor import CursorResult
- from ..engine.interfaces import _ExecuteOptions
- from ..engine.result import Result
- from ..sql import Select
-
-_T = TypeVar("_T", bound=Any)
-_O = TypeVar("_O", bound=object)
-_new_runid = util.counter()
-
-
-_PopulatorDict = Dict[str, List[Tuple[str, Any]]]
-
-
-def instances(cursor: CursorResult[Any], context: QueryContext) -> Result[Any]:
- """Return a :class:`.Result` given an ORM query context.
-
- :param cursor: a :class:`.CursorResult`, generated by a statement
- which came from :class:`.ORMCompileState`
-
- :param context: a :class:`.QueryContext` object
-
- :return: a :class:`.Result` object representing ORM results
-
- .. versionchanged:: 1.4 The instances() function now uses
- :class:`.Result` objects and has an all new interface.
-
- """
-
- context.runid = _new_runid()
-
- if context.top_level_context:
- is_top_level = False
- context.post_load_paths = context.top_level_context.post_load_paths
- else:
- is_top_level = True
- context.post_load_paths = {}
-
- compile_state = context.compile_state
- filtered = compile_state._has_mapper_entities
- single_entity = (
- not context.load_options._only_return_tuples
- and len(compile_state._entities) == 1
- and compile_state._entities[0].supports_single_entity
- )
-
- try:
- (process, labels, extra) = list(
- zip(
- *[
- query_entity.row_processor(context, cursor)
- for query_entity in context.compile_state._entities
- ]
- )
- )
-
- if context.yield_per and (
- context.loaders_require_buffering
- or context.loaders_require_uniquing
- ):
- raise sa_exc.InvalidRequestError(
- "Can't use yield_per with eager loaders that require uniquing "
- "or row buffering, e.g. joinedload() against collections "
- "or subqueryload(). Consider the selectinload() strategy "
- "for better flexibility in loading objects."
- )
-
- except Exception:
- with util.safe_reraise():
- cursor.close()
-
- def _no_unique(entry):
- raise sa_exc.InvalidRequestError(
- "Can't use the ORM yield_per feature in conjunction with unique()"
- )
-
- def _not_hashable(datatype, *, legacy=False, uncertain=False):
- if not legacy:
-
- def go(obj):
- if uncertain:
- try:
- return hash(obj)
- except:
- pass
-
- raise sa_exc.InvalidRequestError(
- "Can't apply uniqueness to row tuple containing value of "
- f"""type {datatype!r}; {
- 'the values returned appear to be'
- if uncertain
- else 'this datatype produces'
- } non-hashable values"""
- )
-
- return go
- elif not uncertain:
- return id
- else:
- _use_id = False
-
- def go(obj):
- nonlocal _use_id
-
- if not _use_id:
- try:
- return hash(obj)
- except:
- pass
-
- # in #10459, we considered using a warning here, however
- # as legacy query uses result.unique() in all cases, this
- # would lead to too many warning cases.
- _use_id = True
-
- return id(obj)
-
- return go
-
- unique_filters = [
- (
- _no_unique
- if context.yield_per
- else (
- _not_hashable(
- ent.column.type, # type: ignore
- legacy=context.load_options._legacy_uniquing,
- uncertain=ent._null_column_type,
- )
- if (
- not ent.use_id_for_hash
- and (ent._non_hashable_value or ent._null_column_type)
- )
- else id if ent.use_id_for_hash else None
- )
- )
- for ent in context.compile_state._entities
- ]
-
- row_metadata = SimpleResultMetaData(
- labels, extra, _unique_filters=unique_filters
- )
-
- def chunks(size): # type: ignore
- while True:
- yield_per = size
-
- context.partials = {}
-
- if yield_per:
- fetch = cursor.fetchmany(yield_per)
-
- if not fetch:
- break
- else:
- fetch = cursor._raw_all_rows()
-
- if single_entity:
- proc = process[0]
- rows = [proc(row) for row in fetch]
- else:
- rows = [
- tuple([proc(row) for proc in process]) for row in fetch
- ]
-
- # if we are the originating load from a query, meaning we
- # aren't being called as a result of a nested "post load",
- # iterate through all the collected post loaders and fire them
- # off. Previously this used to work recursively, however that
- # prevented deeply nested structures from being loadable
- if is_top_level:
- if yield_per:
- # if using yield per, memoize the state of the
- # collection so that it can be restored
- top_level_post_loads = list(
- context.post_load_paths.items()
- )
-
- while context.post_load_paths:
- post_loads = list(context.post_load_paths.items())
- context.post_load_paths.clear()
- for path, post_load in post_loads:
- post_load.invoke(context, path)
-
- if yield_per:
- context.post_load_paths.clear()
- context.post_load_paths.update(top_level_post_loads)
-
- yield rows
-
- if not yield_per:
- break
-
- if context.execution_options.get("prebuffer_rows", False):
- # this is a bit of a hack at the moment.
- # I would rather have some option in the result to pre-buffer
- # internally.
- _prebuffered = list(chunks(None))
-
- def chunks(size):
- return iter(_prebuffered)
-
- result = ChunkedIteratorResult(
- row_metadata,
- chunks,
- source_supports_scalars=single_entity,
- raw=cursor,
- dynamic_yield_per=cursor.context._is_server_side,
- )
-
- # filtered and single_entity are used to indicate to legacy Query that the
- # query has ORM entities, so legacy deduping and scalars should be called
- # on the result.
- result._attributes = result._attributes.union(
- dict(filtered=filtered, is_single_entity=single_entity)
- )
-
- # multi_row_eager_loaders OTOH is specific to joinedload.
- if context.compile_state.multi_row_eager_loaders:
-
- def require_unique(obj):
- raise sa_exc.InvalidRequestError(
- "The unique() method must be invoked on this Result, "
- "as it contains results that include joined eager loads "
- "against collections"
- )
-
- result._unique_filter_state = (None, require_unique)
-
- if context.yield_per:
- result.yield_per(context.yield_per)
-
- return result
-
-
-@util.preload_module("sqlalchemy.orm.context")
-def merge_frozen_result(session, statement, frozen_result, load=True):
- """Merge a :class:`_engine.FrozenResult` back into a :class:`_orm.Session`,
- returning a new :class:`_engine.Result` object with :term:`persistent`
- objects.
-
- See the section :ref:`do_orm_execute_re_executing` for an example.
-
- .. seealso::
-
- :ref:`do_orm_execute_re_executing`
-
- :meth:`_engine.Result.freeze`
-
- :class:`_engine.FrozenResult`
-
- """
- querycontext = util.preloaded.orm_context
-
- if load:
- # flush current contents if we expect to load data
- session._autoflush()
-
- ctx = querycontext.ORMSelectCompileState._create_entities_collection(
- statement, legacy=False
- )
-
- autoflush = session.autoflush
- try:
- session.autoflush = False
- mapped_entities = [
- i
- for i, e in enumerate(ctx._entities)
- if isinstance(e, querycontext._MapperEntity)
- ]
- keys = [ent._label_name for ent in ctx._entities]
-
- keyed_tuple = result_tuple(
- keys, [ent._extra_entities for ent in ctx._entities]
- )
-
- result = []
- for newrow in frozen_result.rewrite_rows():
- for i in mapped_entities:
- if newrow[i] is not None:
- newrow[i] = session._merge(
- attributes.instance_state(newrow[i]),
- attributes.instance_dict(newrow[i]),
- load=load,
- _recursive={},
- _resolve_conflict_map={},
- )
-
- result.append(keyed_tuple(newrow))
-
- return frozen_result.with_new_rows(result)
- finally:
- session.autoflush = autoflush
-
-
-@util.became_legacy_20(
- ":func:`_orm.merge_result`",
- alternative="The function as well as the method on :class:`_orm.Query` "
- "is superseded by the :func:`_orm.merge_frozen_result` function.",
-)
-@util.preload_module("sqlalchemy.orm.context")
-def merge_result(
- query: Query[Any],
- iterator: Union[FrozenResult, Iterable[Sequence[Any]], Iterable[object]],
- load: bool = True,
-) -> Union[FrozenResult, Iterable[Any]]:
- """Merge a result into the given :class:`.Query` object's Session.
-
- See :meth:`_orm.Query.merge_result` for top-level documentation on this
- function.
-
- """
-
- querycontext = util.preloaded.orm_context
-
- session = query.session
- if load:
- # flush current contents if we expect to load data
- session._autoflush()
-
- # TODO: need test coverage and documentation for the FrozenResult
- # use case.
- if isinstance(iterator, FrozenResult):
- frozen_result = iterator
- iterator = iter(frozen_result.data)
- else:
- frozen_result = None
-
- ctx = querycontext.ORMSelectCompileState._create_entities_collection(
- query, legacy=True
- )
-
- autoflush = session.autoflush
- try:
- session.autoflush = False
- single_entity = not frozen_result and len(ctx._entities) == 1
-
- if single_entity:
- if isinstance(ctx._entities[0], querycontext._MapperEntity):
- result = [
- session._merge(
- attributes.instance_state(instance),
- attributes.instance_dict(instance),
- load=load,
- _recursive={},
- _resolve_conflict_map={},
- )
- for instance in iterator
- ]
- else:
- result = list(iterator)
- else:
- mapped_entities = [
- i
- for i, e in enumerate(ctx._entities)
- if isinstance(e, querycontext._MapperEntity)
- ]
- result = []
- keys = [ent._label_name for ent in ctx._entities]
-
- keyed_tuple = result_tuple(
- keys, [ent._extra_entities for ent in ctx._entities]
- )
-
- for row in iterator:
- newrow = list(row)
- for i in mapped_entities:
- if newrow[i] is not None:
- newrow[i] = session._merge(
- attributes.instance_state(newrow[i]),
- attributes.instance_dict(newrow[i]),
- load=load,
- _recursive={},
- _resolve_conflict_map={},
- )
- result.append(keyed_tuple(newrow))
-
- if frozen_result:
- return frozen_result.with_new_rows(result)
- else:
- return iter(result)
- finally:
- session.autoflush = autoflush
-
-
-def get_from_identity(
- session: Session,
- mapper: Mapper[_O],
- key: _IdentityKeyType[_O],
- passive: PassiveFlag,
-) -> Union[LoaderCallableStatus, Optional[_O]]:
- """Look up the given key in the given session's identity map,
- check the object for expired state if found.
-
- """
- instance = session.identity_map.get(key)
- if instance is not None:
- state = attributes.instance_state(instance)
-
- if mapper.inherits and not state.mapper.isa(mapper):
- return attributes.PASSIVE_CLASS_MISMATCH
-
- # expired - ensure it still exists
- if state.expired:
- if not passive & attributes.SQL_OK:
- # TODO: no coverage here
- return attributes.PASSIVE_NO_RESULT
- elif not passive & attributes.RELATED_OBJECT_OK:
- # this mode is used within a flush and the instance's
- # expired state will be checked soon enough, if necessary.
- # also used by immediateloader for a mutually-dependent
- # o2m->m2m load, :ticket:`6301`
- return instance
- try:
- state._load_expired(state, passive)
- except orm_exc.ObjectDeletedError:
- session._remove_newly_deleted([state])
- return None
- return instance
- else:
- return None
-
-
-def load_on_ident(
- session: Session,
- statement: Union[Select, FromStatement],
- key: Optional[_IdentityKeyType],
- *,
- load_options: Optional[Sequence[ORMOption]] = None,
- refresh_state: Optional[InstanceState[Any]] = None,
- with_for_update: Optional[ForUpdateArg] = None,
- only_load_props: Optional[Iterable[str]] = None,
- no_autoflush: bool = False,
- bind_arguments: Mapping[str, Any] = util.EMPTY_DICT,
- execution_options: _ExecuteOptions = util.EMPTY_DICT,
- require_pk_cols: bool = False,
- is_user_refresh: bool = False,
-):
- """Load the given identity key from the database."""
- if key is not None:
- ident = key[1]
- identity_token = key[2]
- else:
- ident = identity_token = None
-
- return load_on_pk_identity(
- session,
- statement,
- ident,
- load_options=load_options,
- refresh_state=refresh_state,
- with_for_update=with_for_update,
- only_load_props=only_load_props,
- identity_token=identity_token,
- no_autoflush=no_autoflush,
- bind_arguments=bind_arguments,
- execution_options=execution_options,
- require_pk_cols=require_pk_cols,
- is_user_refresh=is_user_refresh,
- )
-
-
-def load_on_pk_identity(
- session: Session,
- statement: Union[Select, FromStatement],
- primary_key_identity: Optional[Tuple[Any, ...]],
- *,
- load_options: Optional[Sequence[ORMOption]] = None,
- refresh_state: Optional[InstanceState[Any]] = None,
- with_for_update: Optional[ForUpdateArg] = None,
- only_load_props: Optional[Iterable[str]] = None,
- identity_token: Optional[Any] = None,
- no_autoflush: bool = False,
- bind_arguments: Mapping[str, Any] = util.EMPTY_DICT,
- execution_options: _ExecuteOptions = util.EMPTY_DICT,
- require_pk_cols: bool = False,
- is_user_refresh: bool = False,
-):
- """Load the given primary key identity from the database."""
-
- query = statement
- q = query._clone()
-
- assert not q._is_lambda_element
-
- if load_options is None:
- load_options = QueryContext.default_load_options
-
- if (
- statement._compile_options
- is SelectState.default_select_compile_options
- ):
- compile_options = ORMCompileState.default_compile_options
- else:
- compile_options = statement._compile_options
-
- if primary_key_identity is not None:
- mapper = query._propagate_attrs["plugin_subject"]
-
- (_get_clause, _get_params) = mapper._get_clause
-
- # None present in ident - turn those comparisons
- # into "IS NULL"
- if None in primary_key_identity:
- nones = {
- _get_params[col].key
- for col, value in zip(mapper.primary_key, primary_key_identity)
- if value is None
- }
-
- _get_clause = sql_util.adapt_criterion_to_null(_get_clause, nones)
-
- if len(nones) == len(primary_key_identity):
- util.warn(
- "fully NULL primary key identity cannot load any "
- "object. This condition may raise an error in a future "
- "release."
- )
-
- q._where_criteria = (
- sql_util._deep_annotate(_get_clause, {"_orm_adapt": True}),
- )
-
- params = {
- _get_params[primary_key].key: id_val
- for id_val, primary_key in zip(
- primary_key_identity, mapper.primary_key
- )
- }
- else:
- params = None
-
- if with_for_update is not None:
- version_check = True
- q._for_update_arg = with_for_update
- elif query._for_update_arg is not None:
- version_check = True
- q._for_update_arg = query._for_update_arg
- else:
- version_check = False
-
- if require_pk_cols and only_load_props:
- if not refresh_state:
- raise sa_exc.ArgumentError(
- "refresh_state is required when require_pk_cols is present"
- )
-
- refresh_state_prokeys = refresh_state.mapper._primary_key_propkeys
- has_changes = {
- key
- for key in refresh_state_prokeys.difference(only_load_props)
- if refresh_state.attrs[key].history.has_changes()
- }
- if has_changes:
- # raise if pending pk changes are present.
- # technically, this could be limited to the case where we have
- # relationships in the only_load_props collection to be refreshed
- # also (and only ones that have a secondary eager loader, at that).
- # however, the error is in place across the board so that behavior
- # here is easier to predict. The use case it prevents is one
- # of mutating PK attrs, leaving them unflushed,
- # calling session.refresh(), and expecting those attrs to remain
- # still unflushed. It seems likely someone doing all those
- # things would be better off having the PK attributes flushed
- # to the database before tinkering like that (session.refresh() is
- # tinkering).
- raise sa_exc.InvalidRequestError(
- f"Please flush pending primary key changes on "
- "attributes "
- f"{has_changes} for mapper {refresh_state.mapper} before "
- "proceeding with a refresh"
- )
-
- # overall, the ORM has no internal flow right now for "dont load the
- # primary row of an object at all, but fire off
- # selectinload/subqueryload/immediateload for some relationships".
- # It would probably be a pretty big effort to add such a flow. So
- # here, the case for #8703 is introduced; user asks to refresh some
- # relationship attributes only which are
- # selectinload/subqueryload/immediateload/ etc. (not joinedload).
- # ORM complains there's no columns in the primary row to load.
- # So here, we just add the PK cols if that
- # case is detected, so that there is a SELECT emitted for the primary
- # row.
- #
- # Let's just state right up front, for this one little case,
- # the ORM here is adding a whole extra SELECT just to satisfy
- # limitations in the internal flow. This is really not a thing
- # SQLAlchemy finds itself doing like, ever, obviously, we are
- # constantly working to *remove* SELECTs we don't need. We
- # rationalize this for now based on 1. session.refresh() is not
- # commonly used 2. session.refresh() with only relationship attrs is
- # even less commonly used 3. the SELECT in question is very low
- # latency.
- #
- # to add the flow to not include the SELECT, the quickest way
- # might be to just manufacture a single-row result set to send off to
- # instances(), but we'd have to weave that into context.py and all
- # that. For 2.0.0, we have enough big changes to navigate for now.
- #
- mp = refresh_state.mapper._props
- for p in only_load_props:
- if mp[p]._is_relationship:
- only_load_props = refresh_state_prokeys.union(only_load_props)
- break
-
- if refresh_state and refresh_state.load_options:
- compile_options += {"_current_path": refresh_state.load_path.parent}
- q = q.options(*refresh_state.load_options)
-
- new_compile_options, load_options = _set_get_options(
- compile_options,
- load_options,
- version_check=version_check,
- only_load_props=only_load_props,
- refresh_state=refresh_state,
- identity_token=identity_token,
- is_user_refresh=is_user_refresh,
- )
-
- q._compile_options = new_compile_options
- q._order_by = None
-
- if no_autoflush:
- load_options += {"_autoflush": False}
-
- execution_options = util.EMPTY_DICT.merge_with(
- execution_options, {"_sa_orm_load_options": load_options}
- )
- result = (
- session.execute(
- q,
- params=params,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- )
- .unique()
- .scalars()
- )
-
- try:
- return result.one()
- except orm_exc.NoResultFound:
- return None
-
-
-def _set_get_options(
- compile_opt,
- load_opt,
- populate_existing=None,
- version_check=None,
- only_load_props=None,
- refresh_state=None,
- identity_token=None,
- is_user_refresh=None,
-):
- compile_options = {}
- load_options = {}
- if version_check:
- load_options["_version_check"] = version_check
- if populate_existing:
- load_options["_populate_existing"] = populate_existing
- if refresh_state:
- load_options["_refresh_state"] = refresh_state
- compile_options["_for_refresh_state"] = True
- if only_load_props:
- compile_options["_only_load_props"] = frozenset(only_load_props)
- if identity_token:
- load_options["_identity_token"] = identity_token
-
- if is_user_refresh:
- load_options["_is_user_refresh"] = is_user_refresh
- if load_options:
- load_opt += load_options
- if compile_options:
- compile_opt += compile_options
-
- return compile_opt, load_opt
-
-
-def _setup_entity_query(
- compile_state,
- mapper,
- query_entity,
- path,
- adapter,
- column_collection,
- with_polymorphic=None,
- only_load_props=None,
- polymorphic_discriminator=None,
- **kw,
-):
- if with_polymorphic:
- poly_properties = mapper._iterate_polymorphic_properties(
- with_polymorphic
- )
- else:
- poly_properties = mapper._polymorphic_properties
-
- quick_populators = {}
-
- path.set(compile_state.attributes, "memoized_setups", quick_populators)
-
- # for the lead entities in the path, e.g. not eager loads, and
- # assuming a user-passed aliased class, e.g. not a from_self() or any
- # implicit aliasing, don't add columns to the SELECT that aren't
- # in the thing that's aliased.
- check_for_adapt = adapter and len(path) == 1 and path[-1].is_aliased_class
-
- for value in poly_properties:
- if only_load_props and value.key not in only_load_props:
- continue
- value.setup(
- compile_state,
- query_entity,
- path,
- adapter,
- only_load_props=only_load_props,
- column_collection=column_collection,
- memoized_populators=quick_populators,
- check_for_adapt=check_for_adapt,
- **kw,
- )
-
- if (
- polymorphic_discriminator is not None
- and polymorphic_discriminator is not mapper.polymorphic_on
- ):
- if adapter:
- pd = adapter.columns[polymorphic_discriminator]
- else:
- pd = polymorphic_discriminator
- column_collection.append(pd)
-
-
-def _warn_for_runid_changed(state):
- util.warn(
- "Loading context for %s has changed within a load/refresh "
- "handler, suggesting a row refresh operation took place. If this "
- "event handler is expected to be "
- "emitting row refresh operations within an existing load or refresh "
- "operation, set restore_load_context=True when establishing the "
- "listener to ensure the context remains unchanged when the event "
- "handler completes." % (state_str(state),)
- )
-
-
-def _instance_processor(
- query_entity,
- mapper,
- context,
- result,
- path,
- adapter,
- only_load_props=None,
- refresh_state=None,
- polymorphic_discriminator=None,
- _polymorphic_from=None,
-):
- """Produce a mapper level row processor callable
- which processes rows into mapped instances."""
-
- # note that this method, most of which exists in a closure
- # called _instance(), resists being broken out, as
- # attempts to do so tend to add significant function
- # call overhead. _instance() is the most
- # performance-critical section in the whole ORM.
-
- identity_class = mapper._identity_class
- compile_state = context.compile_state
-
- # look for "row getter" functions that have been assigned along
- # with the compile state that were cached from a previous load.
- # these are operator.itemgetter() objects that each will extract a
- # particular column from each row.
-
- getter_key = ("getters", mapper)
- getters = path.get(compile_state.attributes, getter_key, None)
-
- if getters is None:
- # no getters, so go through a list of attributes we are loading for,
- # and the ones that are column based will have already put information
- # for us in another collection "memoized_setups", which represents the
- # output of the LoaderStrategy.setup_query() method. We can just as
- # easily call LoaderStrategy.create_row_processor for each, but by
- # getting it all at once from setup_query we save another method call
- # per attribute.
- props = mapper._prop_set
- if only_load_props is not None:
- props = props.intersection(
- mapper._props[k] for k in only_load_props
- )
-
- quick_populators = path.get(
- context.attributes, "memoized_setups", EMPTY_DICT
- )
-
- todo = []
- cached_populators = {
- "new": [],
- "quick": [],
- "deferred": [],
- "expire": [],
- "existing": [],
- "eager": [],
- }
-
- if refresh_state is None:
- # we can also get the "primary key" tuple getter function
- pk_cols = mapper.primary_key
-
- if adapter:
- pk_cols = [adapter.columns[c] for c in pk_cols]
- primary_key_getter = result._tuple_getter(pk_cols)
- else:
- primary_key_getter = None
-
- getters = {
- "cached_populators": cached_populators,
- "todo": todo,
- "primary_key_getter": primary_key_getter,
- }
- for prop in props:
- if prop in quick_populators:
- # this is an inlined path just for column-based attributes.
- col = quick_populators[prop]
- if col is _DEFER_FOR_STATE:
- cached_populators["new"].append(
- (prop.key, prop._deferred_column_loader)
- )
- elif col is _SET_DEFERRED_EXPIRED:
- # note that in this path, we are no longer
- # searching in the result to see if the column might
- # be present in some unexpected way.
- cached_populators["expire"].append((prop.key, False))
- elif col is _RAISE_FOR_STATE:
- cached_populators["new"].append(
- (prop.key, prop._raise_column_loader)
- )
- else:
- getter = None
- if adapter:
- # this logic had been removed for all 1.4 releases
- # up until 1.4.18; the adapter here is particularly
- # the compound eager adapter which isn't accommodated
- # in the quick_populators right now. The "fallback"
- # logic below instead took over in many more cases
- # until issue #6596 was identified.
-
- # note there is still an issue where this codepath
- # produces no "getter" for cases where a joined-inh
- # mapping includes a labeled column property, meaning
- # KeyError is caught internally and we fall back to
- # _getter(col), which works anyway. The adapter
- # here for joined inh without any aliasing might not
- # be useful. Tests which see this include
- # test.orm.inheritance.test_basic ->
- # EagerTargetingTest.test_adapt_stringency
- # OptimizedLoadTest.test_column_expression_joined
- # PolymorphicOnNotLocalTest.test_polymorphic_on_column_prop # noqa: E501
- #
-
- adapted_col = adapter.columns[col]
- if adapted_col is not None:
- getter = result._getter(adapted_col, False)
- if not getter:
- getter = result._getter(col, False)
- if getter:
- cached_populators["quick"].append((prop.key, getter))
- else:
- # fall back to the ColumnProperty itself, which
- # will iterate through all of its columns
- # to see if one fits
- prop.create_row_processor(
- context,
- query_entity,
- path,
- mapper,
- result,
- adapter,
- cached_populators,
- )
- else:
- # loader strategies like subqueryload, selectinload,
- # joinedload, basically relationships, these need to interact
- # with the context each time to work correctly.
- todo.append(prop)
-
- path.set(compile_state.attributes, getter_key, getters)
-
- cached_populators = getters["cached_populators"]
-
- populators = {key: list(value) for key, value in cached_populators.items()}
- for prop in getters["todo"]:
- prop.create_row_processor(
- context, query_entity, path, mapper, result, adapter, populators
- )
-
- propagated_loader_options = context.propagated_loader_options
- load_path = (
- context.compile_state.current_path + path
- if context.compile_state.current_path.path
- else path
- )
-
- session_identity_map = context.session.identity_map
-
- populate_existing = context.populate_existing or mapper.always_refresh
- load_evt = bool(mapper.class_manager.dispatch.load)
- refresh_evt = bool(mapper.class_manager.dispatch.refresh)
- persistent_evt = bool(context.session.dispatch.loaded_as_persistent)
- if persistent_evt:
- loaded_as_persistent = context.session.dispatch.loaded_as_persistent
- instance_state = attributes.instance_state
- instance_dict = attributes.instance_dict
- session_id = context.session.hash_key
- runid = context.runid
- identity_token = context.identity_token
-
- version_check = context.version_check
- if version_check:
- version_id_col = mapper.version_id_col
- if version_id_col is not None:
- if adapter:
- version_id_col = adapter.columns[version_id_col]
- version_id_getter = result._getter(version_id_col)
- else:
- version_id_getter = None
-
- if not refresh_state and _polymorphic_from is not None:
- key = ("loader", path.path)
-
- if key in context.attributes and context.attributes[key].strategy == (
- ("selectinload_polymorphic", True),
- ):
- option_entities = context.attributes[key].local_opts["entities"]
- else:
- option_entities = None
- selectin_load_via = mapper._should_selectin_load(
- option_entities,
- _polymorphic_from,
- )
-
- if selectin_load_via and selectin_load_via is not _polymorphic_from:
- # only_load_props goes w/ refresh_state only, and in a refresh
- # we are a single row query for the exact entity; polymorphic
- # loading does not apply
- assert only_load_props is None
-
- callable_ = _load_subclass_via_in(
- context,
- path,
- selectin_load_via,
- _polymorphic_from,
- option_entities,
- )
- PostLoad.callable_for_path(
- context,
- load_path,
- selectin_load_via.mapper,
- selectin_load_via,
- callable_,
- selectin_load_via,
- )
-
- post_load = PostLoad.for_context(context, load_path, only_load_props)
-
- if refresh_state:
- refresh_identity_key = refresh_state.key
- if refresh_identity_key is None:
- # super-rare condition; a refresh is being called
- # on a non-instance-key instance; this is meant to only
- # occur within a flush()
- refresh_identity_key = mapper._identity_key_from_state(
- refresh_state
- )
- else:
- refresh_identity_key = None
-
- primary_key_getter = getters["primary_key_getter"]
-
- if mapper.allow_partial_pks:
- is_not_primary_key = _none_set.issuperset
- else:
- is_not_primary_key = _none_set.intersection
-
- def _instance(row):
- # determine the state that we'll be populating
- if refresh_identity_key:
- # fixed state that we're refreshing
- state = refresh_state
- instance = state.obj()
- dict_ = instance_dict(instance)
- isnew = state.runid != runid
- currentload = True
- loaded_instance = False
- else:
- # look at the row, see if that identity is in the
- # session, or we have to create a new one
- identitykey = (
- identity_class,
- primary_key_getter(row),
- identity_token,
- )
-
- instance = session_identity_map.get(identitykey)
-
- if instance is not None:
- # existing instance
- state = instance_state(instance)
- dict_ = instance_dict(instance)
-
- isnew = state.runid != runid
- currentload = not isnew
- loaded_instance = False
-
- if version_check and version_id_getter and not currentload:
- _validate_version_id(
- mapper, state, dict_, row, version_id_getter
- )
-
- else:
- # create a new instance
-
- # check for non-NULL values in the primary key columns,
- # else no entity is returned for the row
- if is_not_primary_key(identitykey[1]):
- return None
-
- isnew = True
- currentload = True
- loaded_instance = True
-
- instance = mapper.class_manager.new_instance()
-
- dict_ = instance_dict(instance)
- state = instance_state(instance)
- state.key = identitykey
- state.identity_token = identity_token
-
- # attach instance to session.
- state.session_id = session_id
- session_identity_map._add_unpresent(state, identitykey)
-
- effective_populate_existing = populate_existing
- if refresh_state is state:
- effective_populate_existing = True
-
- # populate. this looks at whether this state is new
- # for this load or was existing, and whether or not this
- # row is the first row with this identity.
- if currentload or effective_populate_existing:
- # full population routines. Objects here are either
- # just created, or we are doing a populate_existing
-
- # be conservative about setting load_path when populate_existing
- # is in effect; want to maintain options from the original
- # load. see test_expire->test_refresh_maintains_deferred_options
- if isnew and (
- propagated_loader_options or not effective_populate_existing
- ):
- state.load_options = propagated_loader_options
- state.load_path = load_path
-
- _populate_full(
- context,
- row,
- state,
- dict_,
- isnew,
- load_path,
- loaded_instance,
- effective_populate_existing,
- populators,
- )
-
- if isnew:
- # state.runid should be equal to context.runid / runid
- # here, however for event checks we are being more conservative
- # and checking against existing run id
- # assert state.runid == runid
-
- existing_runid = state.runid
-
- if loaded_instance:
- if load_evt:
- state.manager.dispatch.load(state, context)
- if state.runid != existing_runid:
- _warn_for_runid_changed(state)
- if persistent_evt:
- loaded_as_persistent(context.session, state)
- if state.runid != existing_runid:
- _warn_for_runid_changed(state)
- elif refresh_evt:
- state.manager.dispatch.refresh(
- state, context, only_load_props
- )
- if state.runid != runid:
- _warn_for_runid_changed(state)
-
- if effective_populate_existing or state.modified:
- if refresh_state and only_load_props:
- state._commit(dict_, only_load_props)
- else:
- state._commit_all(dict_, session_identity_map)
-
- if post_load:
- post_load.add_state(state, True)
-
- else:
- # partial population routines, for objects that were already
- # in the Session, but a row matches them; apply eager loaders
- # on existing objects, etc.
- unloaded = state.unloaded
- isnew = state not in context.partials
-
- if not isnew or unloaded or populators["eager"]:
- # state is having a partial set of its attributes
- # refreshed. Populate those attributes,
- # and add to the "context.partials" collection.
-
- to_load = _populate_partial(
- context,
- row,
- state,
- dict_,
- isnew,
- load_path,
- unloaded,
- populators,
- )
-
- if isnew:
- if refresh_evt:
- existing_runid = state.runid
- state.manager.dispatch.refresh(state, context, to_load)
- if state.runid != existing_runid:
- _warn_for_runid_changed(state)
-
- state._commit(dict_, to_load)
-
- if post_load and context.invoke_all_eagers:
- post_load.add_state(state, False)
-
- return instance
-
- if mapper.polymorphic_map and not _polymorphic_from and not refresh_state:
- # if we are doing polymorphic, dispatch to a different _instance()
- # method specific to the subclass mapper
- def ensure_no_pk(row):
- identitykey = (
- identity_class,
- primary_key_getter(row),
- identity_token,
- )
- if not is_not_primary_key(identitykey[1]):
- return identitykey
- else:
- return None
-
- _instance = _decorate_polymorphic_switch(
- _instance,
- context,
- query_entity,
- mapper,
- result,
- path,
- polymorphic_discriminator,
- adapter,
- ensure_no_pk,
- )
-
- return _instance
-
-
-def _load_subclass_via_in(
- context, path, entity, polymorphic_from, option_entities
-):
- mapper = entity.mapper
-
- # TODO: polymorphic_from seems to be a Mapper in all cases.
- # this is likely not needed, but as we dont have typing in loading.py
- # yet, err on the safe side
- polymorphic_from_mapper = polymorphic_from.mapper
- not_against_basemost = polymorphic_from_mapper.inherits is not None
-
- zero_idx = len(mapper.base_mapper.primary_key) == 1
-
- if entity.is_aliased_class or not_against_basemost:
- q, enable_opt, disable_opt = mapper._subclass_load_via_in(
- entity, polymorphic_from
- )
- else:
- q, enable_opt, disable_opt = mapper._subclass_load_via_in_mapper
-
- def do_load(context, path, states, load_only, effective_entity):
- if not option_entities:
- # filter out states for those that would have selectinloaded
- # from another loader
- # TODO: we are currently ignoring the case where the
- # "selectin_polymorphic" option is used, as this is much more
- # complex / specific / very uncommon API use
- states = [
- (s, v)
- for s, v in states
- if s.mapper._would_selectin_load_only_from_given_mapper(mapper)
- ]
-
- if not states:
- return
-
- orig_query = context.query
-
- if path.parent:
- enable_opt_lcl = enable_opt._prepend_path(path)
- disable_opt_lcl = disable_opt._prepend_path(path)
- else:
- enable_opt_lcl = enable_opt
- disable_opt_lcl = disable_opt
- options = (
- (enable_opt_lcl,) + orig_query._with_options + (disable_opt_lcl,)
- )
-
- q2 = q.options(*options)
-
- q2._compile_options = context.compile_state.default_compile_options
- q2._compile_options += {"_current_path": path.parent}
-
- if context.populate_existing:
- q2 = q2.execution_options(populate_existing=True)
-
- context.session.execute(
- q2,
- dict(
- primary_keys=[
- state.key[1][0] if zero_idx else state.key[1]
- for state, load_attrs in states
- ]
- ),
- ).unique().scalars().all()
-
- return do_load
-
-
-def _populate_full(
- context,
- row,
- state,
- dict_,
- isnew,
- load_path,
- loaded_instance,
- populate_existing,
- populators,
-):
- if isnew:
- # first time we are seeing a row with this identity.
- state.runid = context.runid
-
- for key, getter in populators["quick"]:
- dict_[key] = getter(row)
- if populate_existing:
- for key, set_callable in populators["expire"]:
- dict_.pop(key, None)
- if set_callable:
- state.expired_attributes.add(key)
- else:
- for key, set_callable in populators["expire"]:
- if set_callable:
- state.expired_attributes.add(key)
-
- for key, populator in populators["new"]:
- populator(state, dict_, row)
-
- elif load_path != state.load_path:
- # new load path, e.g. object is present in more than one
- # column position in a series of rows
- state.load_path = load_path
-
- # if we have data, and the data isn't in the dict, OK, let's put
- # it in.
- for key, getter in populators["quick"]:
- if key not in dict_:
- dict_[key] = getter(row)
-
- # otherwise treat like an "already seen" row
- for key, populator in populators["existing"]:
- populator(state, dict_, row)
- # TODO: allow "existing" populator to know this is
- # a new path for the state:
- # populator(state, dict_, row, new_path=True)
-
- else:
- # have already seen rows with this identity in this same path.
- for key, populator in populators["existing"]:
- populator(state, dict_, row)
-
- # TODO: same path
- # populator(state, dict_, row, new_path=False)
-
-
-def _populate_partial(
- context, row, state, dict_, isnew, load_path, unloaded, populators
-):
- if not isnew:
- if unloaded:
- # extra pass, see #8166
- for key, getter in populators["quick"]:
- if key in unloaded:
- dict_[key] = getter(row)
-
- to_load = context.partials[state]
- for key, populator in populators["existing"]:
- if key in to_load:
- populator(state, dict_, row)
- else:
- to_load = unloaded
- context.partials[state] = to_load
-
- for key, getter in populators["quick"]:
- if key in to_load:
- dict_[key] = getter(row)
- for key, set_callable in populators["expire"]:
- if key in to_load:
- dict_.pop(key, None)
- if set_callable:
- state.expired_attributes.add(key)
- for key, populator in populators["new"]:
- if key in to_load:
- populator(state, dict_, row)
-
- for key, populator in populators["eager"]:
- if key not in unloaded:
- populator(state, dict_, row)
-
- return to_load
-
-
-def _validate_version_id(mapper, state, dict_, row, getter):
- if mapper._get_state_attr_by_column(
- state, dict_, mapper.version_id_col
- ) != getter(row):
- raise orm_exc.StaleDataError(
- "Instance '%s' has version id '%s' which "
- "does not match database-loaded version id '%s'."
- % (
- state_str(state),
- mapper._get_state_attr_by_column(
- state, dict_, mapper.version_id_col
- ),
- getter(row),
- )
- )
-
-
-def _decorate_polymorphic_switch(
- instance_fn,
- context,
- query_entity,
- mapper,
- result,
- path,
- polymorphic_discriminator,
- adapter,
- ensure_no_pk,
-):
- if polymorphic_discriminator is not None:
- polymorphic_on = polymorphic_discriminator
- else:
- polymorphic_on = mapper.polymorphic_on
- if polymorphic_on is None:
- return instance_fn
-
- if adapter:
- polymorphic_on = adapter.columns[polymorphic_on]
-
- def configure_subclass_mapper(discriminator):
- try:
- sub_mapper = mapper.polymorphic_map[discriminator]
- except KeyError:
- raise AssertionError(
- "No such polymorphic_identity %r is defined" % discriminator
- )
- else:
- if sub_mapper is mapper:
- return None
- elif not sub_mapper.isa(mapper):
- return False
-
- return _instance_processor(
- query_entity,
- sub_mapper,
- context,
- result,
- path,
- adapter,
- _polymorphic_from=mapper,
- )
-
- polymorphic_instances = util.PopulateDict(configure_subclass_mapper)
-
- getter = result._getter(polymorphic_on)
-
- def polymorphic_instance(row):
- discriminator = getter(row)
- if discriminator is not None:
- _instance = polymorphic_instances[discriminator]
- if _instance:
- return _instance(row)
- elif _instance is False:
- identitykey = ensure_no_pk(row)
-
- if identitykey:
- raise sa_exc.InvalidRequestError(
- "Row with identity key %s can't be loaded into an "
- "object; the polymorphic discriminator column '%s' "
- "refers to %s, which is not a sub-mapper of "
- "the requested %s"
- % (
- identitykey,
- polymorphic_on,
- mapper.polymorphic_map[discriminator],
- mapper,
- )
- )
- else:
- return None
- else:
- return instance_fn(row)
- else:
- identitykey = ensure_no_pk(row)
-
- if identitykey:
- raise sa_exc.InvalidRequestError(
- "Row with identity key %s can't be loaded into an "
- "object; the polymorphic discriminator column '%s' is "
- "NULL" % (identitykey, polymorphic_on)
- )
- else:
- return None
-
- return polymorphic_instance
-
-
-class PostLoad:
- """Track loaders and states for "post load" operations."""
-
- __slots__ = "loaders", "states", "load_keys"
-
- def __init__(self):
- self.loaders = {}
- self.states = util.OrderedDict()
- self.load_keys = None
-
- def add_state(self, state, overwrite):
- # the states for a polymorphic load here are all shared
- # within a single PostLoad object among multiple subtypes.
- # Filtering of callables on a per-subclass basis needs to be done at
- # the invocation level
- self.states[state] = overwrite
-
- def invoke(self, context, path):
- if not self.states:
- return
- path = path_registry.PathRegistry.coerce(path)
- for (
- effective_context,
- token,
- limit_to_mapper,
- loader,
- arg,
- kw,
- ) in self.loaders.values():
- states = [
- (state, overwrite)
- for state, overwrite in self.states.items()
- if state.manager.mapper.isa(limit_to_mapper)
- ]
- if states:
- loader(
- effective_context, path, states, self.load_keys, *arg, **kw
- )
- self.states.clear()
-
- @classmethod
- def for_context(cls, context, path, only_load_props):
- pl = context.post_load_paths.get(path.path)
- if pl is not None and only_load_props:
- pl.load_keys = only_load_props
- return pl
-
- @classmethod
- def path_exists(self, context, path, key):
- return (
- path.path in context.post_load_paths
- and key in context.post_load_paths[path.path].loaders
- )
-
- @classmethod
- def callable_for_path(
- cls, context, path, limit_to_mapper, token, loader_callable, *arg, **kw
- ):
- if path.path in context.post_load_paths:
- pl = context.post_load_paths[path.path]
- else:
- pl = context.post_load_paths[path.path] = PostLoad()
- pl.loaders[token] = (
- context,
- token,
- limit_to_mapper,
- loader_callable,
- arg,
- kw,
- )
-
-
-def load_scalar_attributes(mapper, state, attribute_names, passive):
- """initiate a column-based attribute refresh operation."""
-
- # assert mapper is _state_mapper(state)
- session = state.session
- if not session:
- raise orm_exc.DetachedInstanceError(
- "Instance %s is not bound to a Session; "
- "attribute refresh operation cannot proceed" % (state_str(state))
- )
-
- no_autoflush = bool(passive & attributes.NO_AUTOFLUSH)
-
- # in the case of inheritance, particularly concrete and abstract
- # concrete inheritance, the class manager might have some keys
- # of attributes on the superclass that we didn't actually map.
- # These could be mapped as "concrete, don't load" or could be completely
- # excluded from the mapping and we know nothing about them. Filter them
- # here to prevent them from coming through.
- if attribute_names:
- attribute_names = attribute_names.intersection(mapper.attrs.keys())
-
- if mapper.inherits and not mapper.concrete:
- # load based on committed attributes in the object, formed into
- # a truncated SELECT that only includes relevant tables. does not
- # currently use state.key
- statement = mapper._optimized_get_statement(state, attribute_names)
- if statement is not None:
- # undefer() isn't needed here because statement has the
- # columns needed already, this implicitly undefers that column
- stmt = FromStatement(mapper, statement)
-
- return load_on_ident(
- session,
- stmt,
- None,
- only_load_props=attribute_names,
- refresh_state=state,
- no_autoflush=no_autoflush,
- )
-
- # normal load, use state.key as the identity to SELECT
- has_key = bool(state.key)
-
- if has_key:
- identity_key = state.key
- else:
- # this codepath is rare - only valid when inside a flush, and the
- # object is becoming persistent but hasn't yet been assigned
- # an identity_key.
- # check here to ensure we have the attrs we need.
- pk_attrs = [
- mapper._columntoproperty[col].key for col in mapper.primary_key
- ]
- if state.expired_attributes.intersection(pk_attrs):
- raise sa_exc.InvalidRequestError(
- "Instance %s cannot be refreshed - it's not "
- " persistent and does not "
- "contain a full primary key." % state_str(state)
- )
- identity_key = mapper._identity_key_from_state(state)
-
- if (
- _none_set.issubset(identity_key) and not mapper.allow_partial_pks
- ) or _none_set.issuperset(identity_key):
- util.warn_limited(
- "Instance %s to be refreshed doesn't "
- "contain a full primary key - can't be refreshed "
- "(and shouldn't be expired, either).",
- state_str(state),
- )
- return
-
- result = load_on_ident(
- session,
- select(mapper).set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL),
- identity_key,
- refresh_state=state,
- only_load_props=attribute_names,
- no_autoflush=no_autoflush,
- )
-
- # if instance is pending, a refresh operation
- # may not complete (even if PK attributes are assigned)
- if has_key and result is None:
- raise orm_exc.ObjectDeletedError(state)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/mapped_collection.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/mapped_collection.py
deleted file mode 100644
index 13c6b68..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/mapped_collection.py
+++ /dev/null
@@ -1,560 +0,0 @@
-# orm/mapped_collection.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
-
-from __future__ import annotations
-
-import operator
-from typing import Any
-from typing import Callable
-from typing import Dict
-from typing import Generic
-from typing import List
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from . import base
-from .collections import collection
-from .collections import collection_adapter
-from .. import exc as sa_exc
-from .. import util
-from ..sql import coercions
-from ..sql import expression
-from ..sql import roles
-from ..util.typing import Literal
-
-if TYPE_CHECKING:
- from . import AttributeEventToken
- from . import Mapper
- from .collections import CollectionAdapter
- from ..sql.elements import ColumnElement
-
-_KT = TypeVar("_KT", bound=Any)
-_VT = TypeVar("_VT", bound=Any)
-
-_F = TypeVar("_F", bound=Callable[[Any], Any])
-
-
-class _PlainColumnGetter(Generic[_KT]):
- """Plain column getter, stores collection of Column objects
- directly.
-
- Serializes to a :class:`._SerializableColumnGetterV2`
- which has more expensive __call__() performance
- and some rare caveats.
-
- """
-
- __slots__ = ("cols", "composite")
-
- def __init__(self, cols: Sequence[ColumnElement[_KT]]) -> None:
- self.cols = cols
- self.composite = len(cols) > 1
-
- def __reduce__(
- self,
- ) -> Tuple[
- Type[_SerializableColumnGetterV2[_KT]],
- Tuple[Sequence[Tuple[Optional[str], Optional[str]]]],
- ]:
- return _SerializableColumnGetterV2._reduce_from_cols(self.cols)
-
- def _cols(self, mapper: Mapper[_KT]) -> Sequence[ColumnElement[_KT]]:
- return self.cols
-
- def __call__(self, value: _KT) -> Union[_KT, Tuple[_KT, ...]]:
- state = base.instance_state(value)
- m = base._state_mapper(state)
-
- key: List[_KT] = [
- m._get_state_attr_by_column(state, state.dict, col)
- for col in self._cols(m)
- ]
- if self.composite:
- return tuple(key)
- else:
- obj = key[0]
- if obj is None:
- return _UNMAPPED_AMBIGUOUS_NONE
- else:
- return obj
-
-
-class _SerializableColumnGetterV2(_PlainColumnGetter[_KT]):
- """Updated serializable getter which deals with
- multi-table mapped classes.
-
- Two extremely unusual cases are not supported.
- Mappings which have tables across multiple metadata
- objects, or which are mapped to non-Table selectables
- linked across inheriting mappers may fail to function
- here.
-
- """
-
- __slots__ = ("colkeys",)
-
- def __init__(
- self, colkeys: Sequence[Tuple[Optional[str], Optional[str]]]
- ) -> None:
- self.colkeys = colkeys
- self.composite = len(colkeys) > 1
-
- def __reduce__(
- self,
- ) -> Tuple[
- Type[_SerializableColumnGetterV2[_KT]],
- Tuple[Sequence[Tuple[Optional[str], Optional[str]]]],
- ]:
- return self.__class__, (self.colkeys,)
-
- @classmethod
- def _reduce_from_cols(cls, cols: Sequence[ColumnElement[_KT]]) -> Tuple[
- Type[_SerializableColumnGetterV2[_KT]],
- Tuple[Sequence[Tuple[Optional[str], Optional[str]]]],
- ]:
- def _table_key(c: ColumnElement[_KT]) -> Optional[str]:
- if not isinstance(c.table, expression.TableClause):
- return None
- else:
- return c.table.key # type: ignore
-
- colkeys = [(c.key, _table_key(c)) for c in cols]
- return _SerializableColumnGetterV2, (colkeys,)
-
- def _cols(self, mapper: Mapper[_KT]) -> Sequence[ColumnElement[_KT]]:
- cols: List[ColumnElement[_KT]] = []
- metadata = getattr(mapper.local_table, "metadata", None)
- for ckey, tkey in self.colkeys:
- if tkey is None or metadata is None or tkey not in metadata:
- cols.append(mapper.local_table.c[ckey]) # type: ignore
- else:
- cols.append(metadata.tables[tkey].c[ckey])
- return cols
-
-
-def column_keyed_dict(
- mapping_spec: Union[Type[_KT], Callable[[_KT], _VT]],
- *,
- ignore_unpopulated_attribute: bool = False,
-) -> Type[KeyFuncDict[_KT, _KT]]:
- """A dictionary-based collection type with column-based keying.
-
- .. versionchanged:: 2.0 Renamed :data:`.column_mapped_collection` to
- :class:`.column_keyed_dict`.
-
- Returns a :class:`.KeyFuncDict` factory which will produce new
- dictionary keys based on the value of a particular :class:`.Column`-mapped
- attribute on ORM mapped instances to be added to the dictionary.
-
- .. note:: the value of the target attribute must be assigned with its
- value at the time that the object is being added to the
- dictionary collection. Additionally, changes to the key attribute
- are **not tracked**, which means the key in the dictionary is not
- automatically synchronized with the key value on the target object
- itself. See :ref:`key_collections_mutations` for further details.
-
- .. seealso::
-
- :ref:`orm_dictionary_collection` - background on use
-
- :param mapping_spec: a :class:`_schema.Column` object that is expected
- to be mapped by the target mapper to a particular attribute on the
- mapped class, the value of which on a particular instance is to be used
- as the key for a new dictionary entry for that instance.
- :param ignore_unpopulated_attribute: if True, and the mapped attribute
- indicated by the given :class:`_schema.Column` target attribute
- on an object is not populated at all, the operation will be silently
- skipped. By default, an error is raised.
-
- .. versionadded:: 2.0 an error is raised by default if the attribute
- being used for the dictionary key is determined that it was never
- populated with any value. The
- :paramref:`_orm.column_keyed_dict.ignore_unpopulated_attribute`
- parameter may be set which will instead indicate that this condition
- should be ignored, and the append operation silently skipped.
- This is in contrast to the behavior of the 1.x series which would
- erroneously populate the value in the dictionary with an arbitrary key
- value of ``None``.
-
-
- """
- cols = [
- coercions.expect(roles.ColumnArgumentRole, q, argname="mapping_spec")
- for q in util.to_list(mapping_spec)
- ]
- keyfunc = _PlainColumnGetter(cols)
- return _mapped_collection_cls(
- keyfunc,
- ignore_unpopulated_attribute=ignore_unpopulated_attribute,
- )
-
-
-_UNMAPPED_AMBIGUOUS_NONE = object()
-
-
-class _AttrGetter:
- __slots__ = ("attr_name", "getter")
-
- def __init__(self, attr_name: str):
- self.attr_name = attr_name
- self.getter = operator.attrgetter(attr_name)
-
- def __call__(self, mapped_object: Any) -> Any:
- obj = self.getter(mapped_object)
- if obj is None:
- state = base.instance_state(mapped_object)
- mp = state.mapper
- if self.attr_name in mp.attrs:
- dict_ = state.dict
- obj = dict_.get(self.attr_name, base.NO_VALUE)
- if obj is None:
- return _UNMAPPED_AMBIGUOUS_NONE
- else:
- return _UNMAPPED_AMBIGUOUS_NONE
-
- return obj
-
- def __reduce__(self) -> Tuple[Type[_AttrGetter], Tuple[str]]:
- return _AttrGetter, (self.attr_name,)
-
-
-def attribute_keyed_dict(
- attr_name: str, *, ignore_unpopulated_attribute: bool = False
-) -> Type[KeyFuncDict[Any, Any]]:
- """A dictionary-based collection type with attribute-based keying.
-
- .. versionchanged:: 2.0 Renamed :data:`.attribute_mapped_collection` to
- :func:`.attribute_keyed_dict`.
-
- Returns a :class:`.KeyFuncDict` factory which will produce new
- dictionary keys based on the value of a particular named attribute on
- ORM mapped instances to be added to the dictionary.
-
- .. note:: the value of the target attribute must be assigned with its
- value at the time that the object is being added to the
- dictionary collection. Additionally, changes to the key attribute
- are **not tracked**, which means the key in the dictionary is not
- automatically synchronized with the key value on the target object
- itself. See :ref:`key_collections_mutations` for further details.
-
- .. seealso::
-
- :ref:`orm_dictionary_collection` - background on use
-
- :param attr_name: string name of an ORM-mapped attribute
- on the mapped class, the value of which on a particular instance
- is to be used as the key for a new dictionary entry for that instance.
- :param ignore_unpopulated_attribute: if True, and the target attribute
- on an object is not populated at all, the operation will be silently
- skipped. By default, an error is raised.
-
- .. versionadded:: 2.0 an error is raised by default if the attribute
- being used for the dictionary key is determined that it was never
- populated with any value. The
- :paramref:`_orm.attribute_keyed_dict.ignore_unpopulated_attribute`
- parameter may be set which will instead indicate that this condition
- should be ignored, and the append operation silently skipped.
- This is in contrast to the behavior of the 1.x series which would
- erroneously populate the value in the dictionary with an arbitrary key
- value of ``None``.
-
-
- """
-
- return _mapped_collection_cls(
- _AttrGetter(attr_name),
- ignore_unpopulated_attribute=ignore_unpopulated_attribute,
- )
-
-
-def keyfunc_mapping(
- keyfunc: _F,
- *,
- ignore_unpopulated_attribute: bool = False,
-) -> Type[KeyFuncDict[_KT, Any]]:
- """A dictionary-based collection type with arbitrary keying.
-
- .. versionchanged:: 2.0 Renamed :data:`.mapped_collection` to
- :func:`.keyfunc_mapping`.
-
- Returns a :class:`.KeyFuncDict` factory with a keying function
- generated from keyfunc, a callable that takes an entity and returns a
- key value.
-
- .. note:: the given keyfunc is called only once at the time that the
- target object is being added to the collection. Changes to the
- effective value returned by the function are not tracked.
-
-
- .. seealso::
-
- :ref:`orm_dictionary_collection` - background on use
-
- :param keyfunc: a callable that will be passed the ORM-mapped instance
- which should then generate a new key to use in the dictionary.
- If the value returned is :attr:`.LoaderCallableStatus.NO_VALUE`, an error
- is raised.
- :param ignore_unpopulated_attribute: if True, and the callable returns
- :attr:`.LoaderCallableStatus.NO_VALUE` for a particular instance, the
- operation will be silently skipped. By default, an error is raised.
-
- .. versionadded:: 2.0 an error is raised by default if the callable
- being used for the dictionary key returns
- :attr:`.LoaderCallableStatus.NO_VALUE`, which in an ORM attribute
- context indicates an attribute that was never populated with any value.
- The :paramref:`_orm.mapped_collection.ignore_unpopulated_attribute`
- parameter may be set which will instead indicate that this condition
- should be ignored, and the append operation silently skipped. This is
- in contrast to the behavior of the 1.x series which would erroneously
- populate the value in the dictionary with an arbitrary key value of
- ``None``.
-
-
- """
- return _mapped_collection_cls(
- keyfunc, ignore_unpopulated_attribute=ignore_unpopulated_attribute
- )
-
-
-class KeyFuncDict(Dict[_KT, _VT]):
- """Base for ORM mapped dictionary classes.
-
- Extends the ``dict`` type with additional methods needed by SQLAlchemy ORM
- collection classes. Use of :class:`_orm.KeyFuncDict` is most directly
- by using the :func:`.attribute_keyed_dict` or
- :func:`.column_keyed_dict` class factories.
- :class:`_orm.KeyFuncDict` may also serve as the base for user-defined
- custom dictionary classes.
-
- .. versionchanged:: 2.0 Renamed :class:`.MappedCollection` to
- :class:`.KeyFuncDict`.
-
- .. seealso::
-
- :func:`_orm.attribute_keyed_dict`
-
- :func:`_orm.column_keyed_dict`
-
- :ref:`orm_dictionary_collection`
-
- :ref:`orm_custom_collection`
-
-
- """
-
- def __init__(
- self,
- keyfunc: _F,
- *dict_args: Any,
- ignore_unpopulated_attribute: bool = False,
- ) -> None:
- """Create a new collection with keying provided by keyfunc.
-
- keyfunc may be any callable that takes an object and returns an object
- for use as a dictionary key.
-
- The keyfunc will be called every time the ORM needs to add a member by
- value-only (such as when loading instances from the database) or
- remove a member. The usual cautions about dictionary keying apply-
- ``keyfunc(object)`` should return the same output for the life of the
- collection. Keying based on mutable properties can result in
- unreachable instances "lost" in the collection.
-
- """
- self.keyfunc = keyfunc
- self.ignore_unpopulated_attribute = ignore_unpopulated_attribute
- super().__init__(*dict_args)
-
- @classmethod
- def _unreduce(
- cls,
- keyfunc: _F,
- values: Dict[_KT, _KT],
- adapter: Optional[CollectionAdapter] = None,
- ) -> "KeyFuncDict[_KT, _KT]":
- mp: KeyFuncDict[_KT, _KT] = KeyFuncDict(keyfunc)
- mp.update(values)
- # note that the adapter sets itself up onto this collection
- # when its `__setstate__` method is called
- return mp
-
- def __reduce__(
- self,
- ) -> Tuple[
- Callable[[_KT, _KT], KeyFuncDict[_KT, _KT]],
- Tuple[Any, Union[Dict[_KT, _KT], Dict[_KT, _KT]], CollectionAdapter],
- ]:
- return (
- KeyFuncDict._unreduce,
- (
- self.keyfunc,
- dict(self),
- collection_adapter(self),
- ),
- )
-
- @util.preload_module("sqlalchemy.orm.attributes")
- def _raise_for_unpopulated(
- self,
- value: _KT,
- initiator: Union[AttributeEventToken, Literal[None, False]] = None,
- *,
- warn_only: bool,
- ) -> None:
- mapper = base.instance_state(value).mapper
-
- attributes = util.preloaded.orm_attributes
-
- if not isinstance(initiator, attributes.AttributeEventToken):
- relationship = "unknown relationship"
- elif initiator.key in mapper.attrs:
- relationship = f"{mapper.attrs[initiator.key]}"
- else:
- relationship = initiator.key
-
- if warn_only:
- util.warn(
- f"Attribute keyed dictionary value for "
- f"attribute '{relationship}' was None; this will raise "
- "in a future release. "
- f"To skip this assignment entirely, "
- f'Set the "ignore_unpopulated_attribute=True" '
- f"parameter on the mapped collection factory."
- )
- else:
- raise sa_exc.InvalidRequestError(
- "In event triggered from population of "
- f"attribute '{relationship}' "
- "(potentially from a backref), "
- f"can't populate value in KeyFuncDict; "
- "dictionary key "
- f"derived from {base.instance_str(value)} is not "
- f"populated. Ensure appropriate state is set up on "
- f"the {base.instance_str(value)} object "
- f"before assigning to the {relationship} attribute. "
- f"To skip this assignment entirely, "
- f'Set the "ignore_unpopulated_attribute=True" '
- f"parameter on the mapped collection factory."
- )
-
- @collection.appender # type: ignore[misc]
- @collection.internally_instrumented # type: ignore[misc]
- def set(
- self,
- value: _KT,
- _sa_initiator: Union[AttributeEventToken, Literal[None, False]] = None,
- ) -> None:
- """Add an item by value, consulting the keyfunc for the key."""
-
- key = self.keyfunc(value)
-
- if key is base.NO_VALUE:
- if not self.ignore_unpopulated_attribute:
- self._raise_for_unpopulated(
- value, _sa_initiator, warn_only=False
- )
- else:
- return
- elif key is _UNMAPPED_AMBIGUOUS_NONE:
- if not self.ignore_unpopulated_attribute:
- self._raise_for_unpopulated(
- value, _sa_initiator, warn_only=True
- )
- key = None
- else:
- return
-
- self.__setitem__(key, value, _sa_initiator) # type: ignore[call-arg]
-
- @collection.remover # type: ignore[misc]
- @collection.internally_instrumented # type: ignore[misc]
- def remove(
- self,
- value: _KT,
- _sa_initiator: Union[AttributeEventToken, Literal[None, False]] = None,
- ) -> None:
- """Remove an item by value, consulting the keyfunc for the key."""
-
- key = self.keyfunc(value)
-
- if key is base.NO_VALUE:
- if not self.ignore_unpopulated_attribute:
- self._raise_for_unpopulated(
- value, _sa_initiator, warn_only=False
- )
- return
- elif key is _UNMAPPED_AMBIGUOUS_NONE:
- if not self.ignore_unpopulated_attribute:
- self._raise_for_unpopulated(
- value, _sa_initiator, warn_only=True
- )
- key = None
- else:
- return
-
- # Let self[key] raise if key is not in this collection
- # testlib.pragma exempt:__ne__
- if self[key] != value:
- raise sa_exc.InvalidRequestError(
- "Can not remove '%s': collection holds '%s' for key '%s'. "
- "Possible cause: is the KeyFuncDict key function "
- "based on mutable properties or properties that only obtain "
- "values after flush?" % (value, self[key], key)
- )
- self.__delitem__(key, _sa_initiator) # type: ignore[call-arg]
-
-
-def _mapped_collection_cls(
- keyfunc: _F, ignore_unpopulated_attribute: bool
-) -> Type[KeyFuncDict[_KT, _KT]]:
- class _MKeyfuncMapped(KeyFuncDict[_KT, _KT]):
- def __init__(self, *dict_args: Any) -> None:
- super().__init__(
- keyfunc,
- *dict_args,
- ignore_unpopulated_attribute=ignore_unpopulated_attribute,
- )
-
- return _MKeyfuncMapped
-
-
-MappedCollection = KeyFuncDict
-"""A synonym for :class:`.KeyFuncDict`.
-
-.. versionchanged:: 2.0 Renamed :class:`.MappedCollection` to
- :class:`.KeyFuncDict`.
-
-"""
-
-mapped_collection = keyfunc_mapping
-"""A synonym for :func:`_orm.keyfunc_mapping`.
-
-.. versionchanged:: 2.0 Renamed :data:`.mapped_collection` to
- :func:`_orm.keyfunc_mapping`
-
-"""
-
-attribute_mapped_collection = attribute_keyed_dict
-"""A synonym for :func:`_orm.attribute_keyed_dict`.
-
-.. versionchanged:: 2.0 Renamed :data:`.attribute_mapped_collection` to
- :func:`_orm.attribute_keyed_dict`
-
-"""
-
-column_mapped_collection = column_keyed_dict
-"""A synonym for :func:`_orm.column_keyed_dict.
-
-.. versionchanged:: 2.0 Renamed :func:`.column_mapped_collection` to
- :func:`_orm.column_keyed_dict`
-
-"""
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py
deleted file mode 100644
index 0caed0e..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py
+++ /dev/null
@@ -1,4420 +0,0 @@
-# orm/mapper.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
-# mypy: allow-untyped-defs, allow-untyped-calls
-
-"""Logic to map Python classes to and from selectables.
-
-Defines the :class:`~sqlalchemy.orm.mapper.Mapper` class, the central
-configurational unit which associates a class with a database table.
-
-This is a semi-private module; the main configurational API of the ORM is
-available in :class:`~sqlalchemy.orm.`.
-
-"""
-from __future__ import annotations
-
-from collections import deque
-from functools import reduce
-from itertools import chain
-import sys
-import threading
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Collection
-from typing import Deque
-from typing import Dict
-from typing import FrozenSet
-from typing import Generic
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Mapping
-from typing import Optional
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-import weakref
-
-from . import attributes
-from . import exc as orm_exc
-from . import instrumentation
-from . import loading
-from . import properties
-from . import util as orm_util
-from ._typing import _O
-from .base import _class_to_mapper
-from .base import _parse_mapper_argument
-from .base import _state_mapper
-from .base import PassiveFlag
-from .base import state_str
-from .interfaces import _MappedAttribute
-from .interfaces import EXT_SKIP
-from .interfaces import InspectionAttr
-from .interfaces import MapperProperty
-from .interfaces import ORMEntityColumnsClauseRole
-from .interfaces import ORMFromClauseRole
-from .interfaces import StrategizedProperty
-from .path_registry import PathRegistry
-from .. import event
-from .. import exc as sa_exc
-from .. import inspection
-from .. import log
-from .. import schema
-from .. import sql
-from .. import util
-from ..event import dispatcher
-from ..event import EventTarget
-from ..sql import base as sql_base
-from ..sql import coercions
-from ..sql import expression
-from ..sql import operators
-from ..sql import roles
-from ..sql import TableClause
-from ..sql import util as sql_util
-from ..sql import visitors
-from ..sql.cache_key import MemoizedHasCacheKey
-from ..sql.elements import KeyedColumnElement
-from ..sql.schema import Column
-from ..sql.schema import Table
-from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
-from ..util import HasMemoized
-from ..util import HasMemoized_ro_memoized_attribute
-from ..util.typing import Literal
-
-if TYPE_CHECKING:
- from ._typing import _IdentityKeyType
- from ._typing import _InstanceDict
- from ._typing import _ORMColumnExprArgument
- from ._typing import _RegistryType
- from .decl_api import registry
- from .dependency import DependencyProcessor
- from .descriptor_props import CompositeProperty
- from .descriptor_props import SynonymProperty
- from .events import MapperEvents
- from .instrumentation import ClassManager
- from .path_registry import CachingEntityRegistry
- from .properties import ColumnProperty
- from .relationships import RelationshipProperty
- from .state import InstanceState
- from .util import ORMAdapter
- from ..engine import Row
- from ..engine import RowMapping
- from ..sql._typing import _ColumnExpressionArgument
- from ..sql._typing import _EquivalentColumnMap
- from ..sql.base import ReadOnlyColumnCollection
- from ..sql.elements import ColumnClause
- from ..sql.elements import ColumnElement
- from ..sql.selectable import FromClause
- from ..util import OrderedSet
-
-
-_T = TypeVar("_T", bound=Any)
-_MP = TypeVar("_MP", bound="MapperProperty[Any]")
-_Fn = TypeVar("_Fn", bound="Callable[..., Any]")
-
-
-_WithPolymorphicArg = Union[
- Literal["*"],
- Tuple[
- Union[Literal["*"], Sequence[Union["Mapper[Any]", Type[Any]]]],
- Optional["FromClause"],
- ],
- Sequence[Union["Mapper[Any]", Type[Any]]],
-]
-
-
-_mapper_registries: weakref.WeakKeyDictionary[_RegistryType, bool] = (
- weakref.WeakKeyDictionary()
-)
-
-
-def _all_registries() -> Set[registry]:
- with _CONFIGURE_MUTEX:
- return set(_mapper_registries)
-
-
-def _unconfigured_mappers() -> Iterator[Mapper[Any]]:
- for reg in _all_registries():
- yield from reg._mappers_to_configure()
-
-
-_already_compiling = False
-
-
-# a constant returned by _get_attr_by_column to indicate
-# this mapper is not handling an attribute for a particular
-# column
-NO_ATTRIBUTE = util.symbol("NO_ATTRIBUTE")
-
-# lock used to synchronize the "mapper configure" step
-_CONFIGURE_MUTEX = threading.RLock()
-
-
-@inspection._self_inspects
-@log.class_logger
-class Mapper(
- ORMFromClauseRole,
- ORMEntityColumnsClauseRole[_O],
- MemoizedHasCacheKey,
- InspectionAttr,
- log.Identified,
- inspection.Inspectable["Mapper[_O]"],
- EventTarget,
- Generic[_O],
-):
- """Defines an association between a Python class and a database table or
- other relational structure, so that ORM operations against the class may
- proceed.
-
- The :class:`_orm.Mapper` object is instantiated using mapping methods
- present on the :class:`_orm.registry` object. For information
- about instantiating new :class:`_orm.Mapper` objects, see
- :ref:`orm_mapping_classes_toplevel`.
-
- """
-
- dispatch: dispatcher[Mapper[_O]]
-
- _dispose_called = False
- _configure_failed: Any = False
- _ready_for_configure = False
-
- @util.deprecated_params(
- non_primary=(
- "1.3",
- "The :paramref:`.mapper.non_primary` parameter is deprecated, "
- "and will be removed in a future release. The functionality "
- "of non primary mappers is now better suited using the "
- ":class:`.AliasedClass` construct, which can also be used "
- "as the target of a :func:`_orm.relationship` in 1.3.",
- ),
- )
- def __init__(
- self,
- class_: Type[_O],
- local_table: Optional[FromClause] = None,
- properties: Optional[Mapping[str, MapperProperty[Any]]] = None,
- primary_key: Optional[Iterable[_ORMColumnExprArgument[Any]]] = None,
- non_primary: bool = False,
- inherits: Optional[Union[Mapper[Any], Type[Any]]] = None,
- inherit_condition: Optional[_ColumnExpressionArgument[bool]] = None,
- inherit_foreign_keys: Optional[
- Sequence[_ORMColumnExprArgument[Any]]
- ] = None,
- always_refresh: bool = False,
- version_id_col: Optional[_ORMColumnExprArgument[Any]] = None,
- version_id_generator: Optional[
- Union[Literal[False], Callable[[Any], Any]]
- ] = None,
- polymorphic_on: Optional[
- Union[_ORMColumnExprArgument[Any], str, MapperProperty[Any]]
- ] = None,
- _polymorphic_map: Optional[Dict[Any, Mapper[Any]]] = None,
- polymorphic_identity: Optional[Any] = None,
- concrete: bool = False,
- with_polymorphic: Optional[_WithPolymorphicArg] = None,
- polymorphic_abstract: bool = False,
- polymorphic_load: Optional[Literal["selectin", "inline"]] = None,
- allow_partial_pks: bool = True,
- batch: bool = True,
- column_prefix: Optional[str] = None,
- include_properties: Optional[Sequence[str]] = None,
- exclude_properties: Optional[Sequence[str]] = None,
- passive_updates: bool = True,
- passive_deletes: bool = False,
- confirm_deleted_rows: bool = True,
- eager_defaults: Literal[True, False, "auto"] = "auto",
- legacy_is_orphan: bool = False,
- _compiled_cache_size: int = 100,
- ):
- r"""Direct constructor for a new :class:`_orm.Mapper` object.
-
- The :class:`_orm.Mapper` constructor is not called directly, and
- is normally invoked through the
- use of the :class:`_orm.registry` object through either the
- :ref:`Declarative <orm_declarative_mapping>` or
- :ref:`Imperative <orm_imperative_mapping>` mapping styles.
-
- .. versionchanged:: 2.0 The public facing ``mapper()`` function is
- removed; for a classical mapping configuration, use the
- :meth:`_orm.registry.map_imperatively` method.
-
- Parameters documented below may be passed to either the
- :meth:`_orm.registry.map_imperatively` method, or may be passed in the
- ``__mapper_args__`` declarative class attribute described at
- :ref:`orm_declarative_mapper_options`.
-
- :param class\_: The class to be mapped. When using Declarative,
- this argument is automatically passed as the declared class
- itself.
-
- :param local_table: The :class:`_schema.Table` or other
- :class:`_sql.FromClause` (i.e. selectable) to which the class is
- mapped. May be ``None`` if this mapper inherits from another mapper
- using single-table inheritance. When using Declarative, this
- argument is automatically passed by the extension, based on what is
- configured via the :attr:`_orm.DeclarativeBase.__table__` attribute
- or via the :class:`_schema.Table` produced as a result of
- the :attr:`_orm.DeclarativeBase.__tablename__` attribute being
- present.
-
- :param polymorphic_abstract: Indicates this class will be mapped in a
- polymorphic hierarchy, but not directly instantiated. The class is
- mapped normally, except that it has no requirement for a
- :paramref:`_orm.Mapper.polymorphic_identity` within an inheritance
- hierarchy. The class however must be part of a polymorphic
- inheritance scheme which uses
- :paramref:`_orm.Mapper.polymorphic_on` at the base.
-
- .. versionadded:: 2.0
-
- .. seealso::
-
- :ref:`orm_inheritance_abstract_poly`
-
- :param always_refresh: If True, all query operations for this mapped
- class will overwrite all data within object instances that already
- exist within the session, erasing any in-memory changes with
- whatever information was loaded from the database. Usage of this
- flag is highly discouraged; as an alternative, see the method
- :meth:`_query.Query.populate_existing`.
-
- :param allow_partial_pks: Defaults to True. Indicates that a
- composite primary key with some NULL values should be considered as
- possibly existing within the database. This affects whether a
- mapper will assign an incoming row to an existing identity, as well
- as if :meth:`.Session.merge` will check the database first for a
- particular primary key value. A "partial primary key" can occur if
- one has mapped to an OUTER JOIN, for example.
-
- :param batch: Defaults to ``True``, indicating that save operations
- of multiple entities can be batched together for efficiency.
- Setting to False indicates
- that an instance will be fully saved before saving the next
- instance. This is used in the extremely rare case that a
- :class:`.MapperEvents` listener requires being called
- in between individual row persistence operations.
-
- :param column_prefix: A string which will be prepended
- to the mapped attribute name when :class:`_schema.Column`
- objects are automatically assigned as attributes to the
- mapped class. Does not affect :class:`.Column` objects that
- are mapped explicitly in the :paramref:`.Mapper.properties`
- dictionary.
-
- This parameter is typically useful with imperative mappings
- that keep the :class:`.Table` object separate. Below, assuming
- the ``user_table`` :class:`.Table` object has columns named
- ``user_id``, ``user_name``, and ``password``::
-
- class User(Base):
- __table__ = user_table
- __mapper_args__ = {'column_prefix':'_'}
-
- The above mapping will assign the ``user_id``, ``user_name``, and
- ``password`` columns to attributes named ``_user_id``,
- ``_user_name``, and ``_password`` on the mapped ``User`` class.
-
- The :paramref:`.Mapper.column_prefix` parameter is uncommon in
- modern use. For dealing with reflected tables, a more flexible
- approach to automating a naming scheme is to intercept the
- :class:`.Column` objects as they are reflected; see the section
- :ref:`mapper_automated_reflection_schemes` for notes on this usage
- pattern.
-
- :param concrete: If True, indicates this mapper should use concrete
- table inheritance with its parent mapper.
-
- See the section :ref:`concrete_inheritance` for an example.
-
- :param confirm_deleted_rows: defaults to True; when a DELETE occurs
- of one more rows based on specific primary keys, a warning is
- emitted when the number of rows matched does not equal the number
- of rows expected. This parameter may be set to False to handle the
- case where database ON DELETE CASCADE rules may be deleting some of
- those rows automatically. The warning may be changed to an
- exception in a future release.
-
- :param eager_defaults: if True, the ORM will immediately fetch the
- value of server-generated default values after an INSERT or UPDATE,
- rather than leaving them as expired to be fetched on next access.
- This can be used for event schemes where the server-generated values
- are needed immediately before the flush completes.
-
- The fetch of values occurs either by using ``RETURNING`` inline
- with the ``INSERT`` or ``UPDATE`` statement, or by adding an
- additional ``SELECT`` statement subsequent to the ``INSERT`` or
- ``UPDATE``, if the backend does not support ``RETURNING``.
-
- The use of ``RETURNING`` is extremely performant in particular for
- ``INSERT`` statements where SQLAlchemy can take advantage of
- :ref:`insertmanyvalues <engine_insertmanyvalues>`, whereas the use of
- an additional ``SELECT`` is relatively poor performing, adding
- additional SQL round trips which would be unnecessary if these new
- attributes are not to be accessed in any case.
-
- For this reason, :paramref:`.Mapper.eager_defaults` defaults to the
- string value ``"auto"``, which indicates that server defaults for
- INSERT should be fetched using ``RETURNING`` if the backing database
- supports it and if the dialect in use supports "insertmanyreturning"
- for an INSERT statement. If the backing database does not support
- ``RETURNING`` or "insertmanyreturning" is not available, server
- defaults will not be fetched.
-
- .. versionchanged:: 2.0.0rc1 added the "auto" option for
- :paramref:`.Mapper.eager_defaults`
-
- .. seealso::
-
- :ref:`orm_server_defaults`
-
- .. versionchanged:: 2.0.0 RETURNING now works with multiple rows
- INSERTed at once using the
- :ref:`insertmanyvalues <engine_insertmanyvalues>` feature, which
- among other things allows the :paramref:`.Mapper.eager_defaults`
- feature to be very performant on supporting backends.
-
- :param exclude_properties: A list or set of string column names to
- be excluded from mapping.
-
- .. seealso::
-
- :ref:`include_exclude_cols`
-
- :param include_properties: An inclusive list or set of string column
- names to map.
-
- .. seealso::
-
- :ref:`include_exclude_cols`
-
- :param inherits: A mapped class or the corresponding
- :class:`_orm.Mapper`
- of one indicating a superclass to which this :class:`_orm.Mapper`
- should *inherit* from. The mapped class here must be a subclass
- of the other mapper's class. When using Declarative, this argument
- is passed automatically as a result of the natural class
- hierarchy of the declared classes.
-
- .. seealso::
-
- :ref:`inheritance_toplevel`
-
- :param inherit_condition: For joined table inheritance, a SQL
- expression which will
- define how the two tables are joined; defaults to a natural join
- between the two tables.
-
- :param inherit_foreign_keys: When ``inherit_condition`` is used and
- the columns present are missing a :class:`_schema.ForeignKey`
- configuration, this parameter can be used to specify which columns
- are "foreign". In most cases can be left as ``None``.
-
- :param legacy_is_orphan: Boolean, defaults to ``False``.
- When ``True``, specifies that "legacy" orphan consideration
- is to be applied to objects mapped by this mapper, which means
- that a pending (that is, not persistent) object is auto-expunged
- from an owning :class:`.Session` only when it is de-associated
- from *all* parents that specify a ``delete-orphan`` cascade towards
- this mapper. The new default behavior is that the object is
- auto-expunged when it is de-associated with *any* of its parents
- that specify ``delete-orphan`` cascade. This behavior is more
- consistent with that of a persistent object, and allows behavior to
- be consistent in more scenarios independently of whether or not an
- orphan object has been flushed yet or not.
-
- See the change note and example at :ref:`legacy_is_orphan_addition`
- for more detail on this change.
-
- :param non_primary: Specify that this :class:`_orm.Mapper`
- is in addition
- to the "primary" mapper, that is, the one used for persistence.
- The :class:`_orm.Mapper` created here may be used for ad-hoc
- mapping of the class to an alternate selectable, for loading
- only.
-
- .. seealso::
-
- :ref:`relationship_aliased_class` - the new pattern that removes
- the need for the :paramref:`_orm.Mapper.non_primary` flag.
-
- :param passive_deletes: Indicates DELETE behavior of foreign key
- columns when a joined-table inheritance entity is being deleted.
- Defaults to ``False`` for a base mapper; for an inheriting mapper,
- defaults to ``False`` unless the value is set to ``True``
- on the superclass mapper.
-
- When ``True``, it is assumed that ON DELETE CASCADE is configured
- on the foreign key relationships that link this mapper's table
- to its superclass table, so that when the unit of work attempts
- to delete the entity, it need only emit a DELETE statement for the
- superclass table, and not this table.
-
- When ``False``, a DELETE statement is emitted for this mapper's
- table individually. If the primary key attributes local to this
- table are unloaded, then a SELECT must be emitted in order to
- validate these attributes; note that the primary key columns
- of a joined-table subclass are not part of the "primary key" of
- the object as a whole.
-
- Note that a value of ``True`` is **always** forced onto the
- subclass mappers; that is, it's not possible for a superclass
- to specify passive_deletes without this taking effect for
- all subclass mappers.
-
- .. seealso::
-
- :ref:`passive_deletes` - description of similar feature as
- used with :func:`_orm.relationship`
-
- :paramref:`.mapper.passive_updates` - supporting ON UPDATE
- CASCADE for joined-table inheritance mappers
-
- :param passive_updates: Indicates UPDATE behavior of foreign key
- columns when a primary key column changes on a joined-table
- inheritance mapping. Defaults to ``True``.
-
- When True, it is assumed that ON UPDATE CASCADE is configured on
- the foreign key in the database, and that the database will handle
- propagation of an UPDATE from a source column to dependent columns
- on joined-table rows.
-
- When False, it is assumed that the database does not enforce
- referential integrity and will not be issuing its own CASCADE
- operation for an update. The unit of work process will
- emit an UPDATE statement for the dependent columns during a
- primary key change.
-
- .. seealso::
-
- :ref:`passive_updates` - description of a similar feature as
- used with :func:`_orm.relationship`
-
- :paramref:`.mapper.passive_deletes` - supporting ON DELETE
- CASCADE for joined-table inheritance mappers
-
- :param polymorphic_load: Specifies "polymorphic loading" behavior
- for a subclass in an inheritance hierarchy (joined and single
- table inheritance only). Valid values are:
-
- * "'inline'" - specifies this class should be part of
- the "with_polymorphic" mappers, e.g. its columns will be included
- in a SELECT query against the base.
-
- * "'selectin'" - specifies that when instances of this class
- are loaded, an additional SELECT will be emitted to retrieve
- the columns specific to this subclass. The SELECT uses
- IN to fetch multiple subclasses at once.
-
- .. versionadded:: 1.2
-
- .. seealso::
-
- :ref:`with_polymorphic_mapper_config`
-
- :ref:`polymorphic_selectin`
-
- :param polymorphic_on: Specifies the column, attribute, or
- SQL expression used to determine the target class for an
- incoming row, when inheriting classes are present.
-
- May be specified as a string attribute name, or as a SQL
- expression such as a :class:`_schema.Column` or in a Declarative
- mapping a :func:`_orm.mapped_column` object. It is typically
- expected that the SQL expression corresponds to a column in the
- base-most mapped :class:`.Table`::
-
- class Employee(Base):
- __tablename__ = 'employee'
-
- id: Mapped[int] = mapped_column(primary_key=True)
- discriminator: Mapped[str] = mapped_column(String(50))
-
- __mapper_args__ = {
- "polymorphic_on":discriminator,
- "polymorphic_identity":"employee"
- }
-
- It may also be specified
- as a SQL expression, as in this example where we
- use the :func:`.case` construct to provide a conditional
- approach::
-
- class Employee(Base):
- __tablename__ = 'employee'
-
- id: Mapped[int] = mapped_column(primary_key=True)
- discriminator: Mapped[str] = mapped_column(String(50))
-
- __mapper_args__ = {
- "polymorphic_on":case(
- (discriminator == "EN", "engineer"),
- (discriminator == "MA", "manager"),
- else_="employee"),
- "polymorphic_identity":"employee"
- }
-
- It may also refer to any attribute using its string name,
- which is of particular use when using annotated column
- configurations::
-
- class Employee(Base):
- __tablename__ = 'employee'
-
- id: Mapped[int] = mapped_column(primary_key=True)
- discriminator: Mapped[str]
-
- __mapper_args__ = {
- "polymorphic_on": "discriminator",
- "polymorphic_identity": "employee"
- }
-
- When setting ``polymorphic_on`` to reference an
- attribute or expression that's not present in the
- locally mapped :class:`_schema.Table`, yet the value
- of the discriminator should be persisted to the database,
- the value of the
- discriminator is not automatically set on new
- instances; this must be handled by the user,
- either through manual means or via event listeners.
- A typical approach to establishing such a listener
- looks like::
-
- from sqlalchemy import event
- from sqlalchemy.orm import object_mapper
-
- @event.listens_for(Employee, "init", propagate=True)
- def set_identity(instance, *arg, **kw):
- mapper = object_mapper(instance)
- instance.discriminator = mapper.polymorphic_identity
-
- Where above, we assign the value of ``polymorphic_identity``
- for the mapped class to the ``discriminator`` attribute,
- thus persisting the value to the ``discriminator`` column
- in the database.
-
- .. warning::
-
- Currently, **only one discriminator column may be set**, typically
- on the base-most class in the hierarchy. "Cascading" polymorphic
- columns are not yet supported.
-
- .. seealso::
-
- :ref:`inheritance_toplevel`
-
- :param polymorphic_identity: Specifies the value which
- identifies this particular class as returned by the column expression
- referred to by the :paramref:`_orm.Mapper.polymorphic_on` setting. As
- rows are received, the value corresponding to the
- :paramref:`_orm.Mapper.polymorphic_on` column expression is compared
- to this value, indicating which subclass should be used for the newly
- reconstructed object.
-
- .. seealso::
-
- :ref:`inheritance_toplevel`
-
- :param properties: A dictionary mapping the string names of object
- attributes to :class:`.MapperProperty` instances, which define the
- persistence behavior of that attribute. Note that
- :class:`_schema.Column`
- objects present in
- the mapped :class:`_schema.Table` are automatically placed into
- ``ColumnProperty`` instances upon mapping, unless overridden.
- When using Declarative, this argument is passed automatically,
- based on all those :class:`.MapperProperty` instances declared
- in the declared class body.
-
- .. seealso::
-
- :ref:`orm_mapping_properties` - in the
- :ref:`orm_mapping_classes_toplevel`
-
- :param primary_key: A list of :class:`_schema.Column`
- objects, or alternatively string names of attribute names which
- refer to :class:`_schema.Column`, which define
- the primary key to be used against this mapper's selectable unit.
- This is normally simply the primary key of the ``local_table``, but
- can be overridden here.
-
- .. versionchanged:: 2.0.2 :paramref:`_orm.Mapper.primary_key`
- arguments may be indicated as string attribute names as well.
-
- .. seealso::
-
- :ref:`mapper_primary_key` - background and example use
-
- :param version_id_col: A :class:`_schema.Column`
- that will be used to keep a running version id of rows
- in the table. This is used to detect concurrent updates or
- the presence of stale data in a flush. The methodology is to
- detect if an UPDATE statement does not match the last known
- version id, a
- :class:`~sqlalchemy.orm.exc.StaleDataError` exception is
- thrown.
- By default, the column must be of :class:`.Integer` type,
- unless ``version_id_generator`` specifies an alternative version
- generator.
-
- .. seealso::
-
- :ref:`mapper_version_counter` - discussion of version counting
- and rationale.
-
- :param version_id_generator: Define how new version ids should
- be generated. Defaults to ``None``, which indicates that
- a simple integer counting scheme be employed. To provide a custom
- versioning scheme, provide a callable function of the form::
-
- def generate_version(version):
- return next_version
-
- Alternatively, server-side versioning functions such as triggers,
- or programmatic versioning schemes outside of the version id
- generator may be used, by specifying the value ``False``.
- Please see :ref:`server_side_version_counter` for a discussion
- of important points when using this option.
-
- .. seealso::
-
- :ref:`custom_version_counter`
-
- :ref:`server_side_version_counter`
-
-
- :param with_polymorphic: A tuple in the form ``(<classes>,
- <selectable>)`` indicating the default style of "polymorphic"
- loading, that is, which tables are queried at once. <classes> is
- any single or list of mappers and/or classes indicating the
- inherited classes that should be loaded at once. The special value
- ``'*'`` may be used to indicate all descending classes should be
- loaded immediately. The second tuple argument <selectable>
- indicates a selectable that will be used to query for multiple
- classes.
-
- The :paramref:`_orm.Mapper.polymorphic_load` parameter may be
- preferable over the use of :paramref:`_orm.Mapper.with_polymorphic`
- in modern mappings to indicate a per-subclass technique of
- indicating polymorphic loading styles.
-
- .. seealso::
-
- :ref:`with_polymorphic_mapper_config`
-
- """
- self.class_ = util.assert_arg_type(class_, type, "class_")
- self._sort_key = "%s.%s" % (
- self.class_.__module__,
- self.class_.__name__,
- )
-
- self._primary_key_argument = util.to_list(primary_key)
- self.non_primary = non_primary
-
- self.always_refresh = always_refresh
-
- if isinstance(version_id_col, MapperProperty):
- self.version_id_prop = version_id_col
- self.version_id_col = None
- else:
- self.version_id_col = (
- coercions.expect(
- roles.ColumnArgumentOrKeyRole,
- version_id_col,
- argname="version_id_col",
- )
- if version_id_col is not None
- else None
- )
-
- if version_id_generator is False:
- self.version_id_generator = False
- elif version_id_generator is None:
- self.version_id_generator = lambda x: (x or 0) + 1
- else:
- self.version_id_generator = version_id_generator
-
- self.concrete = concrete
- self.single = False
-
- if inherits is not None:
- self.inherits = _parse_mapper_argument(inherits)
- else:
- self.inherits = None
-
- if local_table is not None:
- self.local_table = coercions.expect(
- roles.StrictFromClauseRole,
- local_table,
- disable_inspection=True,
- argname="local_table",
- )
- elif self.inherits:
- # note this is a new flow as of 2.0 so that
- # .local_table need not be Optional
- self.local_table = self.inherits.local_table
- self.single = True
- else:
- raise sa_exc.ArgumentError(
- f"Mapper[{self.class_.__name__}(None)] has None for a "
- "primary table argument and does not specify 'inherits'"
- )
-
- if inherit_condition is not None:
- self.inherit_condition = coercions.expect(
- roles.OnClauseRole, inherit_condition
- )
- else:
- self.inherit_condition = None
-
- self.inherit_foreign_keys = inherit_foreign_keys
- self._init_properties = dict(properties) if properties else {}
- self._delete_orphans = []
- self.batch = batch
- self.eager_defaults = eager_defaults
- self.column_prefix = column_prefix
-
- # interim - polymorphic_on is further refined in
- # _configure_polymorphic_setter
- self.polymorphic_on = (
- coercions.expect( # type: ignore
- roles.ColumnArgumentOrKeyRole,
- polymorphic_on,
- argname="polymorphic_on",
- )
- if polymorphic_on is not None
- else None
- )
- self.polymorphic_abstract = polymorphic_abstract
- self._dependency_processors = []
- self.validators = util.EMPTY_DICT
- self.passive_updates = passive_updates
- self.passive_deletes = passive_deletes
- self.legacy_is_orphan = legacy_is_orphan
- self._clause_adapter = None
- self._requires_row_aliasing = False
- self._inherits_equated_pairs = None
- self._memoized_values = {}
- self._compiled_cache_size = _compiled_cache_size
- self._reconstructor = None
- self.allow_partial_pks = allow_partial_pks
-
- if self.inherits and not self.concrete:
- self.confirm_deleted_rows = False
- else:
- self.confirm_deleted_rows = confirm_deleted_rows
-
- self._set_with_polymorphic(with_polymorphic)
- self.polymorphic_load = polymorphic_load
-
- # our 'polymorphic identity', a string name that when located in a
- # result set row indicates this Mapper should be used to construct
- # the object instance for that row.
- self.polymorphic_identity = polymorphic_identity
-
- # a dictionary of 'polymorphic identity' names, associating those
- # names with Mappers that will be used to construct object instances
- # upon a select operation.
- if _polymorphic_map is None:
- self.polymorphic_map = {}
- else:
- self.polymorphic_map = _polymorphic_map
-
- if include_properties is not None:
- self.include_properties = util.to_set(include_properties)
- else:
- self.include_properties = None
- if exclude_properties:
- self.exclude_properties = util.to_set(exclude_properties)
- else:
- self.exclude_properties = None
-
- # prevent this mapper from being constructed
- # while a configure_mappers() is occurring (and defer a
- # configure_mappers() until construction succeeds)
- with _CONFIGURE_MUTEX:
- cast("MapperEvents", self.dispatch._events)._new_mapper_instance(
- class_, self
- )
- self._configure_inheritance()
- self._configure_class_instrumentation()
- self._configure_properties()
- self._configure_polymorphic_setter()
- self._configure_pks()
- self.registry._flag_new_mapper(self)
- self._log("constructed")
- self._expire_memoizations()
-
- self.dispatch.after_mapper_constructed(self, self.class_)
-
- def _prefer_eager_defaults(self, dialect, table):
- if self.eager_defaults == "auto":
- if not table.implicit_returning:
- return False
-
- return (
- table in self._server_default_col_keys
- and dialect.insert_executemany_returning
- )
- else:
- return self.eager_defaults
-
- def _gen_cache_key(self, anon_map, bindparams):
- return (self,)
-
- # ### BEGIN
- # ATTRIBUTE DECLARATIONS START HERE
-
- is_mapper = True
- """Part of the inspection API."""
-
- represents_outer_join = False
-
- registry: _RegistryType
-
- @property
- def mapper(self) -> Mapper[_O]:
- """Part of the inspection API.
-
- Returns self.
-
- """
- return self
-
- @property
- def entity(self):
- r"""Part of the inspection API.
-
- Returns self.class\_.
-
- """
- return self.class_
-
- class_: Type[_O]
- """The class to which this :class:`_orm.Mapper` is mapped."""
-
- _identity_class: Type[_O]
-
- _delete_orphans: List[Tuple[str, Type[Any]]]
- _dependency_processors: List[DependencyProcessor]
- _memoized_values: Dict[Any, Callable[[], Any]]
- _inheriting_mappers: util.WeakSequence[Mapper[Any]]
- _all_tables: Set[TableClause]
- _polymorphic_attr_key: Optional[str]
-
- _pks_by_table: Dict[FromClause, OrderedSet[ColumnClause[Any]]]
- _cols_by_table: Dict[FromClause, OrderedSet[ColumnElement[Any]]]
-
- _props: util.OrderedDict[str, MapperProperty[Any]]
- _init_properties: Dict[str, MapperProperty[Any]]
-
- _columntoproperty: _ColumnMapping
-
- _set_polymorphic_identity: Optional[Callable[[InstanceState[_O]], None]]
- _validate_polymorphic_identity: Optional[
- Callable[[Mapper[_O], InstanceState[_O], _InstanceDict], None]
- ]
-
- tables: Sequence[TableClause]
- """A sequence containing the collection of :class:`_schema.Table`
- or :class:`_schema.TableClause` objects which this :class:`_orm.Mapper`
- is aware of.
-
- If the mapper is mapped to a :class:`_expression.Join`, or an
- :class:`_expression.Alias`
- representing a :class:`_expression.Select`, the individual
- :class:`_schema.Table`
- objects that comprise the full construct will be represented here.
-
- This is a *read only* attribute determined during mapper construction.
- Behavior is undefined if directly modified.
-
- """
-
- validators: util.immutabledict[str, Tuple[str, Dict[str, Any]]]
- """An immutable dictionary of attributes which have been decorated
- using the :func:`_orm.validates` decorator.
-
- The dictionary contains string attribute names as keys
- mapped to the actual validation method.
-
- """
-
- always_refresh: bool
- allow_partial_pks: bool
- version_id_col: Optional[ColumnElement[Any]]
-
- with_polymorphic: Optional[
- Tuple[
- Union[Literal["*"], Sequence[Union[Mapper[Any], Type[Any]]]],
- Optional[FromClause],
- ]
- ]
-
- version_id_generator: Optional[Union[Literal[False], Callable[[Any], Any]]]
-
- local_table: FromClause
- """The immediate :class:`_expression.FromClause` to which this
- :class:`_orm.Mapper` refers.
-
- Typically is an instance of :class:`_schema.Table`, may be any
- :class:`.FromClause`.
-
- The "local" table is the
- selectable that the :class:`_orm.Mapper` is directly responsible for
- managing from an attribute access and flush perspective. For
- non-inheriting mappers, :attr:`.Mapper.local_table` will be the same
- as :attr:`.Mapper.persist_selectable`. For inheriting mappers,
- :attr:`.Mapper.local_table` refers to the specific portion of
- :attr:`.Mapper.persist_selectable` that includes the columns to which
- this :class:`.Mapper` is loading/persisting, such as a particular
- :class:`.Table` within a join.
-
- .. seealso::
-
- :attr:`_orm.Mapper.persist_selectable`.
-
- :attr:`_orm.Mapper.selectable`.
-
- """
-
- persist_selectable: FromClause
- """The :class:`_expression.FromClause` to which this :class:`_orm.Mapper`
- is mapped.
-
- Typically is an instance of :class:`_schema.Table`, may be any
- :class:`.FromClause`.
-
- The :attr:`_orm.Mapper.persist_selectable` is similar to
- :attr:`.Mapper.local_table`, but represents the :class:`.FromClause` that
- represents the inheriting class hierarchy overall in an inheritance
- scenario.
-
- :attr.`.Mapper.persist_selectable` is also separate from the
- :attr:`.Mapper.selectable` attribute, the latter of which may be an
- alternate subquery used for selecting columns.
- :attr.`.Mapper.persist_selectable` is oriented towards columns that
- will be written on a persist operation.
-
- .. seealso::
-
- :attr:`_orm.Mapper.selectable`.
-
- :attr:`_orm.Mapper.local_table`.
-
- """
-
- inherits: Optional[Mapper[Any]]
- """References the :class:`_orm.Mapper` which this :class:`_orm.Mapper`
- inherits from, if any.
-
- """
-
- inherit_condition: Optional[ColumnElement[bool]]
-
- configured: bool = False
- """Represent ``True`` if this :class:`_orm.Mapper` has been configured.
-
- This is a *read only* attribute determined during mapper construction.
- Behavior is undefined if directly modified.
-
- .. seealso::
-
- :func:`.configure_mappers`.
-
- """
-
- concrete: bool
- """Represent ``True`` if this :class:`_orm.Mapper` is a concrete
- inheritance mapper.
-
- This is a *read only* attribute determined during mapper construction.
- Behavior is undefined if directly modified.
-
- """
-
- primary_key: Tuple[Column[Any], ...]
- """An iterable containing the collection of :class:`_schema.Column`
- objects
- which comprise the 'primary key' of the mapped table, from the
- perspective of this :class:`_orm.Mapper`.
-
- This list is against the selectable in
- :attr:`_orm.Mapper.persist_selectable`.
- In the case of inheriting mappers, some columns may be managed by a
- superclass mapper. For example, in the case of a
- :class:`_expression.Join`, the
- primary key is determined by all of the primary key columns across all
- tables referenced by the :class:`_expression.Join`.
-
- The list is also not necessarily the same as the primary key column
- collection associated with the underlying tables; the :class:`_orm.Mapper`
- features a ``primary_key`` argument that can override what the
- :class:`_orm.Mapper` considers as primary key columns.
-
- This is a *read only* attribute determined during mapper construction.
- Behavior is undefined if directly modified.
-
- """
-
- class_manager: ClassManager[_O]
- """The :class:`.ClassManager` which maintains event listeners
- and class-bound descriptors for this :class:`_orm.Mapper`.
-
- This is a *read only* attribute determined during mapper construction.
- Behavior is undefined if directly modified.
-
- """
-
- single: bool
- """Represent ``True`` if this :class:`_orm.Mapper` is a single table
- inheritance mapper.
-
- :attr:`_orm.Mapper.local_table` will be ``None`` if this flag is set.
-
- This is a *read only* attribute determined during mapper construction.
- Behavior is undefined if directly modified.
-
- """
-
- non_primary: bool
- """Represent ``True`` if this :class:`_orm.Mapper` is a "non-primary"
- mapper, e.g. a mapper that is used only to select rows but not for
- persistence management.
-
- This is a *read only* attribute determined during mapper construction.
- Behavior is undefined if directly modified.
-
- """
-
- polymorphic_on: Optional[KeyedColumnElement[Any]]
- """The :class:`_schema.Column` or SQL expression specified as the
- ``polymorphic_on`` argument
- for this :class:`_orm.Mapper`, within an inheritance scenario.
-
- This attribute is normally a :class:`_schema.Column` instance but
- may also be an expression, such as one derived from
- :func:`.cast`.
-
- This is a *read only* attribute determined during mapper construction.
- Behavior is undefined if directly modified.
-
- """
-
- polymorphic_map: Dict[Any, Mapper[Any]]
- """A mapping of "polymorphic identity" identifiers mapped to
- :class:`_orm.Mapper` instances, within an inheritance scenario.
-
- The identifiers can be of any type which is comparable to the
- type of column represented by :attr:`_orm.Mapper.polymorphic_on`.
-
- An inheritance chain of mappers will all reference the same
- polymorphic map object. The object is used to correlate incoming
- result rows to target mappers.
-
- This is a *read only* attribute determined during mapper construction.
- Behavior is undefined if directly modified.
-
- """
-
- polymorphic_identity: Optional[Any]
- """Represent an identifier which is matched against the
- :attr:`_orm.Mapper.polymorphic_on` column during result row loading.
-
- Used only with inheritance, this object can be of any type which is
- comparable to the type of column represented by
- :attr:`_orm.Mapper.polymorphic_on`.
-
- This is a *read only* attribute determined during mapper construction.
- Behavior is undefined if directly modified.
-
- """
-
- base_mapper: Mapper[Any]
- """The base-most :class:`_orm.Mapper` in an inheritance chain.
-
- In a non-inheriting scenario, this attribute will always be this
- :class:`_orm.Mapper`. In an inheritance scenario, it references
- the :class:`_orm.Mapper` which is parent to all other :class:`_orm.Mapper`
- objects in the inheritance chain.
-
- This is a *read only* attribute determined during mapper construction.
- Behavior is undefined if directly modified.
-
- """
-
- columns: ReadOnlyColumnCollection[str, Column[Any]]
- """A collection of :class:`_schema.Column` or other scalar expression
- objects maintained by this :class:`_orm.Mapper`.
-
- The collection behaves the same as that of the ``c`` attribute on
- any :class:`_schema.Table` object,
- except that only those columns included in
- this mapping are present, and are keyed based on the attribute name
- defined in the mapping, not necessarily the ``key`` attribute of the
- :class:`_schema.Column` itself. Additionally, scalar expressions mapped
- by :func:`.column_property` are also present here.
-
- This is a *read only* attribute determined during mapper construction.
- Behavior is undefined if directly modified.
-
- """
-
- c: ReadOnlyColumnCollection[str, Column[Any]]
- """A synonym for :attr:`_orm.Mapper.columns`."""
-
- @util.non_memoized_property
- @util.deprecated("1.3", "Use .persist_selectable")
- def mapped_table(self):
- return self.persist_selectable
-
- @util.memoized_property
- def _path_registry(self) -> CachingEntityRegistry:
- return PathRegistry.per_mapper(self)
-
- def _configure_inheritance(self):
- """Configure settings related to inheriting and/or inherited mappers
- being present."""
-
- # a set of all mappers which inherit from this one.
- self._inheriting_mappers = util.WeakSequence()
-
- if self.inherits:
- if not issubclass(self.class_, self.inherits.class_):
- raise sa_exc.ArgumentError(
- "Class '%s' does not inherit from '%s'"
- % (self.class_.__name__, self.inherits.class_.__name__)
- )
-
- self.dispatch._update(self.inherits.dispatch)
-
- if self.non_primary != self.inherits.non_primary:
- np = not self.non_primary and "primary" or "non-primary"
- raise sa_exc.ArgumentError(
- "Inheritance of %s mapper for class '%s' is "
- "only allowed from a %s mapper"
- % (np, self.class_.__name__, np)
- )
-
- if self.single:
- self.persist_selectable = self.inherits.persist_selectable
- elif self.local_table is not self.inherits.local_table:
- if self.concrete:
- self.persist_selectable = self.local_table
- for mapper in self.iterate_to_root():
- if mapper.polymorphic_on is not None:
- mapper._requires_row_aliasing = True
- else:
- if self.inherit_condition is None:
- # figure out inherit condition from our table to the
- # immediate table of the inherited mapper, not its
- # full table which could pull in other stuff we don't
- # want (allows test/inheritance.InheritTest4 to pass)
- try:
- self.inherit_condition = sql_util.join_condition(
- self.inherits.local_table, self.local_table
- )
- except sa_exc.NoForeignKeysError as nfe:
- assert self.inherits.local_table is not None
- assert self.local_table is not None
- raise sa_exc.NoForeignKeysError(
- "Can't determine the inherit condition "
- "between inherited table '%s' and "
- "inheriting "
- "table '%s'; tables have no "
- "foreign key relationships established. "
- "Please ensure the inheriting table has "
- "a foreign key relationship to the "
- "inherited "
- "table, or provide an "
- "'on clause' using "
- "the 'inherit_condition' mapper argument."
- % (
- self.inherits.local_table.description,
- self.local_table.description,
- )
- ) from nfe
- except sa_exc.AmbiguousForeignKeysError as afe:
- assert self.inherits.local_table is not None
- assert self.local_table is not None
- raise sa_exc.AmbiguousForeignKeysError(
- "Can't determine the inherit condition "
- "between inherited table '%s' and "
- "inheriting "
- "table '%s'; tables have more than one "
- "foreign key relationship established. "
- "Please specify the 'on clause' using "
- "the 'inherit_condition' mapper argument."
- % (
- self.inherits.local_table.description,
- self.local_table.description,
- )
- ) from afe
- assert self.inherits.persist_selectable is not None
- self.persist_selectable = sql.join(
- self.inherits.persist_selectable,
- self.local_table,
- self.inherit_condition,
- )
-
- fks = util.to_set(self.inherit_foreign_keys)
- self._inherits_equated_pairs = sql_util.criterion_as_pairs(
- self.persist_selectable.onclause,
- consider_as_foreign_keys=fks,
- )
- else:
- self.persist_selectable = self.local_table
-
- if self.polymorphic_identity is None:
- self._identity_class = self.class_
-
- if (
- not self.polymorphic_abstract
- and self.inherits.base_mapper.polymorphic_on is not None
- ):
- util.warn(
- f"{self} does not indicate a 'polymorphic_identity', "
- "yet is part of an inheritance hierarchy that has a "
- f"'polymorphic_on' column of "
- f"'{self.inherits.base_mapper.polymorphic_on}'. "
- "If this is an intermediary class that should not be "
- "instantiated, the class may either be left unmapped, "
- "or may include the 'polymorphic_abstract=True' "
- "parameter in its Mapper arguments. To leave the "
- "class unmapped when using Declarative, set the "
- "'__abstract__ = True' attribute on the class."
- )
- elif self.concrete:
- self._identity_class = self.class_
- else:
- self._identity_class = self.inherits._identity_class
-
- if self.version_id_col is None:
- self.version_id_col = self.inherits.version_id_col
- self.version_id_generator = self.inherits.version_id_generator
- elif (
- self.inherits.version_id_col is not None
- and self.version_id_col is not self.inherits.version_id_col
- ):
- util.warn(
- "Inheriting version_id_col '%s' does not match inherited "
- "version_id_col '%s' and will not automatically populate "
- "the inherited versioning column. "
- "version_id_col should only be specified on "
- "the base-most mapper that includes versioning."
- % (
- self.version_id_col.description,
- self.inherits.version_id_col.description,
- )
- )
-
- self.polymorphic_map = self.inherits.polymorphic_map
- self.batch = self.inherits.batch
- self.inherits._inheriting_mappers.append(self)
- self.base_mapper = self.inherits.base_mapper
- self.passive_updates = self.inherits.passive_updates
- self.passive_deletes = (
- self.inherits.passive_deletes or self.passive_deletes
- )
- self._all_tables = self.inherits._all_tables
-
- if self.polymorphic_identity is not None:
- if self.polymorphic_identity in self.polymorphic_map:
- util.warn(
- "Reassigning polymorphic association for identity %r "
- "from %r to %r: Check for duplicate use of %r as "
- "value for polymorphic_identity."
- % (
- self.polymorphic_identity,
- self.polymorphic_map[self.polymorphic_identity],
- self,
- self.polymorphic_identity,
- )
- )
- self.polymorphic_map[self.polymorphic_identity] = self
-
- if self.polymorphic_load and self.concrete:
- raise sa_exc.ArgumentError(
- "polymorphic_load is not currently supported "
- "with concrete table inheritance"
- )
- if self.polymorphic_load == "inline":
- self.inherits._add_with_polymorphic_subclass(self)
- elif self.polymorphic_load == "selectin":
- pass
- elif self.polymorphic_load is not None:
- raise sa_exc.ArgumentError(
- "unknown argument for polymorphic_load: %r"
- % self.polymorphic_load
- )
-
- else:
- self._all_tables = set()
- self.base_mapper = self
- assert self.local_table is not None
- self.persist_selectable = self.local_table
- if self.polymorphic_identity is not None:
- self.polymorphic_map[self.polymorphic_identity] = self
- self._identity_class = self.class_
-
- if self.persist_selectable is None:
- raise sa_exc.ArgumentError(
- "Mapper '%s' does not have a persist_selectable specified."
- % self
- )
-
- def _set_with_polymorphic(
- self, with_polymorphic: Optional[_WithPolymorphicArg]
- ) -> None:
- if with_polymorphic == "*":
- self.with_polymorphic = ("*", None)
- elif isinstance(with_polymorphic, (tuple, list)):
- if isinstance(with_polymorphic[0], (str, tuple, list)):
- self.with_polymorphic = cast(
- """Tuple[
- Union[
- Literal["*"],
- Sequence[Union["Mapper[Any]", Type[Any]]],
- ],
- Optional["FromClause"],
- ]""",
- with_polymorphic,
- )
- else:
- self.with_polymorphic = (with_polymorphic, None)
- elif with_polymorphic is not None:
- raise sa_exc.ArgumentError(
- f"Invalid setting for with_polymorphic: {with_polymorphic!r}"
- )
- else:
- self.with_polymorphic = None
-
- if self.with_polymorphic and self.with_polymorphic[1] is not None:
- self.with_polymorphic = (
- self.with_polymorphic[0],
- coercions.expect(
- roles.StrictFromClauseRole,
- self.with_polymorphic[1],
- allow_select=True,
- ),
- )
-
- if self.configured:
- self._expire_memoizations()
-
- def _add_with_polymorphic_subclass(self, mapper):
- subcl = mapper.class_
- if self.with_polymorphic is None:
- self._set_with_polymorphic((subcl,))
- elif self.with_polymorphic[0] != "*":
- assert isinstance(self.with_polymorphic[0], tuple)
- self._set_with_polymorphic(
- (self.with_polymorphic[0] + (subcl,), self.with_polymorphic[1])
- )
-
- def _set_concrete_base(self, mapper):
- """Set the given :class:`_orm.Mapper` as the 'inherits' for this
- :class:`_orm.Mapper`, assuming this :class:`_orm.Mapper` is concrete
- and does not already have an inherits."""
-
- assert self.concrete
- assert not self.inherits
- assert isinstance(mapper, Mapper)
- self.inherits = mapper
- self.inherits.polymorphic_map.update(self.polymorphic_map)
- self.polymorphic_map = self.inherits.polymorphic_map
- for mapper in self.iterate_to_root():
- if mapper.polymorphic_on is not None:
- mapper._requires_row_aliasing = True
- self.batch = self.inherits.batch
- for mp in self.self_and_descendants:
- mp.base_mapper = self.inherits.base_mapper
- self.inherits._inheriting_mappers.append(self)
- self.passive_updates = self.inherits.passive_updates
- self._all_tables = self.inherits._all_tables
-
- for key, prop in mapper._props.items():
- if key not in self._props and not self._should_exclude(
- key, key, local=False, column=None
- ):
- self._adapt_inherited_property(key, prop, False)
-
- def _set_polymorphic_on(self, polymorphic_on):
- self.polymorphic_on = polymorphic_on
- self._configure_polymorphic_setter(True)
-
- def _configure_class_instrumentation(self):
- """If this mapper is to be a primary mapper (i.e. the
- non_primary flag is not set), associate this Mapper with the
- given class and entity name.
-
- Subsequent calls to ``class_mapper()`` for the ``class_`` / ``entity``
- name combination will return this mapper. Also decorate the
- `__init__` method on the mapped class to include optional
- auto-session attachment logic.
-
- """
-
- # we expect that declarative has applied the class manager
- # already and set up a registry. if this is None,
- # this raises as of 2.0.
- manager = attributes.opt_manager_of_class(self.class_)
-
- if self.non_primary:
- if not manager or not manager.is_mapped:
- raise sa_exc.InvalidRequestError(
- "Class %s has no primary mapper configured. Configure "
- "a primary mapper first before setting up a non primary "
- "Mapper." % self.class_
- )
- self.class_manager = manager
-
- assert manager.registry is not None
- self.registry = manager.registry
- self._identity_class = manager.mapper._identity_class
- manager.registry._add_non_primary_mapper(self)
- return
-
- if manager is None or not manager.registry:
- raise sa_exc.InvalidRequestError(
- "The _mapper() function and Mapper() constructor may not be "
- "invoked directly outside of a declarative registry."
- " Please use the sqlalchemy.orm.registry.map_imperatively() "
- "function for a classical mapping."
- )
-
- self.dispatch.instrument_class(self, self.class_)
-
- # this invokes the class_instrument event and sets up
- # the __init__ method. documented behavior is that this must
- # occur after the instrument_class event above.
- # yes two events with the same two words reversed and different APIs.
- # :(
-
- manager = instrumentation.register_class(
- self.class_,
- mapper=self,
- expired_attribute_loader=util.partial(
- loading.load_scalar_attributes, self
- ),
- # finalize flag means instrument the __init__ method
- # and call the class_instrument event
- finalize=True,
- )
-
- self.class_manager = manager
-
- assert manager.registry is not None
- self.registry = manager.registry
-
- # The remaining members can be added by any mapper,
- # e_name None or not.
- if manager.mapper is None:
- return
-
- event.listen(manager, "init", _event_on_init, raw=True)
-
- for key, method in util.iterate_attributes(self.class_):
- if key == "__init__" and hasattr(method, "_sa_original_init"):
- method = method._sa_original_init
- if hasattr(method, "__func__"):
- method = method.__func__
- if callable(method):
- if hasattr(method, "__sa_reconstructor__"):
- self._reconstructor = method
- event.listen(manager, "load", _event_on_load, raw=True)
- elif hasattr(method, "__sa_validators__"):
- validation_opts = method.__sa_validation_opts__
- for name in method.__sa_validators__:
- if name in self.validators:
- raise sa_exc.InvalidRequestError(
- "A validation function for mapped "
- "attribute %r on mapper %s already exists."
- % (name, self)
- )
- self.validators = self.validators.union(
- {name: (method, validation_opts)}
- )
-
- def _set_dispose_flags(self) -> None:
- self.configured = True
- self._ready_for_configure = True
- self._dispose_called = True
-
- self.__dict__.pop("_configure_failed", None)
-
- def _str_arg_to_mapped_col(self, argname: str, key: str) -> Column[Any]:
- try:
- prop = self._props[key]
- except KeyError as err:
- raise sa_exc.ArgumentError(
- f"Can't determine {argname} column '{key}' - "
- "no attribute is mapped to this name."
- ) from err
- try:
- expr = prop.expression
- except AttributeError as ae:
- raise sa_exc.ArgumentError(
- f"Can't determine {argname} column '{key}'; "
- "property does not refer to a single mapped Column"
- ) from ae
- if not isinstance(expr, Column):
- raise sa_exc.ArgumentError(
- f"Can't determine {argname} column '{key}'; "
- "property does not refer to a single "
- "mapped Column"
- )
- return expr
-
- def _configure_pks(self) -> None:
- self.tables = sql_util.find_tables(self.persist_selectable)
-
- self._all_tables.update(t for t in self.tables)
-
- self._pks_by_table = {}
- self._cols_by_table = {}
-
- all_cols = util.column_set(
- chain(*[col.proxy_set for col in self._columntoproperty])
- )
-
- pk_cols = util.column_set(c for c in all_cols if c.primary_key)
-
- # identify primary key columns which are also mapped by this mapper.
- for fc in set(self.tables).union([self.persist_selectable]):
- if fc.primary_key and pk_cols.issuperset(fc.primary_key):
- # ordering is important since it determines the ordering of
- # mapper.primary_key (and therefore query.get())
- self._pks_by_table[fc] = util.ordered_column_set( # type: ignore # noqa: E501
- fc.primary_key
- ).intersection(
- pk_cols
- )
- self._cols_by_table[fc] = util.ordered_column_set(fc.c).intersection( # type: ignore # noqa: E501
- all_cols
- )
-
- if self._primary_key_argument:
- coerced_pk_arg = [
- (
- self._str_arg_to_mapped_col("primary_key", c)
- if isinstance(c, str)
- else c
- )
- for c in (
- coercions.expect(
- roles.DDLConstraintColumnRole,
- coerce_pk,
- argname="primary_key",
- )
- for coerce_pk in self._primary_key_argument
- )
- ]
- else:
- coerced_pk_arg = None
-
- # if explicit PK argument sent, add those columns to the
- # primary key mappings
- if coerced_pk_arg:
- for k in coerced_pk_arg:
- if k.table not in self._pks_by_table:
- self._pks_by_table[k.table] = util.OrderedSet()
- self._pks_by_table[k.table].add(k)
-
- # otherwise, see that we got a full PK for the mapped table
- elif (
- self.persist_selectable not in self._pks_by_table
- or len(self._pks_by_table[self.persist_selectable]) == 0
- ):
- raise sa_exc.ArgumentError(
- "Mapper %s could not assemble any primary "
- "key columns for mapped table '%s'"
- % (self, self.persist_selectable.description)
- )
- elif self.local_table not in self._pks_by_table and isinstance(
- self.local_table, schema.Table
- ):
- util.warn(
- "Could not assemble any primary "
- "keys for locally mapped table '%s' - "
- "no rows will be persisted in this Table."
- % self.local_table.description
- )
-
- if (
- self.inherits
- and not self.concrete
- and not self._primary_key_argument
- ):
- # if inheriting, the "primary key" for this mapper is
- # that of the inheriting (unless concrete or explicit)
- self.primary_key = self.inherits.primary_key
- else:
- # determine primary key from argument or persist_selectable pks
- primary_key: Collection[ColumnElement[Any]]
-
- if coerced_pk_arg:
- primary_key = [
- cc if cc is not None else c
- for cc, c in (
- (self.persist_selectable.corresponding_column(c), c)
- for c in coerced_pk_arg
- )
- ]
- else:
- # if heuristically determined PKs, reduce to the minimal set
- # of columns by eliminating FK->PK pairs for a multi-table
- # expression. May over-reduce for some kinds of UNIONs
- # / CTEs; use explicit PK argument for these special cases
- primary_key = sql_util.reduce_columns(
- self._pks_by_table[self.persist_selectable],
- ignore_nonexistent_tables=True,
- )
-
- if len(primary_key) == 0:
- raise sa_exc.ArgumentError(
- "Mapper %s could not assemble any primary "
- "key columns for mapped table '%s'"
- % (self, self.persist_selectable.description)
- )
-
- self.primary_key = tuple(primary_key)
- self._log("Identified primary key columns: %s", primary_key)
-
- # determine cols that aren't expressed within our tables; mark these
- # as "read only" properties which are refreshed upon INSERT/UPDATE
- self._readonly_props = {
- self._columntoproperty[col]
- for col in self._columntoproperty
- if self._columntoproperty[col] not in self._identity_key_props
- and (
- not hasattr(col, "table")
- or col.table not in self._cols_by_table
- )
- }
-
- def _configure_properties(self) -> None:
- self.columns = self.c = sql_base.ColumnCollection() # type: ignore
-
- # object attribute names mapped to MapperProperty objects
- self._props = util.OrderedDict()
-
- # table columns mapped to MapperProperty
- self._columntoproperty = _ColumnMapping(self)
-
- explicit_col_props_by_column: Dict[
- KeyedColumnElement[Any], Tuple[str, ColumnProperty[Any]]
- ] = {}
- explicit_col_props_by_key: Dict[str, ColumnProperty[Any]] = {}
-
- # step 1: go through properties that were explicitly passed
- # in the properties dictionary. For Columns that are local, put them
- # aside in a separate collection we will reconcile with the Table
- # that's given. For other properties, set them up in _props now.
- if self._init_properties:
- for key, prop_arg in self._init_properties.items():
- if not isinstance(prop_arg, MapperProperty):
- possible_col_prop = self._make_prop_from_column(
- key, prop_arg
- )
- else:
- possible_col_prop = prop_arg
-
- # issue #8705. if the explicit property is actually a
- # Column that is local to the local Table, don't set it up
- # in ._props yet, integrate it into the order given within
- # the Table.
-
- _map_as_property_now = True
- if isinstance(possible_col_prop, properties.ColumnProperty):
- for given_col in possible_col_prop.columns:
- if self.local_table.c.contains_column(given_col):
- _map_as_property_now = False
- explicit_col_props_by_key[key] = possible_col_prop
- explicit_col_props_by_column[given_col] = (
- key,
- possible_col_prop,
- )
-
- if _map_as_property_now:
- self._configure_property(
- key,
- possible_col_prop,
- init=False,
- )
-
- # step 2: pull properties from the inherited mapper. reconcile
- # columns with those which are explicit above. for properties that
- # are only in the inheriting mapper, set them up as local props
- if self.inherits:
- for key, inherited_prop in self.inherits._props.items():
- if self._should_exclude(key, key, local=False, column=None):
- continue
-
- incoming_prop = explicit_col_props_by_key.get(key)
- if incoming_prop:
- new_prop = self._reconcile_prop_with_incoming_columns(
- key,
- inherited_prop,
- warn_only=False,
- incoming_prop=incoming_prop,
- )
- explicit_col_props_by_key[key] = new_prop
-
- for inc_col in incoming_prop.columns:
- explicit_col_props_by_column[inc_col] = (
- key,
- new_prop,
- )
- elif key not in self._props:
- self._adapt_inherited_property(key, inherited_prop, False)
-
- # step 3. Iterate through all columns in the persist selectable.
- # this includes not only columns in the local table / fromclause,
- # but also those columns in the superclass table if we are joined
- # inh or single inh mapper. map these columns as well. additional
- # reconciliation against inherited columns occurs here also.
-
- for column in self.persist_selectable.columns:
- if column in explicit_col_props_by_column:
- # column was explicitly passed to properties; configure
- # it now in the order in which it corresponds to the
- # Table / selectable
- key, prop = explicit_col_props_by_column[column]
- self._configure_property(key, prop, init=False)
- continue
-
- elif column in self._columntoproperty:
- continue
-
- column_key = (self.column_prefix or "") + column.key
- if self._should_exclude(
- column.key,
- column_key,
- local=self.local_table.c.contains_column(column),
- column=column,
- ):
- continue
-
- # adjust the "key" used for this column to that
- # of the inheriting mapper
- for mapper in self.iterate_to_root():
- if column in mapper._columntoproperty:
- column_key = mapper._columntoproperty[column].key
-
- self._configure_property(
- column_key,
- column,
- init=False,
- setparent=True,
- )
-
- def _configure_polymorphic_setter(self, init=False):
- """Configure an attribute on the mapper representing the
- 'polymorphic_on' column, if applicable, and not
- already generated by _configure_properties (which is typical).
-
- Also create a setter function which will assign this
- attribute to the value of the 'polymorphic_identity'
- upon instance construction, also if applicable. This
- routine will run when an instance is created.
-
- """
- setter = False
- polymorphic_key: Optional[str] = None
-
- if self.polymorphic_on is not None:
- setter = True
-
- if isinstance(self.polymorphic_on, str):
- # polymorphic_on specified as a string - link
- # it to mapped ColumnProperty
- try:
- self.polymorphic_on = self._props[self.polymorphic_on]
- except KeyError as err:
- raise sa_exc.ArgumentError(
- "Can't determine polymorphic_on "
- "value '%s' - no attribute is "
- "mapped to this name." % self.polymorphic_on
- ) from err
-
- if self.polymorphic_on in self._columntoproperty:
- # polymorphic_on is a column that is already mapped
- # to a ColumnProperty
- prop = self._columntoproperty[self.polymorphic_on]
- elif isinstance(self.polymorphic_on, MapperProperty):
- # polymorphic_on is directly a MapperProperty,
- # ensure it's a ColumnProperty
- if not isinstance(
- self.polymorphic_on, properties.ColumnProperty
- ):
- raise sa_exc.ArgumentError(
- "Only direct column-mapped "
- "property or SQL expression "
- "can be passed for polymorphic_on"
- )
- prop = self.polymorphic_on
- else:
- # polymorphic_on is a Column or SQL expression and
- # doesn't appear to be mapped. this means it can be 1.
- # only present in the with_polymorphic selectable or
- # 2. a totally standalone SQL expression which we'd
- # hope is compatible with this mapper's persist_selectable
- col = self.persist_selectable.corresponding_column(
- self.polymorphic_on
- )
- if col is None:
- # polymorphic_on doesn't derive from any
- # column/expression isn't present in the mapped
- # table. we will make a "hidden" ColumnProperty
- # for it. Just check that if it's directly a
- # schema.Column and we have with_polymorphic, it's
- # likely a user error if the schema.Column isn't
- # represented somehow in either persist_selectable or
- # with_polymorphic. Otherwise as of 0.7.4 we
- # just go with it and assume the user wants it
- # that way (i.e. a CASE statement)
- setter = False
- instrument = False
- col = self.polymorphic_on
- if isinstance(col, schema.Column) and (
- self.with_polymorphic is None
- or self.with_polymorphic[1] is None
- or self.with_polymorphic[1].corresponding_column(col)
- is None
- ):
- raise sa_exc.InvalidRequestError(
- "Could not map polymorphic_on column "
- "'%s' to the mapped table - polymorphic "
- "loads will not function properly"
- % col.description
- )
- else:
- # column/expression that polymorphic_on derives from
- # is present in our mapped table
- # and is probably mapped, but polymorphic_on itself
- # is not. This happens when
- # the polymorphic_on is only directly present in the
- # with_polymorphic selectable, as when use
- # polymorphic_union.
- # we'll make a separate ColumnProperty for it.
- instrument = True
- key = getattr(col, "key", None)
- if key:
- if self._should_exclude(key, key, False, col):
- raise sa_exc.InvalidRequestError(
- "Cannot exclude or override the "
- "discriminator column %r" % key
- )
- else:
- self.polymorphic_on = col = col.label("_sa_polymorphic_on")
- key = col.key
-
- prop = properties.ColumnProperty(col, _instrument=instrument)
- self._configure_property(key, prop, init=init, setparent=True)
-
- # the actual polymorphic_on should be the first public-facing
- # column in the property
- self.polymorphic_on = prop.columns[0]
- polymorphic_key = prop.key
- else:
- # no polymorphic_on was set.
- # check inheriting mappers for one.
- for mapper in self.iterate_to_root():
- # determine if polymorphic_on of the parent
- # should be propagated here. If the col
- # is present in our mapped table, or if our mapped
- # table is the same as the parent (i.e. single table
- # inheritance), we can use it
- if mapper.polymorphic_on is not None:
- if self.persist_selectable is mapper.persist_selectable:
- self.polymorphic_on = mapper.polymorphic_on
- else:
- self.polymorphic_on = (
- self.persist_selectable
- ).corresponding_column(mapper.polymorphic_on)
- # we can use the parent mapper's _set_polymorphic_identity
- # directly; it ensures the polymorphic_identity of the
- # instance's mapper is used so is portable to subclasses.
- if self.polymorphic_on is not None:
- self._set_polymorphic_identity = (
- mapper._set_polymorphic_identity
- )
- self._polymorphic_attr_key = (
- mapper._polymorphic_attr_key
- )
- self._validate_polymorphic_identity = (
- mapper._validate_polymorphic_identity
- )
- else:
- self._set_polymorphic_identity = None
- self._polymorphic_attr_key = None
- return
-
- if self.polymorphic_abstract and self.polymorphic_on is None:
- raise sa_exc.InvalidRequestError(
- "The Mapper.polymorphic_abstract parameter may only be used "
- "on a mapper hierarchy which includes the "
- "Mapper.polymorphic_on parameter at the base of the hierarchy."
- )
-
- if setter:
-
- def _set_polymorphic_identity(state):
- dict_ = state.dict
- # TODO: what happens if polymorphic_on column attribute name
- # does not match .key?
-
- polymorphic_identity = (
- state.manager.mapper.polymorphic_identity
- )
- if (
- polymorphic_identity is None
- and state.manager.mapper.polymorphic_abstract
- ):
- raise sa_exc.InvalidRequestError(
- f"Can't instantiate class for {state.manager.mapper}; "
- "mapper is marked polymorphic_abstract=True"
- )
-
- state.get_impl(polymorphic_key).set(
- state,
- dict_,
- polymorphic_identity,
- None,
- )
-
- self._polymorphic_attr_key = polymorphic_key
-
- def _validate_polymorphic_identity(mapper, state, dict_):
- if (
- polymorphic_key in dict_
- and dict_[polymorphic_key]
- not in mapper._acceptable_polymorphic_identities
- ):
- util.warn_limited(
- "Flushing object %s with "
- "incompatible polymorphic identity %r; the "
- "object may not refresh and/or load correctly",
- (state_str(state), dict_[polymorphic_key]),
- )
-
- self._set_polymorphic_identity = _set_polymorphic_identity
- self._validate_polymorphic_identity = (
- _validate_polymorphic_identity
- )
- else:
- self._polymorphic_attr_key = None
- self._set_polymorphic_identity = None
-
- _validate_polymorphic_identity = None
-
- @HasMemoized.memoized_attribute
- def _version_id_prop(self):
- if self.version_id_col is not None:
- return self._columntoproperty[self.version_id_col]
- else:
- return None
-
- @HasMemoized.memoized_attribute
- def _acceptable_polymorphic_identities(self):
- identities = set()
-
- stack = deque([self])
- while stack:
- item = stack.popleft()
- if item.persist_selectable is self.persist_selectable:
- identities.add(item.polymorphic_identity)
- stack.extend(item._inheriting_mappers)
-
- return identities
-
- @HasMemoized.memoized_attribute
- def _prop_set(self):
- return frozenset(self._props.values())
-
- @util.preload_module("sqlalchemy.orm.descriptor_props")
- def _adapt_inherited_property(self, key, prop, init):
- descriptor_props = util.preloaded.orm_descriptor_props
-
- if not self.concrete:
- self._configure_property(key, prop, init=False, setparent=False)
- elif key not in self._props:
- # determine if the class implements this attribute; if not,
- # or if it is implemented by the attribute that is handling the
- # given superclass-mapped property, then we need to report that we
- # can't use this at the instance level since we are a concrete
- # mapper and we don't map this. don't trip user-defined
- # descriptors that might have side effects when invoked.
- implementing_attribute = self.class_manager._get_class_attr_mro(
- key, prop
- )
- if implementing_attribute is prop or (
- isinstance(
- implementing_attribute, attributes.InstrumentedAttribute
- )
- and implementing_attribute._parententity is prop.parent
- ):
- self._configure_property(
- key,
- descriptor_props.ConcreteInheritedProperty(),
- init=init,
- setparent=True,
- )
-
- @util.preload_module("sqlalchemy.orm.descriptor_props")
- def _configure_property(
- self,
- key: str,
- prop_arg: Union[KeyedColumnElement[Any], MapperProperty[Any]],
- *,
- init: bool = True,
- setparent: bool = True,
- warn_for_existing: bool = False,
- ) -> MapperProperty[Any]:
- descriptor_props = util.preloaded.orm_descriptor_props
- self._log(
- "_configure_property(%s, %s)", key, prop_arg.__class__.__name__
- )
-
- if not isinstance(prop_arg, MapperProperty):
- prop: MapperProperty[Any] = self._property_from_column(
- key, prop_arg
- )
- else:
- prop = prop_arg
-
- if isinstance(prop, properties.ColumnProperty):
- col = self.persist_selectable.corresponding_column(prop.columns[0])
-
- # if the column is not present in the mapped table,
- # test if a column has been added after the fact to the
- # parent table (or their parent, etc.) [ticket:1570]
- if col is None and self.inherits:
- path = [self]
- for m in self.inherits.iterate_to_root():
- col = m.local_table.corresponding_column(prop.columns[0])
- if col is not None:
- for m2 in path:
- m2.persist_selectable._refresh_for_new_column(col)
- col = self.persist_selectable.corresponding_column(
- prop.columns[0]
- )
- break
- path.append(m)
-
- # subquery expression, column not present in the mapped
- # selectable.
- if col is None:
- col = prop.columns[0]
-
- # column is coming in after _readonly_props was
- # initialized; check for 'readonly'
- if hasattr(self, "_readonly_props") and (
- not hasattr(col, "table")
- or col.table not in self._cols_by_table
- ):
- self._readonly_props.add(prop)
-
- else:
- # if column is coming in after _cols_by_table was
- # initialized, ensure the col is in the right set
- if (
- hasattr(self, "_cols_by_table")
- and col.table in self._cols_by_table
- and col not in self._cols_by_table[col.table]
- ):
- self._cols_by_table[col.table].add(col)
-
- # if this properties.ColumnProperty represents the "polymorphic
- # discriminator" column, mark it. We'll need this when rendering
- # columns in SELECT statements.
- if not hasattr(prop, "_is_polymorphic_discriminator"):
- prop._is_polymorphic_discriminator = (
- col is self.polymorphic_on
- or prop.columns[0] is self.polymorphic_on
- )
-
- if isinstance(col, expression.Label):
- # new in 1.4, get column property against expressions
- # to be addressable in subqueries
- col.key = col._tq_key_label = key
-
- self.columns.add(col, key)
-
- for col in prop.columns:
- for proxy_col in col.proxy_set:
- self._columntoproperty[proxy_col] = prop
-
- if getattr(prop, "key", key) != key:
- util.warn(
- f"ORM mapped property {self.class_.__name__}.{prop.key} being "
- "assigned to attribute "
- f"{key!r} is already associated with "
- f"attribute {prop.key!r}. The attribute will be de-associated "
- f"from {prop.key!r}."
- )
-
- prop.key = key
-
- if setparent:
- prop.set_parent(self, init)
-
- if key in self._props and getattr(
- self._props[key], "_mapped_by_synonym", False
- ):
- syn = self._props[key]._mapped_by_synonym
- raise sa_exc.ArgumentError(
- "Can't call map_column=True for synonym %r=%r, "
- "a ColumnProperty already exists keyed to the name "
- "%r for column %r" % (syn, key, key, syn)
- )
-
- # replacement cases
-
- # case one: prop is replacing a prop that we have mapped. this is
- # independent of whatever might be in the actual class dictionary
- if (
- key in self._props
- and not isinstance(
- self._props[key], descriptor_props.ConcreteInheritedProperty
- )
- and not isinstance(prop, descriptor_props.SynonymProperty)
- ):
- if warn_for_existing:
- util.warn_deprecated(
- f"User-placed attribute {self.class_.__name__}.{key} on "
- f"{self} is replacing an existing ORM-mapped attribute. "
- "Behavior is not fully defined in this case. This "
- "use is deprecated and will raise an error in a future "
- "release",
- "2.0",
- )
- oldprop = self._props[key]
- self._path_registry.pop(oldprop, None)
-
- # case two: prop is replacing an attribute on the class of some kind.
- # we have to be more careful here since it's normal when using
- # Declarative that all the "declared attributes" on the class
- # get replaced.
- elif (
- warn_for_existing
- and self.class_.__dict__.get(key, None) is not None
- and not isinstance(prop, descriptor_props.SynonymProperty)
- and not isinstance(
- self._props.get(key, None),
- descriptor_props.ConcreteInheritedProperty,
- )
- ):
- util.warn_deprecated(
- f"User-placed attribute {self.class_.__name__}.{key} on "
- f"{self} is replacing an existing class-bound "
- "attribute of the same name. "
- "Behavior is not fully defined in this case. This "
- "use is deprecated and will raise an error in a future "
- "release",
- "2.0",
- )
-
- self._props[key] = prop
-
- if not self.non_primary:
- prop.instrument_class(self)
-
- for mapper in self._inheriting_mappers:
- mapper._adapt_inherited_property(key, prop, init)
-
- if init:
- prop.init()
- prop.post_instrument_class(self)
-
- if self.configured:
- self._expire_memoizations()
-
- return prop
-
- def _make_prop_from_column(
- self,
- key: str,
- column: Union[
- Sequence[KeyedColumnElement[Any]], KeyedColumnElement[Any]
- ],
- ) -> ColumnProperty[Any]:
- columns = util.to_list(column)
- mapped_column = []
- for c in columns:
- mc = self.persist_selectable.corresponding_column(c)
- if mc is None:
- mc = self.local_table.corresponding_column(c)
- if mc is not None:
- # if the column is in the local table but not the
- # mapped table, this corresponds to adding a
- # column after the fact to the local table.
- # [ticket:1523]
- self.persist_selectable._refresh_for_new_column(mc)
- mc = self.persist_selectable.corresponding_column(c)
- if mc is None:
- raise sa_exc.ArgumentError(
- "When configuring property '%s' on %s, "
- "column '%s' is not represented in the mapper's "
- "table. Use the `column_property()` function to "
- "force this column to be mapped as a read-only "
- "attribute." % (key, self, c)
- )
- mapped_column.append(mc)
- return properties.ColumnProperty(*mapped_column)
-
- def _reconcile_prop_with_incoming_columns(
- self,
- key: str,
- existing_prop: MapperProperty[Any],
- warn_only: bool,
- incoming_prop: Optional[ColumnProperty[Any]] = None,
- single_column: Optional[KeyedColumnElement[Any]] = None,
- ) -> ColumnProperty[Any]:
- if incoming_prop and (
- self.concrete
- or not isinstance(existing_prop, properties.ColumnProperty)
- ):
- return incoming_prop
-
- existing_column = existing_prop.columns[0]
-
- if incoming_prop and existing_column in incoming_prop.columns:
- return incoming_prop
-
- if incoming_prop is None:
- assert single_column is not None
- incoming_column = single_column
- equated_pair_key = (existing_prop.columns[0], incoming_column)
- else:
- assert single_column is None
- incoming_column = incoming_prop.columns[0]
- equated_pair_key = (incoming_column, existing_prop.columns[0])
-
- if (
- (
- not self._inherits_equated_pairs
- or (equated_pair_key not in self._inherits_equated_pairs)
- )
- and not existing_column.shares_lineage(incoming_column)
- and existing_column is not self.version_id_col
- and incoming_column is not self.version_id_col
- ):
- msg = (
- "Implicitly combining column %s with column "
- "%s under attribute '%s'. Please configure one "
- "or more attributes for these same-named columns "
- "explicitly."
- % (
- existing_prop.columns[-1],
- incoming_column,
- key,
- )
- )
- if warn_only:
- util.warn(msg)
- else:
- raise sa_exc.InvalidRequestError(msg)
-
- # existing properties.ColumnProperty from an inheriting
- # mapper. make a copy and append our column to it
- # breakpoint()
- new_prop = existing_prop.copy()
-
- new_prop.columns.insert(0, incoming_column)
- self._log(
- "inserting column to existing list "
- "in properties.ColumnProperty %s",
- key,
- )
- return new_prop # type: ignore
-
- @util.preload_module("sqlalchemy.orm.descriptor_props")
- def _property_from_column(
- self,
- key: str,
- column: KeyedColumnElement[Any],
- ) -> ColumnProperty[Any]:
- """generate/update a :class:`.ColumnProperty` given a
- :class:`_schema.Column` or other SQL expression object."""
-
- descriptor_props = util.preloaded.orm_descriptor_props
-
- prop = self._props.get(key)
-
- if isinstance(prop, properties.ColumnProperty):
- return self._reconcile_prop_with_incoming_columns(
- key,
- prop,
- single_column=column,
- warn_only=prop.parent is not self,
- )
- elif prop is None or isinstance(
- prop, descriptor_props.ConcreteInheritedProperty
- ):
- return self._make_prop_from_column(key, column)
- else:
- raise sa_exc.ArgumentError(
- "WARNING: when configuring property '%s' on %s, "
- "column '%s' conflicts with property '%r'. "
- "To resolve this, map the column to the class under a "
- "different name in the 'properties' dictionary. Or, "
- "to remove all awareness of the column entirely "
- "(including its availability as a foreign key), "
- "use the 'include_properties' or 'exclude_properties' "
- "mapper arguments to control specifically which table "
- "columns get mapped." % (key, self, column.key, prop)
- )
-
- @util.langhelpers.tag_method_for_warnings(
- "This warning originated from the `configure_mappers()` process, "
- "which was invoked automatically in response to a user-initiated "
- "operation.",
- sa_exc.SAWarning,
- )
- def _check_configure(self) -> None:
- if self.registry._new_mappers:
- _configure_registries({self.registry}, cascade=True)
-
- def _post_configure_properties(self) -> None:
- """Call the ``init()`` method on all ``MapperProperties``
- attached to this mapper.
-
- This is a deferred configuration step which is intended
- to execute once all mappers have been constructed.
-
- """
-
- self._log("_post_configure_properties() started")
- l = [(key, prop) for key, prop in self._props.items()]
- for key, prop in l:
- self._log("initialize prop %s", key)
-
- if prop.parent is self and not prop._configure_started:
- prop.init()
-
- if prop._configure_finished:
- prop.post_instrument_class(self)
-
- self._log("_post_configure_properties() complete")
- self.configured = True
-
- def add_properties(self, dict_of_properties):
- """Add the given dictionary of properties to this mapper,
- using `add_property`.
-
- """
- for key, value in dict_of_properties.items():
- self.add_property(key, value)
-
- def add_property(
- self, key: str, prop: Union[Column[Any], MapperProperty[Any]]
- ) -> None:
- """Add an individual MapperProperty to this mapper.
-
- If the mapper has not been configured yet, just adds the
- property to the initial properties dictionary sent to the
- constructor. If this Mapper has already been configured, then
- the given MapperProperty is configured immediately.
-
- """
- prop = self._configure_property(
- key, prop, init=self.configured, warn_for_existing=True
- )
- assert isinstance(prop, MapperProperty)
- self._init_properties[key] = prop
-
- def _expire_memoizations(self) -> None:
- for mapper in self.iterate_to_root():
- mapper._reset_memoizations()
-
- @property
- def _log_desc(self) -> str:
- return (
- "("
- + self.class_.__name__
- + "|"
- + (
- self.local_table is not None
- and self.local_table.description
- or str(self.local_table)
- )
- + (self.non_primary and "|non-primary" or "")
- + ")"
- )
-
- def _log(self, msg: str, *args: Any) -> None:
- self.logger.info("%s " + msg, *((self._log_desc,) + args))
-
- def _log_debug(self, msg: str, *args: Any) -> None:
- self.logger.debug("%s " + msg, *((self._log_desc,) + args))
-
- def __repr__(self) -> str:
- return "<Mapper at 0x%x; %s>" % (id(self), self.class_.__name__)
-
- def __str__(self) -> str:
- return "Mapper[%s%s(%s)]" % (
- self.class_.__name__,
- self.non_primary and " (non-primary)" or "",
- (
- self.local_table.description
- if self.local_table is not None
- else self.persist_selectable.description
- ),
- )
-
- def _is_orphan(self, state: InstanceState[_O]) -> bool:
- orphan_possible = False
- for mapper in self.iterate_to_root():
- for key, cls in mapper._delete_orphans:
- orphan_possible = True
-
- has_parent = attributes.manager_of_class(cls).has_parent(
- state, key, optimistic=state.has_identity
- )
-
- if self.legacy_is_orphan and has_parent:
- return False
- elif not self.legacy_is_orphan and not has_parent:
- return True
-
- if self.legacy_is_orphan:
- return orphan_possible
- else:
- return False
-
- def has_property(self, key: str) -> bool:
- return key in self._props
-
- def get_property(
- self, key: str, _configure_mappers: bool = False
- ) -> MapperProperty[Any]:
- """return a MapperProperty associated with the given key."""
-
- if _configure_mappers:
- self._check_configure()
-
- try:
- return self._props[key]
- except KeyError as err:
- raise sa_exc.InvalidRequestError(
- f"Mapper '{self}' has no property '{key}'. If this property "
- "was indicated from other mappers or configure events, ensure "
- "registry.configure() has been called."
- ) from err
-
- def get_property_by_column(
- self, column: ColumnElement[_T]
- ) -> MapperProperty[_T]:
- """Given a :class:`_schema.Column` object, return the
- :class:`.MapperProperty` which maps this column."""
-
- return self._columntoproperty[column]
-
- @property
- def iterate_properties(self):
- """return an iterator of all MapperProperty objects."""
-
- return iter(self._props.values())
-
- def _mappers_from_spec(
- self, spec: Any, selectable: Optional[FromClause]
- ) -> Sequence[Mapper[Any]]:
- """given a with_polymorphic() argument, return the set of mappers it
- represents.
-
- Trims the list of mappers to just those represented within the given
- selectable, if present. This helps some more legacy-ish mappings.
-
- """
- if spec == "*":
- mappers = list(self.self_and_descendants)
- elif spec:
- mapper_set = set()
- for m in util.to_list(spec):
- m = _class_to_mapper(m)
- if not m.isa(self):
- raise sa_exc.InvalidRequestError(
- "%r does not inherit from %r" % (m, self)
- )
-
- if selectable is None:
- mapper_set.update(m.iterate_to_root())
- else:
- mapper_set.add(m)
- mappers = [m for m in self.self_and_descendants if m in mapper_set]
- else:
- mappers = []
-
- if selectable is not None:
- tables = set(
- sql_util.find_tables(selectable, include_aliases=True)
- )
- mappers = [m for m in mappers if m.local_table in tables]
- return mappers
-
- def _selectable_from_mappers(
- self, mappers: Iterable[Mapper[Any]], innerjoin: bool
- ) -> FromClause:
- """given a list of mappers (assumed to be within this mapper's
- inheritance hierarchy), construct an outerjoin amongst those mapper's
- mapped tables.
-
- """
- from_obj = self.persist_selectable
- for m in mappers:
- if m is self:
- continue
- if m.concrete:
- raise sa_exc.InvalidRequestError(
- "'with_polymorphic()' requires 'selectable' argument "
- "when concrete-inheriting mappers are used."
- )
- elif not m.single:
- if innerjoin:
- from_obj = from_obj.join(
- m.local_table, m.inherit_condition
- )
- else:
- from_obj = from_obj.outerjoin(
- m.local_table, m.inherit_condition
- )
-
- return from_obj
-
- @HasMemoized.memoized_attribute
- def _version_id_has_server_side_value(self) -> bool:
- vid_col = self.version_id_col
-
- if vid_col is None:
- return False
-
- elif not isinstance(vid_col, Column):
- return True
- else:
- return vid_col.server_default is not None or (
- vid_col.default is not None
- and (
- not vid_col.default.is_scalar
- and not vid_col.default.is_callable
- )
- )
-
- @HasMemoized.memoized_attribute
- def _single_table_criterion(self):
- if self.single and self.inherits and self.polymorphic_on is not None:
- return self.polymorphic_on._annotate(
- {"parententity": self, "parentmapper": self}
- ).in_(
- [
- m.polymorphic_identity
- for m in self.self_and_descendants
- if not m.polymorphic_abstract
- ]
- )
- else:
- return None
-
- @HasMemoized.memoized_attribute
- def _has_aliased_polymorphic_fromclause(self):
- """return True if with_polymorphic[1] is an aliased fromclause,
- like a subquery.
-
- As of #8168, polymorphic adaption with ORMAdapter is used only
- if this is present.
-
- """
- return self.with_polymorphic and isinstance(
- self.with_polymorphic[1],
- expression.AliasedReturnsRows,
- )
-
- @HasMemoized.memoized_attribute
- def _should_select_with_poly_adapter(self):
- """determine if _MapperEntity or _ORMColumnEntity will need to use
- polymorphic adaption when setting up a SELECT as well as fetching
- rows for mapped classes and subclasses against this Mapper.
-
- moved here from context.py for #8456 to generalize the ruleset
- for this condition.
-
- """
-
- # this has been simplified as of #8456.
- # rule is: if we have a with_polymorphic or a concrete-style
- # polymorphic selectable, *or* if the base mapper has either of those,
- # we turn on the adaption thing. if not, we do *no* adaption.
- #
- # (UPDATE for #8168: the above comment was not accurate, as we were
- # still saying "do polymorphic" if we were using an auto-generated
- # flattened JOIN for with_polymorphic.)
- #
- # this splits the behavior among the "regular" joined inheritance
- # and single inheritance mappers, vs. the "weird / difficult"
- # concrete and joined inh mappings that use a with_polymorphic of
- # some kind or polymorphic_union.
- #
- # note we have some tests in test_polymorphic_rel that query against
- # a subclass, then refer to the superclass that has a with_polymorphic
- # on it (such as test_join_from_polymorphic_explicit_aliased_three).
- # these tests actually adapt the polymorphic selectable (like, the
- # UNION or the SELECT subquery with JOIN in it) to be just the simple
- # subclass table. Hence even if we are a "plain" inheriting mapper
- # but our base has a wpoly on it, we turn on adaption. This is a
- # legacy case we should probably disable.
- #
- #
- # UPDATE: simplified way more as of #8168. polymorphic adaption
- # is turned off even if with_polymorphic is set, as long as there
- # is no user-defined aliased selectable / subquery configured.
- # this scales back the use of polymorphic adaption in practice
- # to basically no cases except for concrete inheritance with a
- # polymorphic base class.
- #
- return (
- self._has_aliased_polymorphic_fromclause
- or self._requires_row_aliasing
- or (self.base_mapper._has_aliased_polymorphic_fromclause)
- or self.base_mapper._requires_row_aliasing
- )
-
- @HasMemoized.memoized_attribute
- def _with_polymorphic_mappers(self) -> Sequence[Mapper[Any]]:
- self._check_configure()
-
- if not self.with_polymorphic:
- return []
- return self._mappers_from_spec(*self.with_polymorphic)
-
- @HasMemoized.memoized_attribute
- def _post_inspect(self):
- """This hook is invoked by attribute inspection.
-
- E.g. when Query calls:
-
- coercions.expect(roles.ColumnsClauseRole, ent, keep_inspect=True)
-
- This allows the inspection process run a configure mappers hook.
-
- """
- self._check_configure()
-
- @HasMemoized_ro_memoized_attribute
- def _with_polymorphic_selectable(self) -> FromClause:
- if not self.with_polymorphic:
- return self.persist_selectable
-
- spec, selectable = self.with_polymorphic
- if selectable is not None:
- return selectable
- else:
- return self._selectable_from_mappers(
- self._mappers_from_spec(spec, selectable), False
- )
-
- with_polymorphic_mappers = _with_polymorphic_mappers
- """The list of :class:`_orm.Mapper` objects included in the
- default "polymorphic" query.
-
- """
-
- @HasMemoized_ro_memoized_attribute
- def _insert_cols_evaluating_none(self):
- return {
- table: frozenset(
- col for col in columns if col.type.should_evaluate_none
- )
- for table, columns in self._cols_by_table.items()
- }
-
- @HasMemoized.memoized_attribute
- def _insert_cols_as_none(self):
- return {
- table: frozenset(
- col.key
- for col in columns
- if not col.primary_key
- and not col.server_default
- and not col.default
- and not col.type.should_evaluate_none
- )
- for table, columns in self._cols_by_table.items()
- }
-
- @HasMemoized.memoized_attribute
- def _propkey_to_col(self):
- return {
- table: {self._columntoproperty[col].key: col for col in columns}
- for table, columns in self._cols_by_table.items()
- }
-
- @HasMemoized.memoized_attribute
- def _pk_keys_by_table(self):
- return {
- table: frozenset([col.key for col in pks])
- for table, pks in self._pks_by_table.items()
- }
-
- @HasMemoized.memoized_attribute
- def _pk_attr_keys_by_table(self):
- return {
- table: frozenset([self._columntoproperty[col].key for col in pks])
- for table, pks in self._pks_by_table.items()
- }
-
- @HasMemoized.memoized_attribute
- def _server_default_cols(
- self,
- ) -> Mapping[FromClause, FrozenSet[Column[Any]]]:
- return {
- table: frozenset(
- [
- col
- for col in cast("Iterable[Column[Any]]", columns)
- if col.server_default is not None
- or (
- col.default is not None
- and col.default.is_clause_element
- )
- ]
- )
- for table, columns in self._cols_by_table.items()
- }
-
- @HasMemoized.memoized_attribute
- def _server_onupdate_default_cols(
- self,
- ) -> Mapping[FromClause, FrozenSet[Column[Any]]]:
- return {
- table: frozenset(
- [
- col
- for col in cast("Iterable[Column[Any]]", columns)
- if col.server_onupdate is not None
- or (
- col.onupdate is not None
- and col.onupdate.is_clause_element
- )
- ]
- )
- for table, columns in self._cols_by_table.items()
- }
-
- @HasMemoized.memoized_attribute
- def _server_default_col_keys(self) -> Mapping[FromClause, FrozenSet[str]]:
- return {
- table: frozenset(col.key for col in cols if col.key is not None)
- for table, cols in self._server_default_cols.items()
- }
-
- @HasMemoized.memoized_attribute
- def _server_onupdate_default_col_keys(
- self,
- ) -> Mapping[FromClause, FrozenSet[str]]:
- return {
- table: frozenset(col.key for col in cols if col.key is not None)
- for table, cols in self._server_onupdate_default_cols.items()
- }
-
- @HasMemoized.memoized_attribute
- def _server_default_plus_onupdate_propkeys(self) -> Set[str]:
- result: Set[str] = set()
-
- col_to_property = self._columntoproperty
- for table, columns in self._server_default_cols.items():
- result.update(
- col_to_property[col].key
- for col in columns.intersection(col_to_property)
- )
- for table, columns in self._server_onupdate_default_cols.items():
- result.update(
- col_to_property[col].key
- for col in columns.intersection(col_to_property)
- )
- return result
-
- @HasMemoized.memoized_instancemethod
- def __clause_element__(self):
- annotations: Dict[str, Any] = {
- "entity_namespace": self,
- "parententity": self,
- "parentmapper": self,
- }
- if self.persist_selectable is not self.local_table:
- # joined table inheritance, with polymorphic selectable,
- # etc.
- annotations["dml_table"] = self.local_table._annotate(
- {
- "entity_namespace": self,
- "parententity": self,
- "parentmapper": self,
- }
- )._set_propagate_attrs(
- {"compile_state_plugin": "orm", "plugin_subject": self}
- )
-
- return self.selectable._annotate(annotations)._set_propagate_attrs(
- {"compile_state_plugin": "orm", "plugin_subject": self}
- )
-
- @util.memoized_property
- def select_identity_token(self):
- return (
- expression.null()
- ._annotate(
- {
- "entity_namespace": self,
- "parententity": self,
- "parentmapper": self,
- "identity_token": True,
- }
- )
- ._set_propagate_attrs(
- {"compile_state_plugin": "orm", "plugin_subject": self}
- )
- )
-
- @property
- def selectable(self) -> FromClause:
- """The :class:`_schema.FromClause` construct this
- :class:`_orm.Mapper` selects from by default.
-
- Normally, this is equivalent to :attr:`.persist_selectable`, unless
- the ``with_polymorphic`` feature is in use, in which case the
- full "polymorphic" selectable is returned.
-
- """
- return self._with_polymorphic_selectable
-
- def _with_polymorphic_args(
- self,
- spec: Any = None,
- selectable: Union[Literal[False, None], FromClause] = False,
- innerjoin: bool = False,
- ) -> Tuple[Sequence[Mapper[Any]], FromClause]:
- if selectable not in (None, False):
- selectable = coercions.expect(
- roles.StrictFromClauseRole, selectable, allow_select=True
- )
-
- if self.with_polymorphic:
- if not spec:
- spec = self.with_polymorphic[0]
- if selectable is False:
- selectable = self.with_polymorphic[1]
- elif selectable is False:
- selectable = None
- mappers = self._mappers_from_spec(spec, selectable)
- if selectable is not None:
- return mappers, selectable
- else:
- return mappers, self._selectable_from_mappers(mappers, innerjoin)
-
- @HasMemoized.memoized_attribute
- def _polymorphic_properties(self):
- return list(
- self._iterate_polymorphic_properties(
- self._with_polymorphic_mappers
- )
- )
-
- @property
- def _all_column_expressions(self):
- poly_properties = self._polymorphic_properties
- adapter = self._polymorphic_adapter
-
- return [
- adapter.columns[c] if adapter else c
- for prop in poly_properties
- if isinstance(prop, properties.ColumnProperty)
- and prop._renders_in_subqueries
- for c in prop.columns
- ]
-
- def _columns_plus_keys(self, polymorphic_mappers=()):
- if polymorphic_mappers:
- poly_properties = self._iterate_polymorphic_properties(
- polymorphic_mappers
- )
- else:
- poly_properties = self._polymorphic_properties
-
- return [
- (prop.key, prop.columns[0])
- for prop in poly_properties
- if isinstance(prop, properties.ColumnProperty)
- ]
-
- @HasMemoized.memoized_attribute
- def _polymorphic_adapter(self) -> Optional[orm_util.ORMAdapter]:
- if self._has_aliased_polymorphic_fromclause:
- return orm_util.ORMAdapter(
- orm_util._TraceAdaptRole.MAPPER_POLYMORPHIC_ADAPTER,
- self,
- selectable=self.selectable,
- equivalents=self._equivalent_columns,
- limit_on_entity=False,
- )
- else:
- return None
-
- def _iterate_polymorphic_properties(self, mappers=None):
- """Return an iterator of MapperProperty objects which will render into
- a SELECT."""
- if mappers is None:
- mappers = self._with_polymorphic_mappers
-
- if not mappers:
- for c in self.iterate_properties:
- yield c
- else:
- # in the polymorphic case, filter out discriminator columns
- # from other mappers, as these are sometimes dependent on that
- # mapper's polymorphic selectable (which we don't want rendered)
- for c in util.unique_list(
- chain(
- *[
- list(mapper.iterate_properties)
- for mapper in [self] + mappers
- ]
- )
- ):
- if getattr(c, "_is_polymorphic_discriminator", False) and (
- self.polymorphic_on is None
- or c.columns[0] is not self.polymorphic_on
- ):
- continue
- yield c
-
- @HasMemoized.memoized_attribute
- def attrs(self) -> util.ReadOnlyProperties[MapperProperty[Any]]:
- """A namespace of all :class:`.MapperProperty` objects
- associated this mapper.
-
- This is an object that provides each property based on
- its key name. For instance, the mapper for a
- ``User`` class which has ``User.name`` attribute would
- provide ``mapper.attrs.name``, which would be the
- :class:`.ColumnProperty` representing the ``name``
- column. The namespace object can also be iterated,
- which would yield each :class:`.MapperProperty`.
-
- :class:`_orm.Mapper` has several pre-filtered views
- of this attribute which limit the types of properties
- returned, including :attr:`.synonyms`, :attr:`.column_attrs`,
- :attr:`.relationships`, and :attr:`.composites`.
-
- .. warning::
-
- The :attr:`_orm.Mapper.attrs` accessor namespace is an
- instance of :class:`.OrderedProperties`. This is
- a dictionary-like object which includes a small number of
- named methods such as :meth:`.OrderedProperties.items`
- and :meth:`.OrderedProperties.values`. When
- accessing attributes dynamically, favor using the dict-access
- scheme, e.g. ``mapper.attrs[somename]`` over
- ``getattr(mapper.attrs, somename)`` to avoid name collisions.
-
- .. seealso::
-
- :attr:`_orm.Mapper.all_orm_descriptors`
-
- """
-
- self._check_configure()
- return util.ReadOnlyProperties(self._props)
-
- @HasMemoized.memoized_attribute
- def all_orm_descriptors(self) -> util.ReadOnlyProperties[InspectionAttr]:
- """A namespace of all :class:`.InspectionAttr` attributes associated
- with the mapped class.
-
- These attributes are in all cases Python :term:`descriptors`
- associated with the mapped class or its superclasses.
-
- This namespace includes attributes that are mapped to the class
- as well as attributes declared by extension modules.
- It includes any Python descriptor type that inherits from
- :class:`.InspectionAttr`. This includes
- :class:`.QueryableAttribute`, as well as extension types such as
- :class:`.hybrid_property`, :class:`.hybrid_method` and
- :class:`.AssociationProxy`.
-
- To distinguish between mapped attributes and extension attributes,
- the attribute :attr:`.InspectionAttr.extension_type` will refer
- to a constant that distinguishes between different extension types.
-
- The sorting of the attributes is based on the following rules:
-
- 1. Iterate through the class and its superclasses in order from
- subclass to superclass (i.e. iterate through ``cls.__mro__``)
-
- 2. For each class, yield the attributes in the order in which they
- appear in ``__dict__``, with the exception of those in step
- 3 below. In Python 3.6 and above this ordering will be the
- same as that of the class' construction, with the exception
- of attributes that were added after the fact by the application
- or the mapper.
-
- 3. If a certain attribute key is also in the superclass ``__dict__``,
- then it's included in the iteration for that class, and not the
- class in which it first appeared.
-
- The above process produces an ordering that is deterministic in terms
- of the order in which attributes were assigned to the class.
-
- .. versionchanged:: 1.3.19 ensured deterministic ordering for
- :meth:`_orm.Mapper.all_orm_descriptors`.
-
- When dealing with a :class:`.QueryableAttribute`, the
- :attr:`.QueryableAttribute.property` attribute refers to the
- :class:`.MapperProperty` property, which is what you get when
- referring to the collection of mapped properties via
- :attr:`_orm.Mapper.attrs`.
-
- .. warning::
-
- The :attr:`_orm.Mapper.all_orm_descriptors`
- accessor namespace is an
- instance of :class:`.OrderedProperties`. This is
- a dictionary-like object which includes a small number of
- named methods such as :meth:`.OrderedProperties.items`
- and :meth:`.OrderedProperties.values`. When
- accessing attributes dynamically, favor using the dict-access
- scheme, e.g. ``mapper.all_orm_descriptors[somename]`` over
- ``getattr(mapper.all_orm_descriptors, somename)`` to avoid name
- collisions.
-
- .. seealso::
-
- :attr:`_orm.Mapper.attrs`
-
- """
- return util.ReadOnlyProperties(
- dict(self.class_manager._all_sqla_attributes())
- )
-
- @HasMemoized.memoized_attribute
- @util.preload_module("sqlalchemy.orm.descriptor_props")
- def _pk_synonyms(self) -> Dict[str, str]:
- """return a dictionary of {syn_attribute_name: pk_attr_name} for
- all synonyms that refer to primary key columns
-
- """
- descriptor_props = util.preloaded.orm_descriptor_props
-
- pk_keys = {prop.key for prop in self._identity_key_props}
-
- return {
- syn.key: syn.name
- for k, syn in self._props.items()
- if isinstance(syn, descriptor_props.SynonymProperty)
- and syn.name in pk_keys
- }
-
- @HasMemoized.memoized_attribute
- @util.preload_module("sqlalchemy.orm.descriptor_props")
- def synonyms(self) -> util.ReadOnlyProperties[SynonymProperty[Any]]:
- """Return a namespace of all :class:`.Synonym`
- properties maintained by this :class:`_orm.Mapper`.
-
- .. seealso::
-
- :attr:`_orm.Mapper.attrs` - namespace of all
- :class:`.MapperProperty`
- objects.
-
- """
- descriptor_props = util.preloaded.orm_descriptor_props
-
- return self._filter_properties(descriptor_props.SynonymProperty)
-
- @property
- def entity_namespace(self):
- return self.class_
-
- @HasMemoized.memoized_attribute
- def column_attrs(self) -> util.ReadOnlyProperties[ColumnProperty[Any]]:
- """Return a namespace of all :class:`.ColumnProperty`
- properties maintained by this :class:`_orm.Mapper`.
-
- .. seealso::
-
- :attr:`_orm.Mapper.attrs` - namespace of all
- :class:`.MapperProperty`
- objects.
-
- """
- return self._filter_properties(properties.ColumnProperty)
-
- @HasMemoized.memoized_attribute
- @util.preload_module("sqlalchemy.orm.relationships")
- def relationships(
- self,
- ) -> util.ReadOnlyProperties[RelationshipProperty[Any]]:
- """A namespace of all :class:`.Relationship` properties
- maintained by this :class:`_orm.Mapper`.
-
- .. warning::
-
- the :attr:`_orm.Mapper.relationships` accessor namespace is an
- instance of :class:`.OrderedProperties`. This is
- a dictionary-like object which includes a small number of
- named methods such as :meth:`.OrderedProperties.items`
- and :meth:`.OrderedProperties.values`. When
- accessing attributes dynamically, favor using the dict-access
- scheme, e.g. ``mapper.relationships[somename]`` over
- ``getattr(mapper.relationships, somename)`` to avoid name
- collisions.
-
- .. seealso::
-
- :attr:`_orm.Mapper.attrs` - namespace of all
- :class:`.MapperProperty`
- objects.
-
- """
- return self._filter_properties(
- util.preloaded.orm_relationships.RelationshipProperty
- )
-
- @HasMemoized.memoized_attribute
- @util.preload_module("sqlalchemy.orm.descriptor_props")
- def composites(self) -> util.ReadOnlyProperties[CompositeProperty[Any]]:
- """Return a namespace of all :class:`.Composite`
- properties maintained by this :class:`_orm.Mapper`.
-
- .. seealso::
-
- :attr:`_orm.Mapper.attrs` - namespace of all
- :class:`.MapperProperty`
- objects.
-
- """
- return self._filter_properties(
- util.preloaded.orm_descriptor_props.CompositeProperty
- )
-
- def _filter_properties(
- self, type_: Type[_MP]
- ) -> util.ReadOnlyProperties[_MP]:
- self._check_configure()
- return util.ReadOnlyProperties(
- util.OrderedDict(
- (k, v) for k, v in self._props.items() if isinstance(v, type_)
- )
- )
-
- @HasMemoized.memoized_attribute
- def _get_clause(self):
- """create a "get clause" based on the primary key. this is used
- by query.get() and many-to-one lazyloads to load this item
- by primary key.
-
- """
- params = [
- (
- primary_key,
- sql.bindparam("pk_%d" % idx, type_=primary_key.type),
- )
- for idx, primary_key in enumerate(self.primary_key, 1)
- ]
- return (
- sql.and_(*[k == v for (k, v) in params]),
- util.column_dict(params),
- )
-
- @HasMemoized.memoized_attribute
- def _equivalent_columns(self) -> _EquivalentColumnMap:
- """Create a map of all equivalent columns, based on
- the determination of column pairs that are equated to
- one another based on inherit condition. This is designed
- to work with the queries that util.polymorphic_union
- comes up with, which often don't include the columns from
- the base table directly (including the subclass table columns
- only).
-
- The resulting structure is a dictionary of columns mapped
- to lists of equivalent columns, e.g.::
-
- {
- tablea.col1:
- {tableb.col1, tablec.col1},
- tablea.col2:
- {tabled.col2}
- }
-
- """
- result: _EquivalentColumnMap = {}
-
- def visit_binary(binary):
- if binary.operator == operators.eq:
- if binary.left in result:
- result[binary.left].add(binary.right)
- else:
- result[binary.left] = {binary.right}
- if binary.right in result:
- result[binary.right].add(binary.left)
- else:
- result[binary.right] = {binary.left}
-
- for mapper in self.base_mapper.self_and_descendants:
- if mapper.inherit_condition is not None:
- visitors.traverse(
- mapper.inherit_condition, {}, {"binary": visit_binary}
- )
-
- return result
-
- def _is_userland_descriptor(self, assigned_name: str, obj: Any) -> bool:
- if isinstance(
- obj,
- (
- _MappedAttribute,
- instrumentation.ClassManager,
- expression.ColumnElement,
- ),
- ):
- return False
- else:
- return assigned_name not in self._dataclass_fields
-
- @HasMemoized.memoized_attribute
- def _dataclass_fields(self):
- return [f.name for f in util.dataclass_fields(self.class_)]
-
- def _should_exclude(self, name, assigned_name, local, column):
- """determine whether a particular property should be implicitly
- present on the class.
-
- This occurs when properties are propagated from an inherited class, or
- are applied from the columns present in the mapped table.
-
- """
-
- if column is not None and sql_base._never_select_column(column):
- return True
-
- # check for class-bound attributes and/or descriptors,
- # either local or from an inherited class
- # ignore dataclass field default values
- if local:
- if self.class_.__dict__.get(
- assigned_name, None
- ) is not None and self._is_userland_descriptor(
- assigned_name, self.class_.__dict__[assigned_name]
- ):
- return True
- else:
- attr = self.class_manager._get_class_attr_mro(assigned_name, None)
- if attr is not None and self._is_userland_descriptor(
- assigned_name, attr
- ):
- return True
-
- if (
- self.include_properties is not None
- and name not in self.include_properties
- and (column is None or column not in self.include_properties)
- ):
- self._log("not including property %s" % (name))
- return True
-
- if self.exclude_properties is not None and (
- name in self.exclude_properties
- or (column is not None and column in self.exclude_properties)
- ):
- self._log("excluding property %s" % (name))
- return True
-
- return False
-
- def common_parent(self, other: Mapper[Any]) -> bool:
- """Return true if the given mapper shares a
- common inherited parent as this mapper."""
-
- return self.base_mapper is other.base_mapper
-
- def is_sibling(self, other: Mapper[Any]) -> bool:
- """return true if the other mapper is an inheriting sibling to this
- one. common parent but different branch
-
- """
- return (
- self.base_mapper is other.base_mapper
- and not self.isa(other)
- and not other.isa(self)
- )
-
- def _canload(
- self, state: InstanceState[Any], allow_subtypes: bool
- ) -> bool:
- s = self.primary_mapper()
- if self.polymorphic_on is not None or allow_subtypes:
- return _state_mapper(state).isa(s)
- else:
- return _state_mapper(state) is s
-
- def isa(self, other: Mapper[Any]) -> bool:
- """Return True if the this mapper inherits from the given mapper."""
-
- m: Optional[Mapper[Any]] = self
- while m and m is not other:
- m = m.inherits
- return bool(m)
-
- def iterate_to_root(self) -> Iterator[Mapper[Any]]:
- m: Optional[Mapper[Any]] = self
- while m:
- yield m
- m = m.inherits
-
- @HasMemoized.memoized_attribute
- def self_and_descendants(self) -> Sequence[Mapper[Any]]:
- """The collection including this mapper and all descendant mappers.
-
- This includes not just the immediately inheriting mappers but
- all their inheriting mappers as well.
-
- """
- descendants = []
- stack = deque([self])
- while stack:
- item = stack.popleft()
- descendants.append(item)
- stack.extend(item._inheriting_mappers)
- return util.WeakSequence(descendants)
-
- def polymorphic_iterator(self) -> Iterator[Mapper[Any]]:
- """Iterate through the collection including this mapper and
- all descendant mappers.
-
- This includes not just the immediately inheriting mappers but
- all their inheriting mappers as well.
-
- To iterate through an entire hierarchy, use
- ``mapper.base_mapper.polymorphic_iterator()``.
-
- """
- return iter(self.self_and_descendants)
-
- def primary_mapper(self) -> Mapper[Any]:
- """Return the primary mapper corresponding to this mapper's class key
- (class)."""
-
- return self.class_manager.mapper
-
- @property
- def primary_base_mapper(self) -> Mapper[Any]:
- return self.class_manager.mapper.base_mapper
-
- def _result_has_identity_key(self, result, adapter=None):
- pk_cols: Sequence[ColumnClause[Any]] = self.primary_key
- if adapter:
- pk_cols = [adapter.columns[c] for c in pk_cols]
- rk = result.keys()
- for col in pk_cols:
- if col not in rk:
- return False
- else:
- return True
-
- def identity_key_from_row(
- self,
- row: Optional[Union[Row[Any], RowMapping]],
- identity_token: Optional[Any] = None,
- adapter: Optional[ORMAdapter] = None,
- ) -> _IdentityKeyType[_O]:
- """Return an identity-map key for use in storing/retrieving an
- item from the identity map.
-
- :param row: A :class:`.Row` or :class:`.RowMapping` produced from a
- result set that selected from the ORM mapped primary key columns.
-
- .. versionchanged:: 2.0
- :class:`.Row` or :class:`.RowMapping` are accepted
- for the "row" argument
-
- """
- pk_cols: Sequence[ColumnClause[Any]] = self.primary_key
- if adapter:
- pk_cols = [adapter.columns[c] for c in pk_cols]
-
- if hasattr(row, "_mapping"):
- mapping = row._mapping # type: ignore
- else:
- mapping = cast("Mapping[Any, Any]", row)
-
- return (
- self._identity_class,
- tuple(mapping[column] for column in pk_cols), # type: ignore
- identity_token,
- )
-
- def identity_key_from_primary_key(
- self,
- primary_key: Tuple[Any, ...],
- identity_token: Optional[Any] = None,
- ) -> _IdentityKeyType[_O]:
- """Return an identity-map key for use in storing/retrieving an
- item from an identity map.
-
- :param primary_key: A list of values indicating the identifier.
-
- """
- return (
- self._identity_class,
- tuple(primary_key),
- identity_token,
- )
-
- def identity_key_from_instance(self, instance: _O) -> _IdentityKeyType[_O]:
- """Return the identity key for the given instance, based on
- its primary key attributes.
-
- If the instance's state is expired, calling this method
- will result in a database check to see if the object has been deleted.
- If the row no longer exists,
- :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
-
- This value is typically also found on the instance state under the
- attribute name `key`.
-
- """
- state = attributes.instance_state(instance)
- return self._identity_key_from_state(state, PassiveFlag.PASSIVE_OFF)
-
- def _identity_key_from_state(
- self,
- state: InstanceState[_O],
- passive: PassiveFlag = PassiveFlag.PASSIVE_RETURN_NO_VALUE,
- ) -> _IdentityKeyType[_O]:
- dict_ = state.dict
- manager = state.manager
- return (
- self._identity_class,
- tuple(
- [
- manager[prop.key].impl.get(state, dict_, passive)
- for prop in self._identity_key_props
- ]
- ),
- state.identity_token,
- )
-
- def primary_key_from_instance(self, instance: _O) -> Tuple[Any, ...]:
- """Return the list of primary key values for the given
- instance.
-
- If the instance's state is expired, calling this method
- will result in a database check to see if the object has been deleted.
- If the row no longer exists,
- :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
-
- """
- state = attributes.instance_state(instance)
- identity_key = self._identity_key_from_state(
- state, PassiveFlag.PASSIVE_OFF
- )
- return identity_key[1]
-
- @HasMemoized.memoized_attribute
- def _persistent_sortkey_fn(self):
- key_fns = [col.type.sort_key_function for col in self.primary_key]
-
- if set(key_fns).difference([None]):
-
- def key(state):
- return tuple(
- key_fn(val) if key_fn is not None else val
- for key_fn, val in zip(key_fns, state.key[1])
- )
-
- else:
-
- def key(state):
- return state.key[1]
-
- return key
-
- @HasMemoized.memoized_attribute
- def _identity_key_props(self):
- return [self._columntoproperty[col] for col in self.primary_key]
-
- @HasMemoized.memoized_attribute
- def _all_pk_cols(self):
- collection: Set[ColumnClause[Any]] = set()
- for table in self.tables:
- collection.update(self._pks_by_table[table])
- return collection
-
- @HasMemoized.memoized_attribute
- def _should_undefer_in_wildcard(self):
- cols: Set[ColumnElement[Any]] = set(self.primary_key)
- if self.polymorphic_on is not None:
- cols.add(self.polymorphic_on)
- return cols
-
- @HasMemoized.memoized_attribute
- def _primary_key_propkeys(self):
- return {self._columntoproperty[col].key for col in self._all_pk_cols}
-
- def _get_state_attr_by_column(
- self,
- state: InstanceState[_O],
- dict_: _InstanceDict,
- column: ColumnElement[Any],
- passive: PassiveFlag = PassiveFlag.PASSIVE_RETURN_NO_VALUE,
- ) -> Any:
- prop = self._columntoproperty[column]
- return state.manager[prop.key].impl.get(state, dict_, passive=passive)
-
- def _set_committed_state_attr_by_column(self, state, dict_, column, value):
- prop = self._columntoproperty[column]
- state.manager[prop.key].impl.set_committed_value(state, dict_, value)
-
- def _set_state_attr_by_column(self, state, dict_, column, value):
- prop = self._columntoproperty[column]
- state.manager[prop.key].impl.set(state, dict_, value, None)
-
- def _get_committed_attr_by_column(self, obj, column):
- state = attributes.instance_state(obj)
- dict_ = attributes.instance_dict(obj)
- return self._get_committed_state_attr_by_column(
- state, dict_, column, passive=PassiveFlag.PASSIVE_OFF
- )
-
- def _get_committed_state_attr_by_column(
- self, state, dict_, column, passive=PassiveFlag.PASSIVE_RETURN_NO_VALUE
- ):
- prop = self._columntoproperty[column]
- return state.manager[prop.key].impl.get_committed_value(
- state, dict_, passive=passive
- )
-
- def _optimized_get_statement(self, state, attribute_names):
- """assemble a WHERE clause which retrieves a given state by primary
- key, using a minimized set of tables.
-
- Applies to a joined-table inheritance mapper where the
- requested attribute names are only present on joined tables,
- not the base table. The WHERE clause attempts to include
- only those tables to minimize joins.
-
- """
- props = self._props
-
- col_attribute_names = set(attribute_names).intersection(
- state.mapper.column_attrs.keys()
- )
- tables: Set[FromClause] = set(
- chain(
- *[
- sql_util.find_tables(c, check_columns=True)
- for key in col_attribute_names
- for c in props[key].columns
- ]
- )
- )
-
- if self.base_mapper.local_table in tables:
- return None
-
- def visit_binary(binary):
- leftcol = binary.left
- rightcol = binary.right
- if leftcol is None or rightcol is None:
- return
-
- if leftcol.table not in tables:
- leftval = self._get_committed_state_attr_by_column(
- state,
- state.dict,
- leftcol,
- passive=PassiveFlag.PASSIVE_NO_INITIALIZE,
- )
- if leftval in orm_util._none_set:
- raise _OptGetColumnsNotAvailable()
- binary.left = sql.bindparam(
- None, leftval, type_=binary.right.type
- )
- elif rightcol.table not in tables:
- rightval = self._get_committed_state_attr_by_column(
- state,
- state.dict,
- rightcol,
- passive=PassiveFlag.PASSIVE_NO_INITIALIZE,
- )
- if rightval in orm_util._none_set:
- raise _OptGetColumnsNotAvailable()
- binary.right = sql.bindparam(
- None, rightval, type_=binary.right.type
- )
-
- allconds: List[ColumnElement[bool]] = []
-
- start = False
-
- # as of #7507, from the lowest base table on upwards,
- # we include all intermediary tables.
-
- for mapper in reversed(list(self.iterate_to_root())):
- if mapper.local_table in tables:
- start = True
- elif not isinstance(mapper.local_table, expression.TableClause):
- return None
- if start and not mapper.single:
- assert mapper.inherits
- assert not mapper.concrete
- assert mapper.inherit_condition is not None
- allconds.append(mapper.inherit_condition)
- tables.add(mapper.local_table)
-
- # only the bottom table needs its criteria to be altered to fit
- # the primary key ident - the rest of the tables upwards to the
- # descendant-most class should all be present and joined to each
- # other.
- try:
- _traversed = visitors.cloned_traverse(
- allconds[0], {}, {"binary": visit_binary}
- )
- except _OptGetColumnsNotAvailable:
- return None
- else:
- allconds[0] = _traversed
-
- cond = sql.and_(*allconds)
-
- cols = []
- for key in col_attribute_names:
- cols.extend(props[key].columns)
- return (
- sql.select(*cols)
- .where(cond)
- .set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
- )
-
- def _iterate_to_target_viawpoly(self, mapper):
- if self.isa(mapper):
- prev = self
- for m in self.iterate_to_root():
- yield m
-
- if m is not prev and prev not in m._with_polymorphic_mappers:
- break
-
- prev = m
- if m is mapper:
- break
-
- @HasMemoized.memoized_attribute
- def _would_selectinload_combinations_cache(self):
- return {}
-
- def _would_selectin_load_only_from_given_mapper(self, super_mapper):
- """return True if this mapper would "selectin" polymorphic load based
- on the given super mapper, and not from a setting from a subclass.
-
- given::
-
- class A:
- ...
-
- class B(A):
- __mapper_args__ = {"polymorphic_load": "selectin"}
-
- class C(B):
- ...
-
- class D(B):
- __mapper_args__ = {"polymorphic_load": "selectin"}
-
- ``inspect(C)._would_selectin_load_only_from_given_mapper(inspect(B))``
- returns True, because C does selectin loading because of B's setting.
-
- OTOH, ``inspect(D)
- ._would_selectin_load_only_from_given_mapper(inspect(B))``
- returns False, because D does selectin loading because of its own
- setting; when we are doing a selectin poly load from B, we want to
- filter out D because it would already have its own selectin poly load
- set up separately.
-
- Added as part of #9373.
-
- """
- cache = self._would_selectinload_combinations_cache
-
- try:
- return cache[super_mapper]
- except KeyError:
- pass
-
- # assert that given object is a supermapper, meaning we already
- # strong reference it directly or indirectly. this allows us
- # to not worry that we are creating new strongrefs to unrelated
- # mappers or other objects.
- assert self.isa(super_mapper)
-
- mapper = super_mapper
- for m in self._iterate_to_target_viawpoly(mapper):
- if m.polymorphic_load == "selectin":
- retval = m is super_mapper
- break
- else:
- retval = False
-
- cache[super_mapper] = retval
- return retval
-
- def _should_selectin_load(self, enabled_via_opt, polymorphic_from):
- if not enabled_via_opt:
- # common case, takes place for all polymorphic loads
- mapper = polymorphic_from
- for m in self._iterate_to_target_viawpoly(mapper):
- if m.polymorphic_load == "selectin":
- return m
- else:
- # uncommon case, selectin load options were used
- enabled_via_opt = set(enabled_via_opt)
- enabled_via_opt_mappers = {e.mapper: e for e in enabled_via_opt}
- for entity in enabled_via_opt.union([polymorphic_from]):
- mapper = entity.mapper
- for m in self._iterate_to_target_viawpoly(mapper):
- if (
- m.polymorphic_load == "selectin"
- or m in enabled_via_opt_mappers
- ):
- return enabled_via_opt_mappers.get(m, m)
-
- return None
-
- @util.preload_module("sqlalchemy.orm.strategy_options")
- def _subclass_load_via_in(self, entity, polymorphic_from):
- """Assemble a that can load the columns local to
- this subclass as a SELECT with IN.
-
- """
- strategy_options = util.preloaded.orm_strategy_options
-
- assert self.inherits
-
- if self.polymorphic_on is not None:
- polymorphic_prop = self._columntoproperty[self.polymorphic_on]
- keep_props = set([polymorphic_prop] + self._identity_key_props)
- else:
- keep_props = set(self._identity_key_props)
-
- disable_opt = strategy_options.Load(entity)
- enable_opt = strategy_options.Load(entity)
-
- classes_to_include = {self}
- m: Optional[Mapper[Any]] = self.inherits
- while (
- m is not None
- and m is not polymorphic_from
- and m.polymorphic_load == "selectin"
- ):
- classes_to_include.add(m)
- m = m.inherits
-
- for prop in self.attrs:
- # skip prop keys that are not instrumented on the mapped class.
- # this is primarily the "_sa_polymorphic_on" property that gets
- # created for an ad-hoc polymorphic_on SQL expression, issue #8704
- if prop.key not in self.class_manager:
- continue
-
- if prop.parent in classes_to_include or prop in keep_props:
- # "enable" options, to turn on the properties that we want to
- # load by default (subject to options from the query)
- if not isinstance(prop, StrategizedProperty):
- continue
-
- enable_opt = enable_opt._set_generic_strategy(
- # convert string name to an attribute before passing
- # to loader strategy. note this must be in terms
- # of given entity, such as AliasedClass, etc.
- (getattr(entity.entity_namespace, prop.key),),
- dict(prop.strategy_key),
- _reconcile_to_other=True,
- )
- else:
- # "disable" options, to turn off the properties from the
- # superclass that we *don't* want to load, applied after
- # the options from the query to override them
- disable_opt = disable_opt._set_generic_strategy(
- # convert string name to an attribute before passing
- # to loader strategy. note this must be in terms
- # of given entity, such as AliasedClass, etc.
- (getattr(entity.entity_namespace, prop.key),),
- {"do_nothing": True},
- _reconcile_to_other=False,
- )
-
- primary_key = [
- sql_util._deep_annotate(pk, {"_orm_adapt": True})
- for pk in self.primary_key
- ]
-
- in_expr: ColumnElement[Any]
-
- if len(primary_key) > 1:
- in_expr = sql.tuple_(*primary_key)
- else:
- in_expr = primary_key[0]
-
- if entity.is_aliased_class:
- assert entity.mapper is self
-
- q = sql.select(entity).set_label_style(
- LABEL_STYLE_TABLENAME_PLUS_COL
- )
-
- in_expr = entity._adapter.traverse(in_expr)
- primary_key = [entity._adapter.traverse(k) for k in primary_key]
- q = q.where(
- in_expr.in_(sql.bindparam("primary_keys", expanding=True))
- ).order_by(*primary_key)
- else:
- q = sql.select(self).set_label_style(
- LABEL_STYLE_TABLENAME_PLUS_COL
- )
- q = q.where(
- in_expr.in_(sql.bindparam("primary_keys", expanding=True))
- ).order_by(*primary_key)
-
- return q, enable_opt, disable_opt
-
- @HasMemoized.memoized_attribute
- def _subclass_load_via_in_mapper(self):
- # the default is loading this mapper against the basemost mapper
- return self._subclass_load_via_in(self, self.base_mapper)
-
- def cascade_iterator(
- self,
- type_: str,
- state: InstanceState[_O],
- halt_on: Optional[Callable[[InstanceState[Any]], bool]] = None,
- ) -> Iterator[
- Tuple[object, Mapper[Any], InstanceState[Any], _InstanceDict]
- ]:
- r"""Iterate each element and its mapper in an object graph,
- for all relationships that meet the given cascade rule.
-
- :param type\_:
- The name of the cascade rule (i.e. ``"save-update"``, ``"delete"``,
- etc.).
-
- .. note:: the ``"all"`` cascade is not accepted here. For a generic
- object traversal function, see :ref:`faq_walk_objects`.
-
- :param state:
- The lead InstanceState. child items will be processed per
- the relationships defined for this object's mapper.
-
- :return: the method yields individual object instances.
-
- .. seealso::
-
- :ref:`unitofwork_cascades`
-
- :ref:`faq_walk_objects` - illustrates a generic function to
- traverse all objects without relying on cascades.
-
- """
- visited_states: Set[InstanceState[Any]] = set()
- prp, mpp = object(), object()
-
- assert state.mapper.isa(self)
-
- # this is actually a recursive structure, fully typing it seems
- # a little too difficult for what it's worth here
- visitables: Deque[
- Tuple[
- Deque[Any],
- object,
- Optional[InstanceState[Any]],
- Optional[_InstanceDict],
- ]
- ]
-
- visitables = deque(
- [(deque(state.mapper._props.values()), prp, state, state.dict)]
- )
-
- while visitables:
- iterator, item_type, parent_state, parent_dict = visitables[-1]
- if not iterator:
- visitables.pop()
- continue
-
- if item_type is prp:
- prop = iterator.popleft()
- if not prop.cascade or type_ not in prop.cascade:
- continue
- assert parent_state is not None
- assert parent_dict is not None
- queue = deque(
- prop.cascade_iterator(
- type_,
- parent_state,
- parent_dict,
- visited_states,
- halt_on,
- )
- )
- if queue:
- visitables.append((queue, mpp, None, None))
- elif item_type is mpp:
- (
- instance,
- instance_mapper,
- corresponding_state,
- corresponding_dict,
- ) = iterator.popleft()
- yield (
- instance,
- instance_mapper,
- corresponding_state,
- corresponding_dict,
- )
- visitables.append(
- (
- deque(instance_mapper._props.values()),
- prp,
- corresponding_state,
- corresponding_dict,
- )
- )
-
- @HasMemoized.memoized_attribute
- def _compiled_cache(self):
- return util.LRUCache(self._compiled_cache_size)
-
- @HasMemoized.memoized_attribute
- def _multiple_persistence_tables(self):
- return len(self.tables) > 1
-
- @HasMemoized.memoized_attribute
- def _sorted_tables(self):
- table_to_mapper: Dict[TableClause, Mapper[Any]] = {}
-
- for mapper in self.base_mapper.self_and_descendants:
- for t in mapper.tables:
- table_to_mapper.setdefault(t, mapper)
-
- extra_dependencies = []
- for table, mapper in table_to_mapper.items():
- super_ = mapper.inherits
- if super_:
- extra_dependencies.extend(
- [(super_table, table) for super_table in super_.tables]
- )
-
- def skip(fk):
- # attempt to skip dependencies that are not
- # significant to the inheritance chain
- # for two tables that are related by inheritance.
- # while that dependency may be important, it's technically
- # not what we mean to sort on here.
- parent = table_to_mapper.get(fk.parent.table)
- dep = table_to_mapper.get(fk.column.table)
- if (
- parent is not None
- and dep is not None
- and dep is not parent
- and dep.inherit_condition is not None
- ):
- cols = set(sql_util._find_columns(dep.inherit_condition))
- if parent.inherit_condition is not None:
- cols = cols.union(
- sql_util._find_columns(parent.inherit_condition)
- )
- return fk.parent not in cols and fk.column not in cols
- else:
- return fk.parent not in cols
- return False
-
- sorted_ = sql_util.sort_tables(
- table_to_mapper,
- skip_fn=skip,
- extra_dependencies=extra_dependencies,
- )
-
- ret = util.OrderedDict()
- for t in sorted_:
- ret[t] = table_to_mapper[t]
- return ret
-
- def _memo(self, key: Any, callable_: Callable[[], _T]) -> _T:
- if key in self._memoized_values:
- return cast(_T, self._memoized_values[key])
- else:
- self._memoized_values[key] = value = callable_()
- return value
-
- @util.memoized_property
- def _table_to_equated(self):
- """memoized map of tables to collections of columns to be
- synchronized upwards to the base mapper."""
-
- result: util.defaultdict[
- Table,
- List[
- Tuple[
- Mapper[Any],
- List[Tuple[ColumnElement[Any], ColumnElement[Any]]],
- ]
- ],
- ] = util.defaultdict(list)
-
- def set_union(x, y):
- return x.union(y)
-
- for table in self._sorted_tables:
- cols = set(table.c)
-
- for m in self.iterate_to_root():
- if m._inherits_equated_pairs and cols.intersection(
- reduce(
- set_union,
- [l.proxy_set for l, r in m._inherits_equated_pairs],
- )
- ):
- result[table].append((m, m._inherits_equated_pairs))
-
- return result
-
-
-class _OptGetColumnsNotAvailable(Exception):
- pass
-
-
-def configure_mappers() -> None:
- """Initialize the inter-mapper relationships of all mappers that
- have been constructed thus far across all :class:`_orm.registry`
- collections.
-
- The configure step is used to reconcile and initialize the
- :func:`_orm.relationship` linkages between mapped classes, as well as to
- invoke configuration events such as the
- :meth:`_orm.MapperEvents.before_configured` and
- :meth:`_orm.MapperEvents.after_configured`, which may be used by ORM
- extensions or user-defined extension hooks.
-
- Mapper configuration is normally invoked automatically, the first time
- mappings from a particular :class:`_orm.registry` are used, as well as
- whenever mappings are used and additional not-yet-configured mappers have
- been constructed. The automatic configuration process however is local only
- to the :class:`_orm.registry` involving the target mapper and any related
- :class:`_orm.registry` objects which it may depend on; this is
- equivalent to invoking the :meth:`_orm.registry.configure` method
- on a particular :class:`_orm.registry`.
-
- By contrast, the :func:`_orm.configure_mappers` function will invoke the
- configuration process on all :class:`_orm.registry` objects that
- exist in memory, and may be useful for scenarios where many individual
- :class:`_orm.registry` objects that are nonetheless interrelated are
- in use.
-
- .. versionchanged:: 1.4
-
- As of SQLAlchemy 1.4.0b2, this function works on a
- per-:class:`_orm.registry` basis, locating all :class:`_orm.registry`
- objects present and invoking the :meth:`_orm.registry.configure` method
- on each. The :meth:`_orm.registry.configure` method may be preferred to
- limit the configuration of mappers to those local to a particular
- :class:`_orm.registry` and/or declarative base class.
-
- Points at which automatic configuration is invoked include when a mapped
- class is instantiated into an instance, as well as when ORM queries
- are emitted using :meth:`.Session.query` or :meth:`_orm.Session.execute`
- with an ORM-enabled statement.
-
- The mapper configure process, whether invoked by
- :func:`_orm.configure_mappers` or from :meth:`_orm.registry.configure`,
- provides several event hooks that can be used to augment the mapper
- configuration step. These hooks include:
-
- * :meth:`.MapperEvents.before_configured` - called once before
- :func:`.configure_mappers` or :meth:`_orm.registry.configure` does any
- work; this can be used to establish additional options, properties, or
- related mappings before the operation proceeds.
-
- * :meth:`.MapperEvents.mapper_configured` - called as each individual
- :class:`_orm.Mapper` is configured within the process; will include all
- mapper state except for backrefs set up by other mappers that are still
- to be configured.
-
- * :meth:`.MapperEvents.after_configured` - called once after
- :func:`.configure_mappers` or :meth:`_orm.registry.configure` is
- complete; at this stage, all :class:`_orm.Mapper` objects that fall
- within the scope of the configuration operation will be fully configured.
- Note that the calling application may still have other mappings that
- haven't been produced yet, such as if they are in modules as yet
- unimported, and may also have mappings that are still to be configured,
- if they are in other :class:`_orm.registry` collections not part of the
- current scope of configuration.
-
- """
-
- _configure_registries(_all_registries(), cascade=True)
-
-
-def _configure_registries(
- registries: Set[_RegistryType], cascade: bool
-) -> None:
- for reg in registries:
- if reg._new_mappers:
- break
- else:
- return
-
- with _CONFIGURE_MUTEX:
- global _already_compiling
- if _already_compiling:
- return
- _already_compiling = True
- try:
- # double-check inside mutex
- for reg in registries:
- if reg._new_mappers:
- break
- else:
- return
-
- Mapper.dispatch._for_class(Mapper).before_configured() # type: ignore # noqa: E501
- # initialize properties on all mappers
- # note that _mapper_registry is unordered, which
- # may randomly conceal/reveal issues related to
- # the order of mapper compilation
-
- _do_configure_registries(registries, cascade)
- finally:
- _already_compiling = False
- Mapper.dispatch._for_class(Mapper).after_configured() # type: ignore
-
-
-@util.preload_module("sqlalchemy.orm.decl_api")
-def _do_configure_registries(
- registries: Set[_RegistryType], cascade: bool
-) -> None:
- registry = util.preloaded.orm_decl_api.registry
-
- orig = set(registries)
-
- for reg in registry._recurse_with_dependencies(registries):
- has_skip = False
-
- for mapper in reg._mappers_to_configure():
- run_configure = None
-
- for fn in mapper.dispatch.before_mapper_configured:
- run_configure = fn(mapper, mapper.class_)
- if run_configure is EXT_SKIP:
- has_skip = True
- break
- if run_configure is EXT_SKIP:
- continue
-
- if getattr(mapper, "_configure_failed", False):
- e = sa_exc.InvalidRequestError(
- "One or more mappers failed to initialize - "
- "can't proceed with initialization of other "
- "mappers. Triggering mapper: '%s'. "
- "Original exception was: %s"
- % (mapper, mapper._configure_failed)
- )
- e._configure_failed = mapper._configure_failed # type: ignore
- raise e
-
- if not mapper.configured:
- try:
- mapper._post_configure_properties()
- mapper._expire_memoizations()
- mapper.dispatch.mapper_configured(mapper, mapper.class_)
- except Exception:
- exc = sys.exc_info()[1]
- if not hasattr(exc, "_configure_failed"):
- mapper._configure_failed = exc
- raise
- if not has_skip:
- reg._new_mappers = False
-
- if not cascade and reg._dependencies.difference(orig):
- raise sa_exc.InvalidRequestError(
- "configure was called with cascade=False but "
- "additional registries remain"
- )
-
-
-@util.preload_module("sqlalchemy.orm.decl_api")
-def _dispose_registries(registries: Set[_RegistryType], cascade: bool) -> None:
- registry = util.preloaded.orm_decl_api.registry
-
- orig = set(registries)
-
- for reg in registry._recurse_with_dependents(registries):
- if not cascade and reg._dependents.difference(orig):
- raise sa_exc.InvalidRequestError(
- "Registry has dependent registries that are not disposed; "
- "pass cascade=True to clear these also"
- )
-
- while reg._managers:
- try:
- manager, _ = reg._managers.popitem()
- except KeyError:
- # guard against race between while and popitem
- pass
- else:
- reg._dispose_manager_and_mapper(manager)
-
- reg._non_primary_mappers.clear()
- reg._dependents.clear()
- for dep in reg._dependencies:
- dep._dependents.discard(reg)
- reg._dependencies.clear()
- # this wasn't done in the 1.3 clear_mappers() and in fact it
- # was a bug, as it could cause configure_mappers() to invoke
- # the "before_configured" event even though mappers had all been
- # disposed.
- reg._new_mappers = False
-
-
-def reconstructor(fn):
- """Decorate a method as the 'reconstructor' hook.
-
- Designates a single method as the "reconstructor", an ``__init__``-like
- method that will be called by the ORM after the instance has been
- loaded from the database or otherwise reconstituted.
-
- .. tip::
-
- The :func:`_orm.reconstructor` decorator makes use of the
- :meth:`_orm.InstanceEvents.load` event hook, which can be
- used directly.
-
- The reconstructor will be invoked with no arguments. Scalar
- (non-collection) database-mapped attributes of the instance will
- be available for use within the function. Eagerly-loaded
- collections are generally not yet available and will usually only
- contain the first element. ORM state changes made to objects at
- this stage will not be recorded for the next flush() operation, so
- the activity within a reconstructor should be conservative.
-
- .. seealso::
-
- :meth:`.InstanceEvents.load`
-
- """
- fn.__sa_reconstructor__ = True
- return fn
-
-
-def validates(
- *names: str, include_removes: bool = False, include_backrefs: bool = True
-) -> Callable[[_Fn], _Fn]:
- r"""Decorate a method as a 'validator' for one or more named properties.
-
- Designates a method as a validator, a method which receives the
- name of the attribute as well as a value to be assigned, or in the
- case of a collection, the value to be added to the collection.
- The function can then raise validation exceptions to halt the
- process from continuing (where Python's built-in ``ValueError``
- and ``AssertionError`` exceptions are reasonable choices), or can
- modify or replace the value before proceeding. The function should
- otherwise return the given value.
-
- Note that a validator for a collection **cannot** issue a load of that
- collection within the validation routine - this usage raises
- an assertion to avoid recursion overflows. This is a reentrant
- condition which is not supported.
-
- :param \*names: list of attribute names to be validated.
- :param include_removes: if True, "remove" events will be
- sent as well - the validation function must accept an additional
- argument "is_remove" which will be a boolean.
-
- :param include_backrefs: defaults to ``True``; if ``False``, the
- validation function will not emit if the originator is an attribute
- event related via a backref. This can be used for bi-directional
- :func:`.validates` usage where only one validator should emit per
- attribute operation.
-
- .. versionchanged:: 2.0.16 This paramter inadvertently defaulted to
- ``False`` for releases 2.0.0 through 2.0.15. Its correct default
- of ``True`` is restored in 2.0.16.
-
- .. seealso::
-
- :ref:`simple_validators` - usage examples for :func:`.validates`
-
- """
-
- def wrap(fn: _Fn) -> _Fn:
- fn.__sa_validators__ = names # type: ignore[attr-defined]
- fn.__sa_validation_opts__ = { # type: ignore[attr-defined]
- "include_removes": include_removes,
- "include_backrefs": include_backrefs,
- }
- return fn
-
- return wrap
-
-
-def _event_on_load(state, ctx):
- instrumenting_mapper = state.manager.mapper
-
- if instrumenting_mapper._reconstructor:
- instrumenting_mapper._reconstructor(state.obj())
-
-
-def _event_on_init(state, args, kwargs):
- """Run init_instance hooks.
-
- This also includes mapper compilation, normally not needed
- here but helps with some piecemeal configuration
- scenarios (such as in the ORM tutorial).
-
- """
-
- instrumenting_mapper = state.manager.mapper
- if instrumenting_mapper:
- instrumenting_mapper._check_configure()
- if instrumenting_mapper._set_polymorphic_identity:
- instrumenting_mapper._set_polymorphic_identity(state)
-
-
-class _ColumnMapping(Dict["ColumnElement[Any]", "MapperProperty[Any]"]):
- """Error reporting helper for mapper._columntoproperty."""
-
- __slots__ = ("mapper",)
-
- def __init__(self, mapper):
- # TODO: weakref would be a good idea here
- self.mapper = mapper
-
- def __missing__(self, column):
- prop = self.mapper._props.get(column)
- if prop:
- raise orm_exc.UnmappedColumnError(
- "Column '%s.%s' is not available, due to "
- "conflicting property '%s':%r"
- % (column.table.name, column.name, column.key, prop)
- )
- raise orm_exc.UnmappedColumnError(
- "No column %s is configured on mapper %s..."
- % (column, self.mapper)
- )
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/path_registry.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/path_registry.py
deleted file mode 100644
index 76484b3..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/path_registry.py
+++ /dev/null
@@ -1,808 +0,0 @@
-# orm/path_registry.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
-"""Path tracking utilities, representing mapper graph traversals.
-
-"""
-
-from __future__ import annotations
-
-from functools import reduce
-from itertools import chain
-import logging
-import operator
-from typing import Any
-from typing import cast
-from typing import Dict
-from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import overload
-from typing import Sequence
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import Union
-
-from . import base as orm_base
-from ._typing import insp_is_mapper_property
-from .. import exc
-from .. import util
-from ..sql import visitors
-from ..sql.cache_key import HasCacheKey
-
-if TYPE_CHECKING:
- from ._typing import _InternalEntityType
- from .interfaces import MapperProperty
- from .mapper import Mapper
- from .relationships import RelationshipProperty
- from .util import AliasedInsp
- from ..sql.cache_key import _CacheKeyTraversalType
- from ..sql.elements import BindParameter
- from ..sql.visitors import anon_map
- from ..util.typing import _LiteralStar
- from ..util.typing import TypeGuard
-
- def is_root(path: PathRegistry) -> TypeGuard[RootRegistry]: ...
-
- def is_entity(path: PathRegistry) -> TypeGuard[AbstractEntityRegistry]: ...
-
-else:
- is_root = operator.attrgetter("is_root")
- is_entity = operator.attrgetter("is_entity")
-
-
-_SerializedPath = List[Any]
-_StrPathToken = str
-_PathElementType = Union[
- _StrPathToken, "_InternalEntityType[Any]", "MapperProperty[Any]"
-]
-
-# the representation is in fact
-# a tuple with alternating:
-# [_InternalEntityType[Any], Union[str, MapperProperty[Any]],
-# _InternalEntityType[Any], Union[str, MapperProperty[Any]], ...]
-# this might someday be a tuple of 2-tuples instead, but paths can be
-# chopped at odd intervals as well so this is less flexible
-_PathRepresentation = Tuple[_PathElementType, ...]
-
-# NOTE: these names are weird since the array is 0-indexed,
-# the "_Odd" entries are at 0, 2, 4, etc
-_OddPathRepresentation = Sequence["_InternalEntityType[Any]"]
-_EvenPathRepresentation = Sequence[Union["MapperProperty[Any]", str]]
-
-
-log = logging.getLogger(__name__)
-
-
-def _unreduce_path(path: _SerializedPath) -> PathRegistry:
- return PathRegistry.deserialize(path)
-
-
-_WILDCARD_TOKEN: _LiteralStar = "*"
-_DEFAULT_TOKEN = "_sa_default"
-
-
-class PathRegistry(HasCacheKey):
- """Represent query load paths and registry functions.
-
- Basically represents structures like:
-
- (<User mapper>, "orders", <Order mapper>, "items", <Item mapper>)
-
- These structures are generated by things like
- query options (joinedload(), subqueryload(), etc.) and are
- used to compose keys stored in the query._attributes dictionary
- for various options.
-
- They are then re-composed at query compile/result row time as
- the query is formed and as rows are fetched, where they again
- serve to compose keys to look up options in the context.attributes
- dictionary, which is copied from query._attributes.
-
- The path structure has a limited amount of caching, where each
- "root" ultimately pulls from a fixed registry associated with
- the first mapper, that also contains elements for each of its
- property keys. However paths longer than two elements, which
- are the exception rather than the rule, are generated on an
- as-needed basis.
-
- """
-
- __slots__ = ()
-
- is_token = False
- is_root = False
- has_entity = False
- is_property = False
- is_entity = False
-
- is_unnatural: bool
-
- path: _PathRepresentation
- natural_path: _PathRepresentation
- parent: Optional[PathRegistry]
- root: RootRegistry
-
- _cache_key_traversal: _CacheKeyTraversalType = [
- ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key_list)
- ]
-
- def __eq__(self, other: Any) -> bool:
- try:
- return other is not None and self.path == other._path_for_compare
- except AttributeError:
- util.warn(
- "Comparison of PathRegistry to %r is not supported"
- % (type(other))
- )
- return False
-
- def __ne__(self, other: Any) -> bool:
- try:
- return other is None or self.path != other._path_for_compare
- except AttributeError:
- util.warn(
- "Comparison of PathRegistry to %r is not supported"
- % (type(other))
- )
- return True
-
- @property
- def _path_for_compare(self) -> Optional[_PathRepresentation]:
- return self.path
-
- def odd_element(self, index: int) -> _InternalEntityType[Any]:
- return self.path[index] # type: ignore
-
- def set(self, attributes: Dict[Any, Any], key: Any, value: Any) -> None:
- log.debug("set '%s' on path '%s' to '%s'", key, self, value)
- attributes[(key, self.natural_path)] = value
-
- def setdefault(
- self, attributes: Dict[Any, Any], key: Any, value: Any
- ) -> None:
- log.debug("setdefault '%s' on path '%s' to '%s'", key, self, value)
- attributes.setdefault((key, self.natural_path), value)
-
- def get(
- self, attributes: Dict[Any, Any], key: Any, value: Optional[Any] = None
- ) -> Any:
- key = (key, self.natural_path)
- if key in attributes:
- return attributes[key]
- else:
- return value
-
- def __len__(self) -> int:
- return len(self.path)
-
- def __hash__(self) -> int:
- return id(self)
-
- @overload
- def __getitem__(self, entity: _StrPathToken) -> TokenRegistry: ...
-
- @overload
- def __getitem__(self, entity: int) -> _PathElementType: ...
-
- @overload
- def __getitem__(self, entity: slice) -> _PathRepresentation: ...
-
- @overload
- def __getitem__(
- self, entity: _InternalEntityType[Any]
- ) -> AbstractEntityRegistry: ...
-
- @overload
- def __getitem__(self, entity: MapperProperty[Any]) -> PropRegistry: ...
-
- def __getitem__(
- self,
- entity: Union[
- _StrPathToken,
- int,
- slice,
- _InternalEntityType[Any],
- MapperProperty[Any],
- ],
- ) -> Union[
- TokenRegistry,
- _PathElementType,
- _PathRepresentation,
- PropRegistry,
- AbstractEntityRegistry,
- ]:
- raise NotImplementedError()
-
- # TODO: what are we using this for?
- @property
- def length(self) -> int:
- return len(self.path)
-
- def pairs(
- self,
- ) -> Iterator[
- Tuple[_InternalEntityType[Any], Union[str, MapperProperty[Any]]]
- ]:
- odd_path = cast(_OddPathRepresentation, self.path)
- even_path = cast(_EvenPathRepresentation, odd_path)
- for i in range(0, len(odd_path), 2):
- yield odd_path[i], even_path[i + 1]
-
- def contains_mapper(self, mapper: Mapper[Any]) -> bool:
- _m_path = cast(_OddPathRepresentation, self.path)
- for path_mapper in [_m_path[i] for i in range(0, len(_m_path), 2)]:
- if path_mapper.mapper.isa(mapper):
- return True
- else:
- return False
-
- def contains(self, attributes: Dict[Any, Any], key: Any) -> bool:
- return (key, self.path) in attributes
-
- def __reduce__(self) -> Any:
- return _unreduce_path, (self.serialize(),)
-
- @classmethod
- def _serialize_path(cls, path: _PathRepresentation) -> _SerializedPath:
- _m_path = cast(_OddPathRepresentation, path)
- _p_path = cast(_EvenPathRepresentation, path)
-
- return list(
- zip(
- tuple(
- m.class_ if (m.is_mapper or m.is_aliased_class) else str(m)
- for m in [_m_path[i] for i in range(0, len(_m_path), 2)]
- ),
- tuple(
- p.key if insp_is_mapper_property(p) else str(p)
- for p in [_p_path[i] for i in range(1, len(_p_path), 2)]
- )
- + (None,),
- )
- )
-
- @classmethod
- def _deserialize_path(cls, path: _SerializedPath) -> _PathRepresentation:
- def _deserialize_mapper_token(mcls: Any) -> Any:
- return (
- # note: we likely dont want configure=True here however
- # this is maintained at the moment for backwards compatibility
- orm_base._inspect_mapped_class(mcls, configure=True)
- if mcls not in PathToken._intern
- else PathToken._intern[mcls]
- )
-
- def _deserialize_key_token(mcls: Any, key: Any) -> Any:
- if key is None:
- return None
- elif key in PathToken._intern:
- return PathToken._intern[key]
- else:
- mp = orm_base._inspect_mapped_class(mcls, configure=True)
- assert mp is not None
- return mp.attrs[key]
-
- p = tuple(
- chain(
- *[
- (
- _deserialize_mapper_token(mcls),
- _deserialize_key_token(mcls, key),
- )
- for mcls, key in path
- ]
- )
- )
- if p and p[-1] is None:
- p = p[0:-1]
- return p
-
- def serialize(self) -> _SerializedPath:
- path = self.path
- return self._serialize_path(path)
-
- @classmethod
- def deserialize(cls, path: _SerializedPath) -> PathRegistry:
- assert path is not None
- p = cls._deserialize_path(path)
- return cls.coerce(p)
-
- @overload
- @classmethod
- def per_mapper(cls, mapper: Mapper[Any]) -> CachingEntityRegistry: ...
-
- @overload
- @classmethod
- def per_mapper(cls, mapper: AliasedInsp[Any]) -> SlotsEntityRegistry: ...
-
- @classmethod
- def per_mapper(
- cls, mapper: _InternalEntityType[Any]
- ) -> AbstractEntityRegistry:
- if mapper.is_mapper:
- return CachingEntityRegistry(cls.root, mapper)
- else:
- return SlotsEntityRegistry(cls.root, mapper)
-
- @classmethod
- def coerce(cls, raw: _PathRepresentation) -> PathRegistry:
- def _red(prev: PathRegistry, next_: _PathElementType) -> PathRegistry:
- return prev[next_]
-
- # can't quite get mypy to appreciate this one :)
- return reduce(_red, raw, cls.root) # type: ignore
-
- def __add__(self, other: PathRegistry) -> PathRegistry:
- def _red(prev: PathRegistry, next_: _PathElementType) -> PathRegistry:
- return prev[next_]
-
- return reduce(_red, other.path, self)
-
- def __str__(self) -> str:
- return f"ORM Path[{' -> '.join(str(elem) for elem in self.path)}]"
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self.path!r})"
-
-
-class CreatesToken(PathRegistry):
- __slots__ = ()
-
- is_aliased_class: bool
- is_root: bool
-
- def token(self, token: _StrPathToken) -> TokenRegistry:
- if token.endswith(f":{_WILDCARD_TOKEN}"):
- return TokenRegistry(self, token)
- elif token.endswith(f":{_DEFAULT_TOKEN}"):
- return TokenRegistry(self.root, token)
- else:
- raise exc.ArgumentError(f"invalid token: {token}")
-
-
-class RootRegistry(CreatesToken):
- """Root registry, defers to mappers so that
- paths are maintained per-root-mapper.
-
- """
-
- __slots__ = ()
-
- inherit_cache = True
-
- path = natural_path = ()
- has_entity = False
- is_aliased_class = False
- is_root = True
- is_unnatural = False
-
- def _getitem(
- self, entity: Any
- ) -> Union[TokenRegistry, AbstractEntityRegistry]:
- if entity in PathToken._intern:
- if TYPE_CHECKING:
- assert isinstance(entity, _StrPathToken)
- return TokenRegistry(self, PathToken._intern[entity])
- else:
- try:
- return entity._path_registry # type: ignore
- except AttributeError:
- raise IndexError(
- f"invalid argument for RootRegistry.__getitem__: {entity}"
- )
-
- def _truncate_recursive(self) -> RootRegistry:
- return self
-
- if not TYPE_CHECKING:
- __getitem__ = _getitem
-
-
-PathRegistry.root = RootRegistry()
-
-
-class PathToken(orm_base.InspectionAttr, HasCacheKey, str):
- """cacheable string token"""
-
- _intern: Dict[str, PathToken] = {}
-
- def _gen_cache_key(
- self, anon_map: anon_map, bindparams: List[BindParameter[Any]]
- ) -> Tuple[Any, ...]:
- return (str(self),)
-
- @property
- def _path_for_compare(self) -> Optional[_PathRepresentation]:
- return None
-
- @classmethod
- def intern(cls, strvalue: str) -> PathToken:
- if strvalue in cls._intern:
- return cls._intern[strvalue]
- else:
- cls._intern[strvalue] = result = PathToken(strvalue)
- return result
-
-
-class TokenRegistry(PathRegistry):
- __slots__ = ("token", "parent", "path", "natural_path")
-
- inherit_cache = True
-
- token: _StrPathToken
- parent: CreatesToken
-
- def __init__(self, parent: CreatesToken, token: _StrPathToken):
- token = PathToken.intern(token)
-
- self.token = token
- self.parent = parent
- self.path = parent.path + (token,)
- self.natural_path = parent.natural_path + (token,)
-
- has_entity = False
-
- is_token = True
-
- def generate_for_superclasses(self) -> Iterator[PathRegistry]:
- # NOTE: this method is no longer used. consider removal
- parent = self.parent
- if is_root(parent):
- yield self
- return
-
- if TYPE_CHECKING:
- assert isinstance(parent, AbstractEntityRegistry)
- if not parent.is_aliased_class:
- for mp_ent in parent.mapper.iterate_to_root():
- yield TokenRegistry(parent.parent[mp_ent], self.token)
- elif (
- parent.is_aliased_class
- and cast(
- "AliasedInsp[Any]",
- parent.entity,
- )._is_with_polymorphic
- ):
- yield self
- for ent in cast(
- "AliasedInsp[Any]", parent.entity
- )._with_polymorphic_entities:
- yield TokenRegistry(parent.parent[ent], self.token)
- else:
- yield self
-
- def _generate_natural_for_superclasses(
- self,
- ) -> Iterator[_PathRepresentation]:
- parent = self.parent
- if is_root(parent):
- yield self.natural_path
- return
-
- if TYPE_CHECKING:
- assert isinstance(parent, AbstractEntityRegistry)
- for mp_ent in parent.mapper.iterate_to_root():
- yield TokenRegistry(parent.parent[mp_ent], self.token).natural_path
- if (
- parent.is_aliased_class
- and cast(
- "AliasedInsp[Any]",
- parent.entity,
- )._is_with_polymorphic
- ):
- yield self.natural_path
- for ent in cast(
- "AliasedInsp[Any]", parent.entity
- )._with_polymorphic_entities:
- yield (
- TokenRegistry(parent.parent[ent], self.token).natural_path
- )
- else:
- yield self.natural_path
-
- def _getitem(self, entity: Any) -> Any:
- try:
- return self.path[entity]
- except TypeError as err:
- raise IndexError(f"{entity}") from err
-
- if not TYPE_CHECKING:
- __getitem__ = _getitem
-
-
-class PropRegistry(PathRegistry):
- __slots__ = (
- "prop",
- "parent",
- "path",
- "natural_path",
- "has_entity",
- "entity",
- "mapper",
- "_wildcard_path_loader_key",
- "_default_path_loader_key",
- "_loader_key",
- "is_unnatural",
- )
- inherit_cache = True
- is_property = True
-
- prop: MapperProperty[Any]
- mapper: Optional[Mapper[Any]]
- entity: Optional[_InternalEntityType[Any]]
-
- def __init__(
- self, parent: AbstractEntityRegistry, prop: MapperProperty[Any]
- ):
- # restate this path in terms of the
- # given MapperProperty's parent.
- insp = cast("_InternalEntityType[Any]", parent[-1])
- natural_parent: AbstractEntityRegistry = parent
-
- # inherit "is_unnatural" from the parent
- self.is_unnatural = parent.parent.is_unnatural or bool(
- parent.mapper.inherits
- )
-
- if not insp.is_aliased_class or insp._use_mapper_path: # type: ignore
- parent = natural_parent = parent.parent[prop.parent]
- elif (
- insp.is_aliased_class
- and insp.with_polymorphic_mappers
- and prop.parent in insp.with_polymorphic_mappers
- ):
- subclass_entity: _InternalEntityType[Any] = parent[-1]._entity_for_mapper(prop.parent) # type: ignore # noqa: E501
- parent = parent.parent[subclass_entity]
-
- # when building a path where with_polymorphic() is in use,
- # special logic to determine the "natural path" when subclass
- # entities are used.
- #
- # here we are trying to distinguish between a path that starts
- # on a the with_polymorhpic entity vs. one that starts on a
- # normal entity that introduces a with_polymorphic() in the
- # middle using of_type():
- #
- # # as in test_polymorphic_rel->
- # # test_subqueryload_on_subclass_uses_path_correctly
- # wp = with_polymorphic(RegularEntity, "*")
- # sess.query(wp).options(someload(wp.SomeSubEntity.foos))
- #
- # vs
- #
- # # as in test_relationship->JoinedloadWPolyOfTypeContinued
- # wp = with_polymorphic(SomeFoo, "*")
- # sess.query(RegularEntity).options(
- # someload(RegularEntity.foos.of_type(wp))
- # .someload(wp.SubFoo.bar)
- # )
- #
- # in the former case, the Query as it generates a path that we
- # want to match will be in terms of the with_polymorphic at the
- # beginning. in the latter case, Query will generate simple
- # paths that don't know about this with_polymorphic, so we must
- # use a separate natural path.
- #
- #
- if parent.parent:
- natural_parent = parent.parent[subclass_entity.mapper]
- self.is_unnatural = True
- else:
- natural_parent = parent
- elif (
- natural_parent.parent
- and insp.is_aliased_class
- and prop.parent # this should always be the case here
- is not insp.mapper
- and insp.mapper.isa(prop.parent)
- ):
- natural_parent = parent.parent[prop.parent]
-
- self.prop = prop
- self.parent = parent
- self.path = parent.path + (prop,)
- self.natural_path = natural_parent.natural_path + (prop,)
-
- self.has_entity = prop._links_to_entity
- if prop._is_relationship:
- if TYPE_CHECKING:
- assert isinstance(prop, RelationshipProperty)
- self.entity = prop.entity
- self.mapper = prop.mapper
- else:
- self.entity = None
- self.mapper = None
-
- self._wildcard_path_loader_key = (
- "loader",
- parent.natural_path + self.prop._wildcard_token,
- )
- self._default_path_loader_key = self.prop._default_path_loader_key
- self._loader_key = ("loader", self.natural_path)
-
- def _truncate_recursive(self) -> PropRegistry:
- earliest = None
- for i, token in enumerate(reversed(self.path[:-1])):
- if token is self.prop:
- earliest = i
-
- if earliest is None:
- return self
- else:
- return self.coerce(self.path[0 : -(earliest + 1)]) # type: ignore
-
- @property
- def entity_path(self) -> AbstractEntityRegistry:
- assert self.entity is not None
- return self[self.entity]
-
- def _getitem(
- self, entity: Union[int, slice, _InternalEntityType[Any]]
- ) -> Union[AbstractEntityRegistry, _PathElementType, _PathRepresentation]:
- if isinstance(entity, (int, slice)):
- return self.path[entity]
- else:
- return SlotsEntityRegistry(self, entity)
-
- if not TYPE_CHECKING:
- __getitem__ = _getitem
-
-
-class AbstractEntityRegistry(CreatesToken):
- __slots__ = (
- "key",
- "parent",
- "is_aliased_class",
- "path",
- "entity",
- "natural_path",
- )
-
- has_entity = True
- is_entity = True
-
- parent: Union[RootRegistry, PropRegistry]
- key: _InternalEntityType[Any]
- entity: _InternalEntityType[Any]
- is_aliased_class: bool
-
- def __init__(
- self,
- parent: Union[RootRegistry, PropRegistry],
- entity: _InternalEntityType[Any],
- ):
- self.key = entity
- self.parent = parent
- self.is_aliased_class = entity.is_aliased_class
- self.entity = entity
- self.path = parent.path + (entity,)
-
- # the "natural path" is the path that we get when Query is traversing
- # from the lead entities into the various relationships; it corresponds
- # to the structure of mappers and relationships. when we are given a
- # path that comes from loader options, as of 1.3 it can have ac-hoc
- # with_polymorphic() and other AliasedInsp objects inside of it, which
- # are usually not present in mappings. So here we track both the
- # "enhanced" path in self.path and the "natural" path that doesn't
- # include those objects so these two traversals can be matched up.
-
- # the test here for "(self.is_aliased_class or parent.is_unnatural)"
- # are to avoid the more expensive conditional logic that follows if we
- # know we don't have to do it. This conditional can just as well be
- # "if parent.path:", it just is more function calls.
- #
- # This is basically the only place that the "is_unnatural" flag
- # actually changes behavior.
- if parent.path and (self.is_aliased_class or parent.is_unnatural):
- # this is an infrequent code path used only for loader strategies
- # that also make use of of_type().
- if entity.mapper.isa(parent.natural_path[-1].mapper): # type: ignore # noqa: E501
- self.natural_path = parent.natural_path + (entity.mapper,)
- else:
- self.natural_path = parent.natural_path + (
- parent.natural_path[-1].entity, # type: ignore
- )
- # it seems to make sense that since these paths get mixed up
- # with statements that are cached or not, we should make
- # sure the natural path is cacheable across different occurrences
- # of equivalent AliasedClass objects. however, so far this
- # does not seem to be needed for whatever reason.
- # elif not parent.path and self.is_aliased_class:
- # self.natural_path = (self.entity._generate_cache_key()[0], )
- else:
- self.natural_path = self.path
-
- def _truncate_recursive(self) -> AbstractEntityRegistry:
- return self.parent._truncate_recursive()[self.entity]
-
- @property
- def root_entity(self) -> _InternalEntityType[Any]:
- return self.odd_element(0)
-
- @property
- def entity_path(self) -> PathRegistry:
- return self
-
- @property
- def mapper(self) -> Mapper[Any]:
- return self.entity.mapper
-
- def __bool__(self) -> bool:
- return True
-
- def _getitem(
- self, entity: Any
- ) -> Union[_PathElementType, _PathRepresentation, PathRegistry]:
- if isinstance(entity, (int, slice)):
- return self.path[entity]
- elif entity in PathToken._intern:
- return TokenRegistry(self, PathToken._intern[entity])
- else:
- return PropRegistry(self, entity)
-
- if not TYPE_CHECKING:
- __getitem__ = _getitem
-
-
-class SlotsEntityRegistry(AbstractEntityRegistry):
- # for aliased class, return lightweight, no-cycles created
- # version
- inherit_cache = True
-
-
-class _ERDict(Dict[Any, Any]):
- def __init__(self, registry: CachingEntityRegistry):
- self.registry = registry
-
- def __missing__(self, key: Any) -> PropRegistry:
- self[key] = item = PropRegistry(self.registry, key)
-
- return item
-
-
-class CachingEntityRegistry(AbstractEntityRegistry):
- # for long lived mapper, return dict based caching
- # version that creates reference cycles
-
- __slots__ = ("_cache",)
-
- inherit_cache = True
-
- def __init__(
- self,
- parent: Union[RootRegistry, PropRegistry],
- entity: _InternalEntityType[Any],
- ):
- super().__init__(parent, entity)
- self._cache = _ERDict(self)
-
- def pop(self, key: Any, default: Any) -> Any:
- return self._cache.pop(key, default)
-
- def _getitem(self, entity: Any) -> Any:
- if isinstance(entity, (int, slice)):
- return self.path[entity]
- elif isinstance(entity, PathToken):
- return TokenRegistry(self, entity)
- else:
- return self._cache[entity]
-
- if not TYPE_CHECKING:
- __getitem__ = _getitem
-
-
-if TYPE_CHECKING:
-
- def path_is_entity(
- path: PathRegistry,
- ) -> TypeGuard[AbstractEntityRegistry]: ...
-
- def path_is_property(path: PathRegistry) -> TypeGuard[PropRegistry]: ...
-
-else:
- path_is_entity = operator.attrgetter("is_entity")
- path_is_property = operator.attrgetter("is_property")
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/persistence.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/persistence.py
deleted file mode 100644
index 369fc59..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/persistence.py
+++ /dev/null
@@ -1,1782 +0,0 @@
-# orm/persistence.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
-# mypy: ignore-errors
-
-
-"""private module containing functions used to emit INSERT, UPDATE
-and DELETE statements on behalf of a :class:`_orm.Mapper` and its descending
-mappers.
-
-The functions here are called only by the unit of work functions
-in unitofwork.py.
-
-"""
-from __future__ import annotations
-
-from itertools import chain
-from itertools import groupby
-from itertools import zip_longest
-import operator
-
-from . import attributes
-from . import exc as orm_exc
-from . import loading
-from . import sync
-from .base import state_str
-from .. import exc as sa_exc
-from .. import future
-from .. import sql
-from .. import util
-from ..engine import cursor as _cursor
-from ..sql import operators
-from ..sql.elements import BooleanClauseList
-from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
-
-
-def save_obj(base_mapper, states, uowtransaction, single=False):
- """Issue ``INSERT`` and/or ``UPDATE`` statements for a list
- of objects.
-
- This is called within the context of a UOWTransaction during a
- flush operation, given a list of states to be flushed. The
- base mapper in an inheritance hierarchy handles the inserts/
- updates for all descendant mappers.
-
- """
-
- # if batch=false, call _save_obj separately for each object
- if not single and not base_mapper.batch:
- for state in _sort_states(base_mapper, states):
- save_obj(base_mapper, [state], uowtransaction, single=True)
- return
-
- states_to_update = []
- states_to_insert = []
-
- for (
- state,
- dict_,
- mapper,
- connection,
- has_identity,
- row_switch,
- update_version_id,
- ) in _organize_states_for_save(base_mapper, states, uowtransaction):
- if has_identity or row_switch:
- states_to_update.append(
- (state, dict_, mapper, connection, update_version_id)
- )
- else:
- states_to_insert.append((state, dict_, mapper, connection))
-
- for table, mapper in base_mapper._sorted_tables.items():
- if table not in mapper._pks_by_table:
- continue
- insert = _collect_insert_commands(table, states_to_insert)
-
- update = _collect_update_commands(
- uowtransaction, table, states_to_update
- )
-
- _emit_update_statements(
- base_mapper,
- uowtransaction,
- mapper,
- table,
- update,
- )
-
- _emit_insert_statements(
- base_mapper,
- uowtransaction,
- mapper,
- table,
- insert,
- )
-
- _finalize_insert_update_commands(
- base_mapper,
- uowtransaction,
- chain(
- (
- (state, state_dict, mapper, connection, False)
- for (state, state_dict, mapper, connection) in states_to_insert
- ),
- (
- (state, state_dict, mapper, connection, True)
- for (
- state,
- state_dict,
- mapper,
- connection,
- update_version_id,
- ) in states_to_update
- ),
- ),
- )
-
-
-def post_update(base_mapper, states, uowtransaction, post_update_cols):
- """Issue UPDATE statements on behalf of a relationship() which
- specifies post_update.
-
- """
-
- states_to_update = list(
- _organize_states_for_post_update(base_mapper, states, uowtransaction)
- )
-
- for table, mapper in base_mapper._sorted_tables.items():
- if table not in mapper._pks_by_table:
- continue
-
- update = (
- (
- state,
- state_dict,
- sub_mapper,
- connection,
- (
- mapper._get_committed_state_attr_by_column(
- state, state_dict, mapper.version_id_col
- )
- if mapper.version_id_col is not None
- else None
- ),
- )
- for state, state_dict, sub_mapper, connection in states_to_update
- if table in sub_mapper._pks_by_table
- )
-
- update = _collect_post_update_commands(
- base_mapper, uowtransaction, table, update, post_update_cols
- )
-
- _emit_post_update_statements(
- base_mapper,
- uowtransaction,
- mapper,
- table,
- update,
- )
-
-
-def delete_obj(base_mapper, states, uowtransaction):
- """Issue ``DELETE`` statements for a list of objects.
-
- This is called within the context of a UOWTransaction during a
- flush operation.
-
- """
-
- states_to_delete = list(
- _organize_states_for_delete(base_mapper, states, uowtransaction)
- )
-
- table_to_mapper = base_mapper._sorted_tables
-
- for table in reversed(list(table_to_mapper.keys())):
- mapper = table_to_mapper[table]
- if table not in mapper._pks_by_table:
- continue
- elif mapper.inherits and mapper.passive_deletes:
- continue
-
- delete = _collect_delete_commands(
- base_mapper, uowtransaction, table, states_to_delete
- )
-
- _emit_delete_statements(
- base_mapper,
- uowtransaction,
- mapper,
- table,
- delete,
- )
-
- for (
- state,
- state_dict,
- mapper,
- connection,
- update_version_id,
- ) in states_to_delete:
- mapper.dispatch.after_delete(mapper, connection, state)
-
-
-def _organize_states_for_save(base_mapper, states, uowtransaction):
- """Make an initial pass across a set of states for INSERT or
- UPDATE.
-
- This includes splitting out into distinct lists for
- each, calling before_insert/before_update, obtaining
- key information for each state including its dictionary,
- mapper, the connection to use for the execution per state,
- and the identity flag.
-
- """
-
- for state, dict_, mapper, connection in _connections_for_states(
- base_mapper, uowtransaction, states
- ):
- has_identity = bool(state.key)
-
- instance_key = state.key or mapper._identity_key_from_state(state)
-
- row_switch = update_version_id = None
-
- # call before_XXX extensions
- if not has_identity:
- mapper.dispatch.before_insert(mapper, connection, state)
- else:
- mapper.dispatch.before_update(mapper, connection, state)
-
- if mapper._validate_polymorphic_identity:
- mapper._validate_polymorphic_identity(mapper, state, dict_)
-
- # detect if we have a "pending" instance (i.e. has
- # no instance_key attached to it), and another instance
- # with the same identity key already exists as persistent.
- # convert to an UPDATE if so.
- if (
- not has_identity
- and instance_key in uowtransaction.session.identity_map
- ):
- instance = uowtransaction.session.identity_map[instance_key]
- existing = attributes.instance_state(instance)
-
- if not uowtransaction.was_already_deleted(existing):
- if not uowtransaction.is_deleted(existing):
- util.warn(
- "New instance %s with identity key %s conflicts "
- "with persistent instance %s"
- % (state_str(state), instance_key, state_str(existing))
- )
- else:
- base_mapper._log_debug(
- "detected row switch for identity %s. "
- "will update %s, remove %s from "
- "transaction",
- instance_key,
- state_str(state),
- state_str(existing),
- )
-
- # remove the "delete" flag from the existing element
- uowtransaction.remove_state_actions(existing)
- row_switch = existing
-
- if (has_identity or row_switch) and mapper.version_id_col is not None:
- update_version_id = mapper._get_committed_state_attr_by_column(
- row_switch if row_switch else state,
- row_switch.dict if row_switch else dict_,
- mapper.version_id_col,
- )
-
- yield (
- state,
- dict_,
- mapper,
- connection,
- has_identity,
- row_switch,
- update_version_id,
- )
-
-
-def _organize_states_for_post_update(base_mapper, states, uowtransaction):
- """Make an initial pass across a set of states for UPDATE
- corresponding to post_update.
-
- This includes obtaining key information for each state
- including its dictionary, mapper, the connection to use for
- the execution per state.
-
- """
- return _connections_for_states(base_mapper, uowtransaction, states)
-
-
-def _organize_states_for_delete(base_mapper, states, uowtransaction):
- """Make an initial pass across a set of states for DELETE.
-
- This includes calling out before_delete and obtaining
- key information for each state including its dictionary,
- mapper, the connection to use for the execution per state.
-
- """
- for state, dict_, mapper, connection in _connections_for_states(
- base_mapper, uowtransaction, states
- ):
- mapper.dispatch.before_delete(mapper, connection, state)
-
- if mapper.version_id_col is not None:
- update_version_id = mapper._get_committed_state_attr_by_column(
- state, dict_, mapper.version_id_col
- )
- else:
- update_version_id = None
-
- yield (state, dict_, mapper, connection, update_version_id)
-
-
-def _collect_insert_commands(
- table,
- states_to_insert,
- *,
- bulk=False,
- return_defaults=False,
- render_nulls=False,
- include_bulk_keys=(),
-):
- """Identify sets of values to use in INSERT statements for a
- list of states.
-
- """
- for state, state_dict, mapper, connection in states_to_insert:
- if table not in mapper._pks_by_table:
- continue
-
- params = {}
- value_params = {}
-
- propkey_to_col = mapper._propkey_to_col[table]
-
- eval_none = mapper._insert_cols_evaluating_none[table]
-
- for propkey in set(propkey_to_col).intersection(state_dict):
- value = state_dict[propkey]
- col = propkey_to_col[propkey]
- if value is None and col not in eval_none and not render_nulls:
- continue
- elif not bulk and (
- hasattr(value, "__clause_element__")
- or isinstance(value, sql.ClauseElement)
- ):
- value_params[col] = (
- value.__clause_element__()
- if hasattr(value, "__clause_element__")
- else value
- )
- else:
- params[col.key] = value
-
- if not bulk:
- # for all the columns that have no default and we don't have
- # a value and where "None" is not a special value, add
- # explicit None to the INSERT. This is a legacy behavior
- # which might be worth removing, as it should not be necessary
- # and also produces confusion, given that "missing" and None
- # now have distinct meanings
- for colkey in (
- mapper._insert_cols_as_none[table]
- .difference(params)
- .difference([c.key for c in value_params])
- ):
- params[colkey] = None
-
- if not bulk or return_defaults:
- # params are in terms of Column key objects, so
- # compare to pk_keys_by_table
- has_all_pks = mapper._pk_keys_by_table[table].issubset(params)
-
- if mapper.base_mapper._prefer_eager_defaults(
- connection.dialect, table
- ):
- has_all_defaults = mapper._server_default_col_keys[
- table
- ].issubset(params)
- else:
- has_all_defaults = True
- else:
- has_all_defaults = has_all_pks = True
-
- if (
- mapper.version_id_generator is not False
- and mapper.version_id_col is not None
- and mapper.version_id_col in mapper._cols_by_table[table]
- ):
- params[mapper.version_id_col.key] = mapper.version_id_generator(
- None
- )
-
- if bulk:
- if mapper._set_polymorphic_identity:
- params.setdefault(
- mapper._polymorphic_attr_key, mapper.polymorphic_identity
- )
-
- if include_bulk_keys:
- params.update((k, state_dict[k]) for k in include_bulk_keys)
-
- yield (
- state,
- state_dict,
- params,
- mapper,
- connection,
- value_params,
- has_all_pks,
- has_all_defaults,
- )
-
-
-def _collect_update_commands(
- uowtransaction,
- table,
- states_to_update,
- *,
- bulk=False,
- use_orm_update_stmt=None,
- include_bulk_keys=(),
-):
- """Identify sets of values to use in UPDATE statements for a
- list of states.
-
- This function works intricately with the history system
- to determine exactly what values should be updated
- as well as how the row should be matched within an UPDATE
- statement. Includes some tricky scenarios where the primary
- key of an object might have been changed.
-
- """
-
- for (
- state,
- state_dict,
- mapper,
- connection,
- update_version_id,
- ) in states_to_update:
- if table not in mapper._pks_by_table:
- continue
-
- pks = mapper._pks_by_table[table]
-
- if use_orm_update_stmt is not None:
- # TODO: ordered values, etc
- value_params = use_orm_update_stmt._values
- else:
- value_params = {}
-
- propkey_to_col = mapper._propkey_to_col[table]
-
- if bulk:
- # keys here are mapped attribute keys, so
- # look at mapper attribute keys for pk
- params = {
- propkey_to_col[propkey].key: state_dict[propkey]
- for propkey in set(propkey_to_col)
- .intersection(state_dict)
- .difference(mapper._pk_attr_keys_by_table[table])
- }
- has_all_defaults = True
- else:
- params = {}
- for propkey in set(propkey_to_col).intersection(
- state.committed_state
- ):
- value = state_dict[propkey]
- col = propkey_to_col[propkey]
-
- if hasattr(value, "__clause_element__") or isinstance(
- value, sql.ClauseElement
- ):
- value_params[col] = (
- value.__clause_element__()
- if hasattr(value, "__clause_element__")
- else value
- )
- # guard against values that generate non-__nonzero__
- # objects for __eq__()
- elif (
- state.manager[propkey].impl.is_equal(
- value, state.committed_state[propkey]
- )
- is not True
- ):
- params[col.key] = value
-
- if mapper.base_mapper.eager_defaults is True:
- has_all_defaults = (
- mapper._server_onupdate_default_col_keys[table]
- ).issubset(params)
- else:
- has_all_defaults = True
-
- if (
- update_version_id is not None
- and mapper.version_id_col in mapper._cols_by_table[table]
- ):
- if not bulk and not (params or value_params):
- # HACK: check for history in other tables, in case the
- # history is only in a different table than the one
- # where the version_id_col is. This logic was lost
- # from 0.9 -> 1.0.0 and restored in 1.0.6.
- for prop in mapper._columntoproperty.values():
- history = state.manager[prop.key].impl.get_history(
- state, state_dict, attributes.PASSIVE_NO_INITIALIZE
- )
- if history.added:
- break
- else:
- # no net change, break
- continue
-
- col = mapper.version_id_col
- no_params = not params and not value_params
- params[col._label] = update_version_id
-
- if (
- bulk or col.key not in params
- ) and mapper.version_id_generator is not False:
- val = mapper.version_id_generator(update_version_id)
- params[col.key] = val
- elif mapper.version_id_generator is False and no_params:
- # no version id generator, no values set on the table,
- # and version id wasn't manually incremented.
- # set version id to itself so we get an UPDATE
- # statement
- params[col.key] = update_version_id
-
- elif not (params or value_params):
- continue
-
- has_all_pks = True
- expect_pk_cascaded = False
- if bulk:
- # keys here are mapped attribute keys, so
- # look at mapper attribute keys for pk
- pk_params = {
- propkey_to_col[propkey]._label: state_dict.get(propkey)
- for propkey in set(propkey_to_col).intersection(
- mapper._pk_attr_keys_by_table[table]
- )
- }
- if util.NONE_SET.intersection(pk_params.values()):
- raise sa_exc.InvalidRequestError(
- f"No primary key value supplied for column(s) "
- f"""{
- ', '.join(
- str(c) for c in pks if pk_params[c._label] is None
- )
- }; """
- "per-row ORM Bulk UPDATE by Primary Key requires that "
- "records contain primary key values",
- code="bupq",
- )
-
- else:
- pk_params = {}
- for col in pks:
- propkey = mapper._columntoproperty[col].key
-
- history = state.manager[propkey].impl.get_history(
- state, state_dict, attributes.PASSIVE_OFF
- )
-
- if history.added:
- if (
- not history.deleted
- or ("pk_cascaded", state, col)
- in uowtransaction.attributes
- ):
- expect_pk_cascaded = True
- pk_params[col._label] = history.added[0]
- params.pop(col.key, None)
- else:
- # else, use the old value to locate the row
- pk_params[col._label] = history.deleted[0]
- if col in value_params:
- has_all_pks = False
- else:
- pk_params[col._label] = history.unchanged[0]
- if pk_params[col._label] is None:
- raise orm_exc.FlushError(
- "Can't update table %s using NULL for primary "
- "key value on column %s" % (table, col)
- )
-
- if include_bulk_keys:
- params.update((k, state_dict[k]) for k in include_bulk_keys)
-
- if params or value_params:
- params.update(pk_params)
- yield (
- state,
- state_dict,
- params,
- mapper,
- connection,
- value_params,
- has_all_defaults,
- has_all_pks,
- )
- elif expect_pk_cascaded:
- # no UPDATE occurs on this table, but we expect that CASCADE rules
- # have changed the primary key of the row; propagate this event to
- # other columns that expect to have been modified. this normally
- # occurs after the UPDATE is emitted however we invoke it here
- # explicitly in the absence of our invoking an UPDATE
- for m, equated_pairs in mapper._table_to_equated[table]:
- sync.populate(
- state,
- m,
- state,
- m,
- equated_pairs,
- uowtransaction,
- mapper.passive_updates,
- )
-
-
-def _collect_post_update_commands(
- base_mapper, uowtransaction, table, states_to_update, post_update_cols
-):
- """Identify sets of values to use in UPDATE statements for a
- list of states within a post_update operation.
-
- """
-
- for (
- state,
- state_dict,
- mapper,
- connection,
- update_version_id,
- ) in states_to_update:
- # assert table in mapper._pks_by_table
-
- pks = mapper._pks_by_table[table]
- params = {}
- hasdata = False
-
- for col in mapper._cols_by_table[table]:
- if col in pks:
- params[col._label] = mapper._get_state_attr_by_column(
- state, state_dict, col, passive=attributes.PASSIVE_OFF
- )
-
- elif col in post_update_cols or col.onupdate is not None:
- prop = mapper._columntoproperty[col]
- history = state.manager[prop.key].impl.get_history(
- state, state_dict, attributes.PASSIVE_NO_INITIALIZE
- )
- if history.added:
- value = history.added[0]
- params[col.key] = value
- hasdata = True
- if hasdata:
- if (
- update_version_id is not None
- and mapper.version_id_col in mapper._cols_by_table[table]
- ):
- col = mapper.version_id_col
- params[col._label] = update_version_id
-
- if (
- bool(state.key)
- and col.key not in params
- and mapper.version_id_generator is not False
- ):
- val = mapper.version_id_generator(update_version_id)
- params[col.key] = val
- yield state, state_dict, mapper, connection, params
-
-
-def _collect_delete_commands(
- base_mapper, uowtransaction, table, states_to_delete
-):
- """Identify values to use in DELETE statements for a list of
- states to be deleted."""
-
- for (
- state,
- state_dict,
- mapper,
- connection,
- update_version_id,
- ) in states_to_delete:
- if table not in mapper._pks_by_table:
- continue
-
- params = {}
- for col in mapper._pks_by_table[table]:
- params[col.key] = value = (
- mapper._get_committed_state_attr_by_column(
- state, state_dict, col
- )
- )
- if value is None:
- raise orm_exc.FlushError(
- "Can't delete from table %s "
- "using NULL for primary "
- "key value on column %s" % (table, col)
- )
-
- if (
- update_version_id is not None
- and mapper.version_id_col in mapper._cols_by_table[table]
- ):
- params[mapper.version_id_col.key] = update_version_id
- yield params, connection
-
-
-def _emit_update_statements(
- base_mapper,
- uowtransaction,
- mapper,
- table,
- update,
- *,
- bookkeeping=True,
- use_orm_update_stmt=None,
- enable_check_rowcount=True,
-):
- """Emit UPDATE statements corresponding to value lists collected
- by _collect_update_commands()."""
-
- needs_version_id = (
- mapper.version_id_col is not None
- and mapper.version_id_col in mapper._cols_by_table[table]
- )
-
- execution_options = {"compiled_cache": base_mapper._compiled_cache}
-
- def update_stmt(existing_stmt=None):
- clauses = BooleanClauseList._construct_raw(operators.and_)
-
- for col in mapper._pks_by_table[table]:
- clauses._append_inplace(
- col == sql.bindparam(col._label, type_=col.type)
- )
-
- if needs_version_id:
- clauses._append_inplace(
- mapper.version_id_col
- == sql.bindparam(
- mapper.version_id_col._label,
- type_=mapper.version_id_col.type,
- )
- )
-
- if existing_stmt is not None:
- stmt = existing_stmt.where(clauses)
- else:
- stmt = table.update().where(clauses)
- return stmt
-
- if use_orm_update_stmt is not None:
- cached_stmt = update_stmt(use_orm_update_stmt)
-
- else:
- cached_stmt = base_mapper._memo(("update", table), update_stmt)
-
- for (
- (connection, paramkeys, hasvalue, has_all_defaults, has_all_pks),
- records,
- ) in groupby(
- update,
- lambda rec: (
- rec[4], # connection
- set(rec[2]), # set of parameter keys
- bool(rec[5]), # whether or not we have "value" parameters
- rec[6], # has_all_defaults
- rec[7], # has all pks
- ),
- ):
- rows = 0
- records = list(records)
-
- statement = cached_stmt
-
- if use_orm_update_stmt is not None:
- statement = statement._annotate(
- {
- "_emit_update_table": table,
- "_emit_update_mapper": mapper,
- }
- )
-
- return_defaults = False
-
- if not has_all_pks:
- statement = statement.return_defaults(*mapper._pks_by_table[table])
- return_defaults = True
-
- if (
- bookkeeping
- and not has_all_defaults
- and mapper.base_mapper.eager_defaults is True
- # change as of #8889 - if RETURNING is not going to be used anyway,
- # (applies to MySQL, MariaDB which lack UPDATE RETURNING) ensure
- # we can do an executemany UPDATE which is more efficient
- and table.implicit_returning
- and connection.dialect.update_returning
- ):
- statement = statement.return_defaults(
- *mapper._server_onupdate_default_cols[table]
- )
- return_defaults = True
-
- if mapper._version_id_has_server_side_value:
- statement = statement.return_defaults(mapper.version_id_col)
- return_defaults = True
-
- assert_singlerow = connection.dialect.supports_sane_rowcount
-
- assert_multirow = (
- assert_singlerow
- and connection.dialect.supports_sane_multi_rowcount
- )
-
- # change as of #8889 - if RETURNING is not going to be used anyway,
- # (applies to MySQL, MariaDB which lack UPDATE RETURNING) ensure
- # we can do an executemany UPDATE which is more efficient
- allow_executemany = not return_defaults and not needs_version_id
-
- if hasvalue:
- for (
- state,
- state_dict,
- params,
- mapper,
- connection,
- value_params,
- has_all_defaults,
- has_all_pks,
- ) in records:
- c = connection.execute(
- statement.values(value_params),
- params,
- execution_options=execution_options,
- )
- if bookkeeping:
- _postfetch(
- mapper,
- uowtransaction,
- table,
- state,
- state_dict,
- c,
- c.context.compiled_parameters[0],
- value_params,
- True,
- c.returned_defaults,
- )
- rows += c.rowcount
- check_rowcount = enable_check_rowcount and assert_singlerow
- else:
- if not allow_executemany:
- check_rowcount = enable_check_rowcount and assert_singlerow
- for (
- state,
- state_dict,
- params,
- mapper,
- connection,
- value_params,
- has_all_defaults,
- has_all_pks,
- ) in records:
- c = connection.execute(
- statement, params, execution_options=execution_options
- )
-
- # TODO: why with bookkeeping=False?
- if bookkeeping:
- _postfetch(
- mapper,
- uowtransaction,
- table,
- state,
- state_dict,
- c,
- c.context.compiled_parameters[0],
- value_params,
- True,
- c.returned_defaults,
- )
- rows += c.rowcount
- else:
- multiparams = [rec[2] for rec in records]
-
- check_rowcount = enable_check_rowcount and (
- assert_multirow
- or (assert_singlerow and len(multiparams) == 1)
- )
-
- c = connection.execute(
- statement, multiparams, execution_options=execution_options
- )
-
- rows += c.rowcount
-
- for (
- state,
- state_dict,
- params,
- mapper,
- connection,
- value_params,
- has_all_defaults,
- has_all_pks,
- ) in records:
- if bookkeeping:
- _postfetch(
- mapper,
- uowtransaction,
- table,
- state,
- state_dict,
- c,
- c.context.compiled_parameters[0],
- value_params,
- True,
- (
- c.returned_defaults
- if not c.context.executemany
- else None
- ),
- )
-
- if check_rowcount:
- if rows != len(records):
- raise orm_exc.StaleDataError(
- "UPDATE statement on table '%s' expected to "
- "update %d row(s); %d were matched."
- % (table.description, len(records), rows)
- )
-
- elif needs_version_id:
- util.warn(
- "Dialect %s does not support updated rowcount "
- "- versioning cannot be verified."
- % c.dialect.dialect_description
- )
-
-
-def _emit_insert_statements(
- base_mapper,
- uowtransaction,
- mapper,
- table,
- insert,
- *,
- bookkeeping=True,
- use_orm_insert_stmt=None,
- execution_options=None,
-):
- """Emit INSERT statements corresponding to value lists collected
- by _collect_insert_commands()."""
-
- if use_orm_insert_stmt is not None:
- cached_stmt = use_orm_insert_stmt
- exec_opt = util.EMPTY_DICT
-
- # if a user query with RETURNING was passed, we definitely need
- # to use RETURNING.
- returning_is_required_anyway = bool(use_orm_insert_stmt._returning)
- deterministic_results_reqd = (
- returning_is_required_anyway
- and use_orm_insert_stmt._sort_by_parameter_order
- ) or bookkeeping
- else:
- returning_is_required_anyway = False
- deterministic_results_reqd = bookkeeping
- cached_stmt = base_mapper._memo(("insert", table), table.insert)
- exec_opt = {"compiled_cache": base_mapper._compiled_cache}
-
- if execution_options:
- execution_options = util.EMPTY_DICT.merge_with(
- exec_opt, execution_options
- )
- else:
- execution_options = exec_opt
-
- return_result = None
-
- for (
- (connection, _, hasvalue, has_all_pks, has_all_defaults),
- records,
- ) in groupby(
- insert,
- lambda rec: (
- rec[4], # connection
- set(rec[2]), # parameter keys
- bool(rec[5]), # whether we have "value" parameters
- rec[6],
- rec[7],
- ),
- ):
- statement = cached_stmt
-
- if use_orm_insert_stmt is not None:
- statement = statement._annotate(
- {
- "_emit_insert_table": table,
- "_emit_insert_mapper": mapper,
- }
- )
-
- if (
- (
- not bookkeeping
- or (
- has_all_defaults
- or not base_mapper._prefer_eager_defaults(
- connection.dialect, table
- )
- or not table.implicit_returning
- or not connection.dialect.insert_returning
- )
- )
- and not returning_is_required_anyway
- and has_all_pks
- and not hasvalue
- ):
- # the "we don't need newly generated values back" section.
- # here we have all the PKs, all the defaults or we don't want
- # to fetch them, or the dialect doesn't support RETURNING at all
- # so we have to post-fetch / use lastrowid anyway.
- records = list(records)
- multiparams = [rec[2] for rec in records]
-
- result = connection.execute(
- statement, multiparams, execution_options=execution_options
- )
- if bookkeeping:
- for (
- (
- state,
- state_dict,
- params,
- mapper_rec,
- conn,
- value_params,
- has_all_pks,
- has_all_defaults,
- ),
- last_inserted_params,
- ) in zip(records, result.context.compiled_parameters):
- if state:
- _postfetch(
- mapper_rec,
- uowtransaction,
- table,
- state,
- state_dict,
- result,
- last_inserted_params,
- value_params,
- False,
- (
- result.returned_defaults
- if not result.context.executemany
- else None
- ),
- )
- else:
- _postfetch_bulk_save(mapper_rec, state_dict, table)
-
- else:
- # here, we need defaults and/or pk values back or we otherwise
- # know that we are using RETURNING in any case
-
- records = list(records)
-
- if returning_is_required_anyway or (
- table.implicit_returning and not hasvalue and len(records) > 1
- ):
- if (
- deterministic_results_reqd
- and connection.dialect.insert_executemany_returning_sort_by_parameter_order # noqa: E501
- ) or (
- not deterministic_results_reqd
- and connection.dialect.insert_executemany_returning
- ):
- do_executemany = True
- elif returning_is_required_anyway:
- if deterministic_results_reqd:
- dt = " with RETURNING and sort by parameter order"
- else:
- dt = " with RETURNING"
- raise sa_exc.InvalidRequestError(
- f"Can't use explicit RETURNING for bulk INSERT "
- f"operation with "
- f"{connection.dialect.dialect_description} backend; "
- f"executemany{dt} is not enabled for this dialect."
- )
- else:
- do_executemany = False
- else:
- do_executemany = False
-
- if use_orm_insert_stmt is None:
- if (
- not has_all_defaults
- and base_mapper._prefer_eager_defaults(
- connection.dialect, table
- )
- ):
- statement = statement.return_defaults(
- *mapper._server_default_cols[table],
- sort_by_parameter_order=bookkeeping,
- )
-
- if mapper.version_id_col is not None:
- statement = statement.return_defaults(
- mapper.version_id_col,
- sort_by_parameter_order=bookkeeping,
- )
- elif do_executemany:
- statement = statement.return_defaults(
- *table.primary_key, sort_by_parameter_order=bookkeeping
- )
-
- if do_executemany:
- multiparams = [rec[2] for rec in records]
-
- result = connection.execute(
- statement, multiparams, execution_options=execution_options
- )
-
- if use_orm_insert_stmt is not None:
- if return_result is None:
- return_result = result
- else:
- return_result = return_result.splice_vertically(result)
-
- if bookkeeping:
- for (
- (
- state,
- state_dict,
- params,
- mapper_rec,
- conn,
- value_params,
- has_all_pks,
- has_all_defaults,
- ),
- last_inserted_params,
- inserted_primary_key,
- returned_defaults,
- ) in zip_longest(
- records,
- result.context.compiled_parameters,
- result.inserted_primary_key_rows,
- result.returned_defaults_rows or (),
- ):
- if inserted_primary_key is None:
- # this is a real problem and means that we didn't
- # get back as many PK rows. we can't continue
- # since this indicates PK rows were missing, which
- # means we likely mis-populated records starting
- # at that point with incorrectly matched PK
- # values.
- raise orm_exc.FlushError(
- "Multi-row INSERT statement for %s did not "
- "produce "
- "the correct number of INSERTed rows for "
- "RETURNING. Ensure there are no triggers or "
- "special driver issues preventing INSERT from "
- "functioning properly." % mapper_rec
- )
-
- for pk, col in zip(
- inserted_primary_key,
- mapper._pks_by_table[table],
- ):
- prop = mapper_rec._columntoproperty[col]
- if state_dict.get(prop.key) is None:
- state_dict[prop.key] = pk
-
- if state:
- _postfetch(
- mapper_rec,
- uowtransaction,
- table,
- state,
- state_dict,
- result,
- last_inserted_params,
- value_params,
- False,
- returned_defaults,
- )
- else:
- _postfetch_bulk_save(mapper_rec, state_dict, table)
- else:
- assert not returning_is_required_anyway
-
- for (
- state,
- state_dict,
- params,
- mapper_rec,
- connection,
- value_params,
- has_all_pks,
- has_all_defaults,
- ) in records:
- if value_params:
- result = connection.execute(
- statement.values(value_params),
- params,
- execution_options=execution_options,
- )
- else:
- result = connection.execute(
- statement,
- params,
- execution_options=execution_options,
- )
-
- primary_key = result.inserted_primary_key
- if primary_key is None:
- raise orm_exc.FlushError(
- "Single-row INSERT statement for %s "
- "did not produce a "
- "new primary key result "
- "being invoked. Ensure there are no triggers or "
- "special driver issues preventing INSERT from "
- "functioning properly." % (mapper_rec,)
- )
- for pk, col in zip(
- primary_key, mapper._pks_by_table[table]
- ):
- prop = mapper_rec._columntoproperty[col]
- if (
- col in value_params
- or state_dict.get(prop.key) is None
- ):
- state_dict[prop.key] = pk
- if bookkeeping:
- if state:
- _postfetch(
- mapper_rec,
- uowtransaction,
- table,
- state,
- state_dict,
- result,
- result.context.compiled_parameters[0],
- value_params,
- False,
- (
- result.returned_defaults
- if not result.context.executemany
- else None
- ),
- )
- else:
- _postfetch_bulk_save(mapper_rec, state_dict, table)
-
- if use_orm_insert_stmt is not None:
- if return_result is None:
- return _cursor.null_dml_result()
- else:
- return return_result
-
-
-def _emit_post_update_statements(
- base_mapper, uowtransaction, mapper, table, update
-):
- """Emit UPDATE statements corresponding to value lists collected
- by _collect_post_update_commands()."""
-
- execution_options = {"compiled_cache": base_mapper._compiled_cache}
-
- needs_version_id = (
- mapper.version_id_col is not None
- and mapper.version_id_col in mapper._cols_by_table[table]
- )
-
- def update_stmt():
- clauses = BooleanClauseList._construct_raw(operators.and_)
-
- for col in mapper._pks_by_table[table]:
- clauses._append_inplace(
- col == sql.bindparam(col._label, type_=col.type)
- )
-
- if needs_version_id:
- clauses._append_inplace(
- mapper.version_id_col
- == sql.bindparam(
- mapper.version_id_col._label,
- type_=mapper.version_id_col.type,
- )
- )
-
- stmt = table.update().where(clauses)
-
- return stmt
-
- statement = base_mapper._memo(("post_update", table), update_stmt)
-
- if mapper._version_id_has_server_side_value:
- statement = statement.return_defaults(mapper.version_id_col)
-
- # execute each UPDATE in the order according to the original
- # list of states to guarantee row access order, but
- # also group them into common (connection, cols) sets
- # to support executemany().
- for key, records in groupby(
- update,
- lambda rec: (rec[3], set(rec[4])), # connection # parameter keys
- ):
- rows = 0
-
- records = list(records)
- connection = key[0]
-
- assert_singlerow = connection.dialect.supports_sane_rowcount
- assert_multirow = (
- assert_singlerow
- and connection.dialect.supports_sane_multi_rowcount
- )
- allow_executemany = not needs_version_id or assert_multirow
-
- if not allow_executemany:
- check_rowcount = assert_singlerow
- for state, state_dict, mapper_rec, connection, params in records:
- c = connection.execute(
- statement, params, execution_options=execution_options
- )
-
- _postfetch_post_update(
- mapper_rec,
- uowtransaction,
- table,
- state,
- state_dict,
- c,
- c.context.compiled_parameters[0],
- )
- rows += c.rowcount
- else:
- multiparams = [
- params
- for state, state_dict, mapper_rec, conn, params in records
- ]
-
- check_rowcount = assert_multirow or (
- assert_singlerow and len(multiparams) == 1
- )
-
- c = connection.execute(
- statement, multiparams, execution_options=execution_options
- )
-
- rows += c.rowcount
- for state, state_dict, mapper_rec, connection, params in records:
- _postfetch_post_update(
- mapper_rec,
- uowtransaction,
- table,
- state,
- state_dict,
- c,
- c.context.compiled_parameters[0],
- )
-
- if check_rowcount:
- if rows != len(records):
- raise orm_exc.StaleDataError(
- "UPDATE statement on table '%s' expected to "
- "update %d row(s); %d were matched."
- % (table.description, len(records), rows)
- )
-
- elif needs_version_id:
- util.warn(
- "Dialect %s does not support updated rowcount "
- "- versioning cannot be verified."
- % c.dialect.dialect_description
- )
-
-
-def _emit_delete_statements(
- base_mapper, uowtransaction, mapper, table, delete
-):
- """Emit DELETE statements corresponding to value lists collected
- by _collect_delete_commands()."""
-
- need_version_id = (
- mapper.version_id_col is not None
- and mapper.version_id_col in mapper._cols_by_table[table]
- )
-
- def delete_stmt():
- clauses = BooleanClauseList._construct_raw(operators.and_)
-
- for col in mapper._pks_by_table[table]:
- clauses._append_inplace(
- col == sql.bindparam(col.key, type_=col.type)
- )
-
- if need_version_id:
- clauses._append_inplace(
- mapper.version_id_col
- == sql.bindparam(
- mapper.version_id_col.key, type_=mapper.version_id_col.type
- )
- )
-
- return table.delete().where(clauses)
-
- statement = base_mapper._memo(("delete", table), delete_stmt)
- for connection, recs in groupby(delete, lambda rec: rec[1]): # connection
- del_objects = [params for params, connection in recs]
-
- execution_options = {"compiled_cache": base_mapper._compiled_cache}
- expected = len(del_objects)
- rows_matched = -1
- only_warn = False
-
- if (
- need_version_id
- and not connection.dialect.supports_sane_multi_rowcount
- ):
- if connection.dialect.supports_sane_rowcount:
- rows_matched = 0
- # execute deletes individually so that versioned
- # rows can be verified
- for params in del_objects:
- c = connection.execute(
- statement, params, execution_options=execution_options
- )
- rows_matched += c.rowcount
- else:
- util.warn(
- "Dialect %s does not support deleted rowcount "
- "- versioning cannot be verified."
- % connection.dialect.dialect_description
- )
- connection.execute(
- statement, del_objects, execution_options=execution_options
- )
- else:
- c = connection.execute(
- statement, del_objects, execution_options=execution_options
- )
-
- if not need_version_id:
- only_warn = True
-
- rows_matched = c.rowcount
-
- if (
- base_mapper.confirm_deleted_rows
- and rows_matched > -1
- and expected != rows_matched
- and (
- connection.dialect.supports_sane_multi_rowcount
- or len(del_objects) == 1
- )
- ):
- # TODO: why does this "only warn" if versioning is turned off,
- # whereas the UPDATE raises?
- if only_warn:
- util.warn(
- "DELETE statement on table '%s' expected to "
- "delete %d row(s); %d were matched. Please set "
- "confirm_deleted_rows=False within the mapper "
- "configuration to prevent this warning."
- % (table.description, expected, rows_matched)
- )
- else:
- raise orm_exc.StaleDataError(
- "DELETE statement on table '%s' expected to "
- "delete %d row(s); %d were matched. Please set "
- "confirm_deleted_rows=False within the mapper "
- "configuration to prevent this warning."
- % (table.description, expected, rows_matched)
- )
-
-
-def _finalize_insert_update_commands(base_mapper, uowtransaction, states):
- """finalize state on states that have been inserted or updated,
- including calling after_insert/after_update events.
-
- """
- for state, state_dict, mapper, connection, has_identity in states:
- if mapper._readonly_props:
- readonly = state.unmodified_intersection(
- [
- p.key
- for p in mapper._readonly_props
- if (
- p.expire_on_flush
- and (not p.deferred or p.key in state.dict)
- )
- or (
- not p.expire_on_flush
- and not p.deferred
- and p.key not in state.dict
- )
- ]
- )
- if readonly:
- state._expire_attributes(state.dict, readonly)
-
- # if eager_defaults option is enabled, load
- # all expired cols. Else if we have a version_id_col, make sure
- # it isn't expired.
- toload_now = []
-
- # this is specifically to emit a second SELECT for eager_defaults,
- # so only if it's set to True, not "auto"
- if base_mapper.eager_defaults is True:
- toload_now.extend(
- state._unloaded_non_object.intersection(
- mapper._server_default_plus_onupdate_propkeys
- )
- )
-
- if (
- mapper.version_id_col is not None
- and mapper.version_id_generator is False
- ):
- if mapper._version_id_prop.key in state.unloaded:
- toload_now.extend([mapper._version_id_prop.key])
-
- if toload_now:
- state.key = base_mapper._identity_key_from_state(state)
- stmt = future.select(mapper).set_label_style(
- LABEL_STYLE_TABLENAME_PLUS_COL
- )
- loading.load_on_ident(
- uowtransaction.session,
- stmt,
- state.key,
- refresh_state=state,
- only_load_props=toload_now,
- )
-
- # call after_XXX extensions
- if not has_identity:
- mapper.dispatch.after_insert(mapper, connection, state)
- else:
- mapper.dispatch.after_update(mapper, connection, state)
-
- if (
- mapper.version_id_generator is False
- and mapper.version_id_col is not None
- ):
- if state_dict[mapper._version_id_prop.key] is None:
- raise orm_exc.FlushError(
- "Instance does not contain a non-NULL version value"
- )
-
-
-def _postfetch_post_update(
- mapper, uowtransaction, table, state, dict_, result, params
-):
- needs_version_id = (
- mapper.version_id_col is not None
- and mapper.version_id_col in mapper._cols_by_table[table]
- )
-
- if not uowtransaction.is_deleted(state):
- # post updating after a regular INSERT or UPDATE, do a full postfetch
- prefetch_cols = result.context.compiled.prefetch
- postfetch_cols = result.context.compiled.postfetch
- elif needs_version_id:
- # post updating before a DELETE with a version_id_col, need to
- # postfetch just version_id_col
- prefetch_cols = postfetch_cols = ()
- else:
- # post updating before a DELETE without a version_id_col,
- # don't need to postfetch
- return
-
- if needs_version_id:
- prefetch_cols = list(prefetch_cols) + [mapper.version_id_col]
-
- refresh_flush = bool(mapper.class_manager.dispatch.refresh_flush)
- if refresh_flush:
- load_evt_attrs = []
-
- for c in prefetch_cols:
- if c.key in params and c in mapper._columntoproperty:
- dict_[mapper._columntoproperty[c].key] = params[c.key]
- if refresh_flush:
- load_evt_attrs.append(mapper._columntoproperty[c].key)
-
- if refresh_flush and load_evt_attrs:
- mapper.class_manager.dispatch.refresh_flush(
- state, uowtransaction, load_evt_attrs
- )
-
- if postfetch_cols:
- state._expire_attributes(
- state.dict,
- [
- mapper._columntoproperty[c].key
- for c in postfetch_cols
- if c in mapper._columntoproperty
- ],
- )
-
-
-def _postfetch(
- mapper,
- uowtransaction,
- table,
- state,
- dict_,
- result,
- params,
- value_params,
- isupdate,
- returned_defaults,
-):
- """Expire attributes in need of newly persisted database state,
- after an INSERT or UPDATE statement has proceeded for that
- state."""
-
- prefetch_cols = result.context.compiled.prefetch
- postfetch_cols = result.context.compiled.postfetch
- returning_cols = result.context.compiled.effective_returning
-
- if (
- mapper.version_id_col is not None
- and mapper.version_id_col in mapper._cols_by_table[table]
- ):
- prefetch_cols = list(prefetch_cols) + [mapper.version_id_col]
-
- refresh_flush = bool(mapper.class_manager.dispatch.refresh_flush)
- if refresh_flush:
- load_evt_attrs = []
-
- if returning_cols:
- row = returned_defaults
- if row is not None:
- for row_value, col in zip(row, returning_cols):
- # pk cols returned from insert are handled
- # distinctly, don't step on the values here
- if col.primary_key and result.context.isinsert:
- continue
-
- # note that columns can be in the "return defaults" that are
- # not mapped to this mapper, typically because they are
- # "excluded", which can be specified directly or also occurs
- # when using declarative w/ single table inheritance
- prop = mapper._columntoproperty.get(col)
- if prop:
- dict_[prop.key] = row_value
- if refresh_flush:
- load_evt_attrs.append(prop.key)
-
- for c in prefetch_cols:
- if c.key in params and c in mapper._columntoproperty:
- pkey = mapper._columntoproperty[c].key
-
- # set prefetched value in dict and also pop from committed_state,
- # since this is new database state that replaces whatever might
- # have previously been fetched (see #10800). this is essentially a
- # shorthand version of set_committed_value(), which could also be
- # used here directly (with more overhead)
- dict_[pkey] = params[c.key]
- state.committed_state.pop(pkey, None)
-
- if refresh_flush:
- load_evt_attrs.append(pkey)
-
- if refresh_flush and load_evt_attrs:
- mapper.class_manager.dispatch.refresh_flush(
- state, uowtransaction, load_evt_attrs
- )
-
- if isupdate and value_params:
- # explicitly suit the use case specified by
- # [ticket:3801], PK SQL expressions for UPDATE on non-RETURNING
- # database which are set to themselves in order to do a version bump.
- postfetch_cols.extend(
- [
- col
- for col in value_params
- if col.primary_key and col not in returning_cols
- ]
- )
-
- if postfetch_cols:
- state._expire_attributes(
- state.dict,
- [
- mapper._columntoproperty[c].key
- for c in postfetch_cols
- if c in mapper._columntoproperty
- ],
- )
-
- # synchronize newly inserted ids from one table to the next
- # TODO: this still goes a little too often. would be nice to
- # have definitive list of "columns that changed" here
- for m, equated_pairs in mapper._table_to_equated[table]:
- sync.populate(
- state,
- m,
- state,
- m,
- equated_pairs,
- uowtransaction,
- mapper.passive_updates,
- )
-
-
-def _postfetch_bulk_save(mapper, dict_, table):
- for m, equated_pairs in mapper._table_to_equated[table]:
- sync.bulk_populate_inherit_keys(dict_, m, equated_pairs)
-
-
-def _connections_for_states(base_mapper, uowtransaction, states):
- """Return an iterator of (state, state.dict, mapper, connection).
-
- The states are sorted according to _sort_states, then paired
- with the connection they should be using for the given
- unit of work transaction.
-
- """
- # if session has a connection callable,
- # organize individual states with the connection
- # to use for update
- if uowtransaction.session.connection_callable:
- connection_callable = uowtransaction.session.connection_callable
- else:
- connection = uowtransaction.transaction.connection(base_mapper)
- connection_callable = None
-
- for state in _sort_states(base_mapper, states):
- if connection_callable:
- connection = connection_callable(base_mapper, state.obj())
-
- mapper = state.manager.mapper
-
- yield state, state.dict, mapper, connection
-
-
-def _sort_states(mapper, states):
- pending = set(states)
- persistent = {s for s in pending if s.key is not None}
- pending.difference_update(persistent)
-
- try:
- persistent_sorted = sorted(
- persistent, key=mapper._persistent_sortkey_fn
- )
- except TypeError as err:
- raise sa_exc.InvalidRequestError(
- "Could not sort objects by primary key; primary key "
- "values must be sortable in Python (was: %s)" % err
- ) from err
- return (
- sorted(pending, key=operator.attrgetter("insert_order"))
- + persistent_sorted
- )
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/properties.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/properties.py
deleted file mode 100644
index adee44a..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/properties.py
+++ /dev/null
@@ -1,886 +0,0 @@
-# orm/properties.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
-
-"""MapperProperty implementations.
-
-This is a private module which defines the behavior of individual ORM-
-mapped attributes.
-
-"""
-
-from __future__ import annotations
-
-from typing import Any
-from typing import cast
-from typing import Dict
-from typing import List
-from typing import Optional
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from . import attributes
-from . import strategy_options
-from .base import _DeclarativeMapped
-from .base import class_mapper
-from .descriptor_props import CompositeProperty
-from .descriptor_props import ConcreteInheritedProperty
-from .descriptor_props import SynonymProperty
-from .interfaces import _AttributeOptions
-from .interfaces import _DEFAULT_ATTRIBUTE_OPTIONS
-from .interfaces import _IntrospectsAnnotations
-from .interfaces import _MapsColumns
-from .interfaces import MapperProperty
-from .interfaces import PropComparator
-from .interfaces import StrategizedProperty
-from .relationships import RelationshipProperty
-from .util import de_stringify_annotation
-from .util import de_stringify_union_elements
-from .. import exc as sa_exc
-from .. import ForeignKey
-from .. import log
-from .. import util
-from ..sql import coercions
-from ..sql import roles
-from ..sql.base import _NoArg
-from ..sql.schema import Column
-from ..sql.schema import SchemaConst
-from ..sql.type_api import TypeEngine
-from ..util.typing import de_optionalize_union_types
-from ..util.typing import is_fwd_ref
-from ..util.typing import is_optional_union
-from ..util.typing import is_pep593
-from ..util.typing import is_pep695
-from ..util.typing import is_union
-from ..util.typing import Self
-from ..util.typing import typing_get_args
-
-if TYPE_CHECKING:
- from ._typing import _IdentityKeyType
- from ._typing import _InstanceDict
- from ._typing import _ORMColumnExprArgument
- from ._typing import _RegistryType
- from .base import Mapped
- from .decl_base import _ClassScanMapperConfig
- from .mapper import Mapper
- from .session import Session
- from .state import _InstallLoaderCallableProto
- from .state import InstanceState
- from ..sql._typing import _InfoType
- from ..sql.elements import ColumnElement
- from ..sql.elements import NamedColumn
- from ..sql.operators import OperatorType
- from ..util.typing import _AnnotationScanType
- from ..util.typing import RODescriptorReference
-
-_T = TypeVar("_T", bound=Any)
-_PT = TypeVar("_PT", bound=Any)
-_NC = TypeVar("_NC", bound="NamedColumn[Any]")
-
-__all__ = [
- "ColumnProperty",
- "CompositeProperty",
- "ConcreteInheritedProperty",
- "RelationshipProperty",
- "SynonymProperty",
-]
-
-
-@log.class_logger
-class ColumnProperty(
- _MapsColumns[_T],
- StrategizedProperty[_T],
- _IntrospectsAnnotations,
- log.Identified,
-):
- """Describes an object attribute that corresponds to a table column
- or other column expression.
-
- Public constructor is the :func:`_orm.column_property` function.
-
- """
-
- strategy_wildcard_key = strategy_options._COLUMN_TOKEN
- inherit_cache = True
- """:meta private:"""
-
- _links_to_entity = False
-
- columns: List[NamedColumn[Any]]
-
- _is_polymorphic_discriminator: bool
-
- _mapped_by_synonym: Optional[str]
-
- comparator_factory: Type[PropComparator[_T]]
-
- __slots__ = (
- "columns",
- "group",
- "deferred",
- "instrument",
- "comparator_factory",
- "active_history",
- "expire_on_flush",
- "_creation_order",
- "_is_polymorphic_discriminator",
- "_mapped_by_synonym",
- "_deferred_column_loader",
- "_raise_column_loader",
- "_renders_in_subqueries",
- "raiseload",
- )
-
- def __init__(
- self,
- column: _ORMColumnExprArgument[_T],
- *additional_columns: _ORMColumnExprArgument[Any],
- attribute_options: Optional[_AttributeOptions] = None,
- group: Optional[str] = None,
- deferred: bool = False,
- raiseload: bool = False,
- comparator_factory: Optional[Type[PropComparator[_T]]] = None,
- active_history: bool = False,
- expire_on_flush: bool = True,
- info: Optional[_InfoType] = None,
- doc: Optional[str] = None,
- _instrument: bool = True,
- _assume_readonly_dc_attributes: bool = False,
- ):
- super().__init__(
- attribute_options=attribute_options,
- _assume_readonly_dc_attributes=_assume_readonly_dc_attributes,
- )
- columns = (column,) + additional_columns
- self.columns = [
- coercions.expect(roles.LabeledColumnExprRole, c) for c in columns
- ]
- self.group = group
- self.deferred = deferred
- self.raiseload = raiseload
- self.instrument = _instrument
- self.comparator_factory = (
- comparator_factory
- if comparator_factory is not None
- else self.__class__.Comparator
- )
- self.active_history = active_history
- self.expire_on_flush = expire_on_flush
-
- if info is not None:
- self.info.update(info)
-
- if doc is not None:
- self.doc = doc
- else:
- for col in reversed(self.columns):
- doc = getattr(col, "doc", None)
- if doc is not None:
- self.doc = doc
- break
- else:
- self.doc = None
-
- util.set_creation_order(self)
-
- self.strategy_key = (
- ("deferred", self.deferred),
- ("instrument", self.instrument),
- )
- if self.raiseload:
- self.strategy_key += (("raiseload", True),)
-
- def declarative_scan(
- self,
- decl_scan: _ClassScanMapperConfig,
- registry: _RegistryType,
- cls: Type[Any],
- originating_module: Optional[str],
- key: str,
- mapped_container: Optional[Type[Mapped[Any]]],
- annotation: Optional[_AnnotationScanType],
- extracted_mapped_annotation: Optional[_AnnotationScanType],
- is_dataclass_field: bool,
- ) -> None:
- column = self.columns[0]
- if column.key is None:
- column.key = key
- if column.name is None:
- column.name = key
-
- @property
- def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]:
- return self
-
- @property
- def columns_to_assign(self) -> List[Tuple[Column[Any], int]]:
- # mypy doesn't care about the isinstance here
- return [
- (c, 0) # type: ignore
- for c in self.columns
- if isinstance(c, Column) and c.table is None
- ]
-
- def _memoized_attr__renders_in_subqueries(self) -> bool:
- if ("query_expression", True) in self.strategy_key:
- return self.strategy._have_default_expression # type: ignore
-
- return ("deferred", True) not in self.strategy_key or (
- self not in self.parent._readonly_props # type: ignore
- )
-
- @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
- def _memoized_attr__deferred_column_loader(
- self,
- ) -> _InstallLoaderCallableProto[Any]:
- state = util.preloaded.orm_state
- strategies = util.preloaded.orm_strategies
- return state.InstanceState._instance_level_callable_processor(
- self.parent.class_manager,
- strategies.LoadDeferredColumns(self.key),
- self.key,
- )
-
- @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
- def _memoized_attr__raise_column_loader(
- self,
- ) -> _InstallLoaderCallableProto[Any]:
- state = util.preloaded.orm_state
- strategies = util.preloaded.orm_strategies
- return state.InstanceState._instance_level_callable_processor(
- self.parent.class_manager,
- strategies.LoadDeferredColumns(self.key, True),
- self.key,
- )
-
- def __clause_element__(self) -> roles.ColumnsClauseRole:
- """Allow the ColumnProperty to work in expression before it is turned
- into an instrumented attribute.
- """
-
- return self.expression
-
- @property
- def expression(self) -> roles.ColumnsClauseRole:
- """Return the primary column or expression for this ColumnProperty.
-
- E.g.::
-
-
- class File(Base):
- # ...
-
- name = Column(String(64))
- extension = Column(String(8))
- filename = column_property(name + '.' + extension)
- path = column_property('C:/' + filename.expression)
-
- .. seealso::
-
- :ref:`mapper_column_property_sql_expressions_composed`
-
- """
- return self.columns[0]
-
- def instrument_class(self, mapper: Mapper[Any]) -> None:
- if not self.instrument:
- return
-
- attributes.register_descriptor(
- mapper.class_,
- self.key,
- comparator=self.comparator_factory(self, mapper),
- parententity=mapper,
- doc=self.doc,
- )
-
- def do_init(self) -> None:
- super().do_init()
-
- if len(self.columns) > 1 and set(self.parent.primary_key).issuperset(
- self.columns
- ):
- util.warn(
- (
- "On mapper %s, primary key column '%s' is being combined "
- "with distinct primary key column '%s' in attribute '%s'. "
- "Use explicit properties to give each column its own "
- "mapped attribute name."
- )
- % (self.parent, self.columns[1], self.columns[0], self.key)
- )
-
- def copy(self) -> ColumnProperty[_T]:
- return ColumnProperty(
- *self.columns,
- deferred=self.deferred,
- group=self.group,
- active_history=self.active_history,
- )
-
- def merge(
- self,
- session: Session,
- source_state: InstanceState[Any],
- source_dict: _InstanceDict,
- dest_state: InstanceState[Any],
- dest_dict: _InstanceDict,
- load: bool,
- _recursive: Dict[Any, object],
- _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
- ) -> None:
- if not self.instrument:
- return
- elif self.key in source_dict:
- value = source_dict[self.key]
-
- if not load:
- dest_dict[self.key] = value
- else:
- impl = dest_state.get_impl(self.key)
- impl.set(dest_state, dest_dict, value, None)
- elif dest_state.has_identity and self.key not in dest_dict:
- dest_state._expire_attributes(
- dest_dict, [self.key], no_loader=True
- )
-
- class Comparator(util.MemoizedSlots, PropComparator[_PT]):
- """Produce boolean, comparison, and other operators for
- :class:`.ColumnProperty` attributes.
-
- See the documentation for :class:`.PropComparator` for a brief
- overview.
-
- .. seealso::
-
- :class:`.PropComparator`
-
- :class:`.ColumnOperators`
-
- :ref:`types_operators`
-
- :attr:`.TypeEngine.comparator_factory`
-
- """
-
- if not TYPE_CHECKING:
- # prevent pylance from being clever about slots
- __slots__ = "__clause_element__", "info", "expressions"
-
- prop: RODescriptorReference[ColumnProperty[_PT]]
-
- expressions: Sequence[NamedColumn[Any]]
- """The full sequence of columns referenced by this
- attribute, adjusted for any aliasing in progress.
-
- .. versionadded:: 1.3.17
-
- .. seealso::
-
- :ref:`maptojoin` - usage example
- """
-
- def _orm_annotate_column(self, column: _NC) -> _NC:
- """annotate and possibly adapt a column to be returned
- as the mapped-attribute exposed version of the column.
-
- The column in this context needs to act as much like the
- column in an ORM mapped context as possible, so includes
- annotations to give hints to various ORM functions as to
- the source entity of this column. It also adapts it
- to the mapper's with_polymorphic selectable if one is
- present.
-
- """
-
- pe = self._parententity
- annotations: Dict[str, Any] = {
- "entity_namespace": pe,
- "parententity": pe,
- "parentmapper": pe,
- "proxy_key": self.prop.key,
- }
-
- col = column
-
- # for a mapper with polymorphic_on and an adapter, return
- # the column against the polymorphic selectable.
- # see also orm.util._orm_downgrade_polymorphic_columns
- # for the reverse operation.
- if self._parentmapper._polymorphic_adapter:
- mapper_local_col = col
- col = self._parentmapper._polymorphic_adapter.traverse(col)
-
- # this is a clue to the ORM Query etc. that this column
- # was adapted to the mapper's polymorphic_adapter. the
- # ORM uses this hint to know which column its adapting.
- annotations["adapt_column"] = mapper_local_col
-
- return col._annotate(annotations)._set_propagate_attrs(
- {"compile_state_plugin": "orm", "plugin_subject": pe}
- )
-
- if TYPE_CHECKING:
-
- def __clause_element__(self) -> NamedColumn[_PT]: ...
-
- def _memoized_method___clause_element__(
- self,
- ) -> NamedColumn[_PT]:
- if self.adapter:
- return self.adapter(self.prop.columns[0], self.prop.key)
- else:
- return self._orm_annotate_column(self.prop.columns[0])
-
- def _memoized_attr_info(self) -> _InfoType:
- """The .info dictionary for this attribute."""
-
- ce = self.__clause_element__()
- try:
- return ce.info # type: ignore
- except AttributeError:
- return self.prop.info
-
- def _memoized_attr_expressions(self) -> Sequence[NamedColumn[Any]]:
- """The full sequence of columns referenced by this
- attribute, adjusted for any aliasing in progress.
-
- .. versionadded:: 1.3.17
-
- """
- if self.adapter:
- return [
- self.adapter(col, self.prop.key)
- for col in self.prop.columns
- ]
- else:
- return [
- self._orm_annotate_column(col) for col in self.prop.columns
- ]
-
- def _fallback_getattr(self, key: str) -> Any:
- """proxy attribute access down to the mapped column.
-
- this allows user-defined comparison methods to be accessed.
- """
- return getattr(self.__clause_element__(), key)
-
- def operate(
- self, op: OperatorType, *other: Any, **kwargs: Any
- ) -> ColumnElement[Any]:
- return op(self.__clause_element__(), *other, **kwargs) # type: ignore[no-any-return] # noqa: E501
-
- def reverse_operate(
- self, op: OperatorType, other: Any, **kwargs: Any
- ) -> ColumnElement[Any]:
- col = self.__clause_element__()
- return op(col._bind_param(op, other), col, **kwargs) # type: ignore[no-any-return] # noqa: E501
-
- def __str__(self) -> str:
- if not self.parent or not self.key:
- return object.__repr__(self)
- return str(self.parent.class_.__name__) + "." + self.key
-
-
-class MappedSQLExpression(ColumnProperty[_T], _DeclarativeMapped[_T]):
- """Declarative front-end for the :class:`.ColumnProperty` class.
-
- Public constructor is the :func:`_orm.column_property` function.
-
- .. versionchanged:: 2.0 Added :class:`_orm.MappedSQLExpression` as
- a Declarative compatible subclass for :class:`_orm.ColumnProperty`.
-
- .. seealso::
-
- :class:`.MappedColumn`
-
- """
-
- inherit_cache = True
- """:meta private:"""
-
-
-class MappedColumn(
- _IntrospectsAnnotations,
- _MapsColumns[_T],
- _DeclarativeMapped[_T],
-):
- """Maps a single :class:`_schema.Column` on a class.
-
- :class:`_orm.MappedColumn` is a specialization of the
- :class:`_orm.ColumnProperty` class and is oriented towards declarative
- configuration.
-
- To construct :class:`_orm.MappedColumn` objects, use the
- :func:`_orm.mapped_column` constructor function.
-
- .. versionadded:: 2.0
-
-
- """
-
- __slots__ = (
- "column",
- "_creation_order",
- "_sort_order",
- "foreign_keys",
- "_has_nullable",
- "_has_insert_default",
- "deferred",
- "deferred_group",
- "deferred_raiseload",
- "active_history",
- "_attribute_options",
- "_has_dataclass_arguments",
- "_use_existing_column",
- )
-
- deferred: Union[_NoArg, bool]
- deferred_raiseload: bool
- deferred_group: Optional[str]
-
- column: Column[_T]
- foreign_keys: Optional[Set[ForeignKey]]
- _attribute_options: _AttributeOptions
-
- def __init__(self, *arg: Any, **kw: Any):
- self._attribute_options = attr_opts = kw.pop(
- "attribute_options", _DEFAULT_ATTRIBUTE_OPTIONS
- )
-
- self._use_existing_column = kw.pop("use_existing_column", False)
-
- self._has_dataclass_arguments = (
- attr_opts is not None
- and attr_opts != _DEFAULT_ATTRIBUTE_OPTIONS
- and any(
- attr_opts[i] is not _NoArg.NO_ARG
- for i, attr in enumerate(attr_opts._fields)
- if attr != "dataclasses_default"
- )
- )
-
- insert_default = kw.pop("insert_default", _NoArg.NO_ARG)
- self._has_insert_default = insert_default is not _NoArg.NO_ARG
-
- if self._has_insert_default:
- kw["default"] = insert_default
- elif attr_opts.dataclasses_default is not _NoArg.NO_ARG:
- kw["default"] = attr_opts.dataclasses_default
-
- self.deferred_group = kw.pop("deferred_group", None)
- self.deferred_raiseload = kw.pop("deferred_raiseload", None)
- self.deferred = kw.pop("deferred", _NoArg.NO_ARG)
- self.active_history = kw.pop("active_history", False)
-
- self._sort_order = kw.pop("sort_order", _NoArg.NO_ARG)
- self.column = cast("Column[_T]", Column(*arg, **kw))
- self.foreign_keys = self.column.foreign_keys
- self._has_nullable = "nullable" in kw and kw.get("nullable") not in (
- None,
- SchemaConst.NULL_UNSPECIFIED,
- )
- util.set_creation_order(self)
-
- def _copy(self, **kw: Any) -> Self:
- new = self.__class__.__new__(self.__class__)
- new.column = self.column._copy(**kw)
- new.deferred = self.deferred
- new.deferred_group = self.deferred_group
- new.deferred_raiseload = self.deferred_raiseload
- new.foreign_keys = new.column.foreign_keys
- new.active_history = self.active_history
- new._has_nullable = self._has_nullable
- new._attribute_options = self._attribute_options
- new._has_insert_default = self._has_insert_default
- new._has_dataclass_arguments = self._has_dataclass_arguments
- new._use_existing_column = self._use_existing_column
- new._sort_order = self._sort_order
- util.set_creation_order(new)
- return new
-
- @property
- def name(self) -> str:
- return self.column.name
-
- @property
- def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]:
- effective_deferred = self.deferred
- if effective_deferred is _NoArg.NO_ARG:
- effective_deferred = bool(
- self.deferred_group or self.deferred_raiseload
- )
-
- if effective_deferred or self.active_history:
- return ColumnProperty(
- self.column,
- deferred=effective_deferred,
- group=self.deferred_group,
- raiseload=self.deferred_raiseload,
- attribute_options=self._attribute_options,
- active_history=self.active_history,
- )
- else:
- return None
-
- @property
- def columns_to_assign(self) -> List[Tuple[Column[Any], int]]:
- return [
- (
- self.column,
- (
- self._sort_order
- if self._sort_order is not _NoArg.NO_ARG
- else 0
- ),
- )
- ]
-
- def __clause_element__(self) -> Column[_T]:
- return self.column
-
- def operate(
- self, op: OperatorType, *other: Any, **kwargs: Any
- ) -> ColumnElement[Any]:
- return op(self.__clause_element__(), *other, **kwargs) # type: ignore[no-any-return] # noqa: E501
-
- def reverse_operate(
- self, op: OperatorType, other: Any, **kwargs: Any
- ) -> ColumnElement[Any]:
- col = self.__clause_element__()
- return op(col._bind_param(op, other), col, **kwargs) # type: ignore[no-any-return] # noqa: E501
-
- def found_in_pep593_annotated(self) -> Any:
- # return a blank mapped_column(). This mapped_column()'s
- # Column will be merged into it in _init_column_for_annotation().
- return MappedColumn()
-
- def declarative_scan(
- self,
- decl_scan: _ClassScanMapperConfig,
- registry: _RegistryType,
- cls: Type[Any],
- originating_module: Optional[str],
- key: str,
- mapped_container: Optional[Type[Mapped[Any]]],
- annotation: Optional[_AnnotationScanType],
- extracted_mapped_annotation: Optional[_AnnotationScanType],
- is_dataclass_field: bool,
- ) -> None:
- column = self.column
-
- if (
- self._use_existing_column
- and decl_scan.inherits
- and decl_scan.single
- ):
- if decl_scan.is_deferred:
- raise sa_exc.ArgumentError(
- "Can't use use_existing_column with deferred mappers"
- )
- supercls_mapper = class_mapper(decl_scan.inherits, False)
-
- colname = column.name if column.name is not None else key
- column = self.column = supercls_mapper.local_table.c.get( # type: ignore # noqa: E501
- colname, column
- )
-
- if column.key is None:
- column.key = key
- if column.name is None:
- column.name = key
-
- sqltype = column.type
-
- if extracted_mapped_annotation is None:
- if sqltype._isnull and not self.column.foreign_keys:
- self._raise_for_required(key, cls)
- else:
- return
-
- self._init_column_for_annotation(
- cls,
- registry,
- extracted_mapped_annotation,
- originating_module,
- )
-
- @util.preload_module("sqlalchemy.orm.decl_base")
- def declarative_scan_for_composite(
- self,
- registry: _RegistryType,
- cls: Type[Any],
- originating_module: Optional[str],
- key: str,
- param_name: str,
- param_annotation: _AnnotationScanType,
- ) -> None:
- decl_base = util.preloaded.orm_decl_base
- decl_base._undefer_column_name(param_name, self.column)
- self._init_column_for_annotation(
- cls, registry, param_annotation, originating_module
- )
-
- def _init_column_for_annotation(
- self,
- cls: Type[Any],
- registry: _RegistryType,
- argument: _AnnotationScanType,
- originating_module: Optional[str],
- ) -> None:
- sqltype = self.column.type
-
- if isinstance(argument, str) or is_fwd_ref(
- argument, check_generic=True
- ):
- assert originating_module is not None
- argument = de_stringify_annotation(
- cls, argument, originating_module, include_generic=True
- )
-
- if is_union(argument):
- assert originating_module is not None
- argument = de_stringify_union_elements(
- cls, argument, originating_module
- )
-
- nullable = is_optional_union(argument)
-
- if not self._has_nullable:
- self.column.nullable = nullable
-
- our_type = de_optionalize_union_types(argument)
-
- use_args_from = None
-
- our_original_type = our_type
-
- if is_pep695(our_type):
- our_type = our_type.__value__
-
- if is_pep593(our_type):
- our_type_is_pep593 = True
-
- pep_593_components = typing_get_args(our_type)
- raw_pep_593_type = pep_593_components[0]
- if is_optional_union(raw_pep_593_type):
- raw_pep_593_type = de_optionalize_union_types(raw_pep_593_type)
-
- nullable = True
- if not self._has_nullable:
- self.column.nullable = nullable
- for elem in pep_593_components[1:]:
- if isinstance(elem, MappedColumn):
- use_args_from = elem
- break
- else:
- our_type_is_pep593 = False
- raw_pep_593_type = None
-
- if use_args_from is not None:
- if (
- not self._has_insert_default
- and use_args_from.column.default is not None
- ):
- self.column.default = None
-
- use_args_from.column._merge(self.column)
- sqltype = self.column.type
-
- if (
- use_args_from.deferred is not _NoArg.NO_ARG
- and self.deferred is _NoArg.NO_ARG
- ):
- self.deferred = use_args_from.deferred
-
- if (
- use_args_from.deferred_group is not None
- and self.deferred_group is None
- ):
- self.deferred_group = use_args_from.deferred_group
-
- if (
- use_args_from.deferred_raiseload is not None
- and self.deferred_raiseload is None
- ):
- self.deferred_raiseload = use_args_from.deferred_raiseload
-
- if (
- use_args_from._use_existing_column
- and not self._use_existing_column
- ):
- self._use_existing_column = True
-
- if use_args_from.active_history:
- self.active_history = use_args_from.active_history
-
- if (
- use_args_from._sort_order is not None
- and self._sort_order is _NoArg.NO_ARG
- ):
- self._sort_order = use_args_from._sort_order
-
- if (
- use_args_from.column.key is not None
- or use_args_from.column.name is not None
- ):
- util.warn_deprecated(
- "Can't use the 'key' or 'name' arguments in "
- "Annotated with mapped_column(); this will be ignored",
- "2.0.22",
- )
-
- if use_args_from._has_dataclass_arguments:
- for idx, arg in enumerate(
- use_args_from._attribute_options._fields
- ):
- if (
- use_args_from._attribute_options[idx]
- is not _NoArg.NO_ARG
- ):
- arg = arg.replace("dataclasses_", "")
- util.warn_deprecated(
- f"Argument '{arg}' is a dataclass argument and "
- "cannot be specified within a mapped_column() "
- "bundled inside of an Annotated object",
- "2.0.22",
- )
-
- if sqltype._isnull and not self.column.foreign_keys:
- new_sqltype = None
-
- if our_type_is_pep593:
- checks = [our_original_type, raw_pep_593_type]
- else:
- checks = [our_original_type]
-
- for check_type in checks:
- new_sqltype = registry._resolve_type(check_type)
- if new_sqltype is not None:
- break
- else:
- if isinstance(our_type, TypeEngine) or (
- isinstance(our_type, type)
- and issubclass(our_type, TypeEngine)
- ):
- raise sa_exc.ArgumentError(
- f"The type provided inside the {self.column.key!r} "
- "attribute Mapped annotation is the SQLAlchemy type "
- f"{our_type}. Expected a Python type instead"
- )
- else:
- raise sa_exc.ArgumentError(
- "Could not locate SQLAlchemy Core type for Python "
- f"type {our_type} inside the {self.column.key!r} "
- "attribute Mapped annotation"
- )
-
- self.column._set_type(new_sqltype)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/query.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/query.py
deleted file mode 100644
index 1dfc9cb..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/query.py
+++ /dev/null
@@ -1,3394 +0,0 @@
-# orm/query.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
-
-"""The Query class and support.
-
-Defines the :class:`_query.Query` class, the central
-construct used by the ORM to construct database queries.
-
-The :class:`_query.Query` class should not be confused with the
-:class:`_expression.Select` class, which defines database
-SELECT operations at the SQL (non-ORM) level. ``Query`` differs from
-``Select`` in that it returns ORM-mapped objects and interacts with an
-ORM session, whereas the ``Select`` construct interacts directly with the
-database to return iterable result sets.
-
-"""
-from __future__ import annotations
-
-import collections.abc as collections_abc
-import operator
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Dict
-from typing import Generic
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Mapping
-from typing import Optional
-from typing import overload
-from typing import Sequence
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from . import attributes
-from . import interfaces
-from . import loading
-from . import util as orm_util
-from ._typing import _O
-from .base import _assertions
-from .context import _column_descriptions
-from .context import _determine_last_joined_entity
-from .context import _legacy_filter_by_entity_zero
-from .context import FromStatement
-from .context import ORMCompileState
-from .context import QueryContext
-from .interfaces import ORMColumnDescription
-from .interfaces import ORMColumnsClauseRole
-from .util import AliasedClass
-from .util import object_mapper
-from .util import with_parent
-from .. import exc as sa_exc
-from .. import inspect
-from .. import inspection
-from .. import log
-from .. import sql
-from .. import util
-from ..engine import Result
-from ..engine import Row
-from ..event import dispatcher
-from ..event import EventTarget
-from ..sql import coercions
-from ..sql import expression
-from ..sql import roles
-from ..sql import Select
-from ..sql import util as sql_util
-from ..sql import visitors
-from ..sql._typing import _FromClauseArgument
-from ..sql._typing import _TP
-from ..sql.annotation import SupportsCloneAnnotations
-from ..sql.base import _entity_namespace_key
-from ..sql.base import _generative
-from ..sql.base import _NoArg
-from ..sql.base import Executable
-from ..sql.base import Generative
-from ..sql.elements import BooleanClauseList
-from ..sql.expression import Exists
-from ..sql.selectable import _MemoizedSelectEntities
-from ..sql.selectable import _SelectFromElements
-from ..sql.selectable import ForUpdateArg
-from ..sql.selectable import HasHints
-from ..sql.selectable import HasPrefixes
-from ..sql.selectable import HasSuffixes
-from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
-from ..sql.selectable import SelectLabelStyle
-from ..util.typing import Literal
-from ..util.typing import Self
-
-
-if TYPE_CHECKING:
- from ._typing import _EntityType
- from ._typing import _ExternalEntityType
- from ._typing import _InternalEntityType
- from ._typing import SynchronizeSessionArgument
- from .mapper import Mapper
- from .path_registry import PathRegistry
- from .session import _PKIdentityArgument
- from .session import Session
- from .state import InstanceState
- from ..engine.cursor import CursorResult
- from ..engine.interfaces import _ImmutableExecuteOptions
- from ..engine.interfaces import CompiledCacheType
- from ..engine.interfaces import IsolationLevel
- from ..engine.interfaces import SchemaTranslateMapType
- from ..engine.result import FrozenResult
- from ..engine.result import ScalarResult
- from ..sql._typing import _ColumnExpressionArgument
- from ..sql._typing import _ColumnExpressionOrStrLabelArgument
- from ..sql._typing import _ColumnsClauseArgument
- from ..sql._typing import _DMLColumnArgument
- from ..sql._typing import _JoinTargetArgument
- from ..sql._typing import _LimitOffsetType
- from ..sql._typing import _MAYBE_ENTITY
- from ..sql._typing import _no_kw
- from ..sql._typing import _NOT_ENTITY
- from ..sql._typing import _OnClauseArgument
- from ..sql._typing import _PropagateAttrsType
- from ..sql._typing import _T0
- from ..sql._typing import _T1
- from ..sql._typing import _T2
- from ..sql._typing import _T3
- from ..sql._typing import _T4
- from ..sql._typing import _T5
- from ..sql._typing import _T6
- from ..sql._typing import _T7
- from ..sql._typing import _TypedColumnClauseArgument as _TCCA
- from ..sql.base import CacheableOptions
- from ..sql.base import ExecutableOption
- from ..sql.elements import ColumnElement
- from ..sql.elements import Label
- from ..sql.selectable import _ForUpdateOfArgument
- from ..sql.selectable import _JoinTargetElement
- from ..sql.selectable import _SetupJoinsElement
- from ..sql.selectable import Alias
- from ..sql.selectable import CTE
- from ..sql.selectable import ExecutableReturnsRows
- from ..sql.selectable import FromClause
- from ..sql.selectable import ScalarSelect
- from ..sql.selectable import Subquery
-
-
-__all__ = ["Query", "QueryContext"]
-
-_T = TypeVar("_T", bound=Any)
-
-
-@inspection._self_inspects
-@log.class_logger
-class Query(
- _SelectFromElements,
- SupportsCloneAnnotations,
- HasPrefixes,
- HasSuffixes,
- HasHints,
- EventTarget,
- log.Identified,
- Generative,
- Executable,
- Generic[_T],
-):
- """ORM-level SQL construction object.
-
- .. legacy:: The ORM :class:`.Query` object is a legacy construct
- as of SQLAlchemy 2.0. See the notes at the top of
- :ref:`query_api_toplevel` for an overview, including links to migration
- documentation.
-
- :class:`_query.Query` objects are normally initially generated using the
- :meth:`~.Session.query` method of :class:`.Session`, and in
- less common cases by instantiating the :class:`_query.Query` directly and
- associating with a :class:`.Session` using the
- :meth:`_query.Query.with_session`
- method.
-
- """
-
- # elements that are in Core and can be cached in the same way
- _where_criteria: Tuple[ColumnElement[Any], ...] = ()
- _having_criteria: Tuple[ColumnElement[Any], ...] = ()
-
- _order_by_clauses: Tuple[ColumnElement[Any], ...] = ()
- _group_by_clauses: Tuple[ColumnElement[Any], ...] = ()
- _limit_clause: Optional[ColumnElement[Any]] = None
- _offset_clause: Optional[ColumnElement[Any]] = None
-
- _distinct: bool = False
- _distinct_on: Tuple[ColumnElement[Any], ...] = ()
-
- _for_update_arg: Optional[ForUpdateArg] = None
- _correlate: Tuple[FromClause, ...] = ()
- _auto_correlate: bool = True
- _from_obj: Tuple[FromClause, ...] = ()
- _setup_joins: Tuple[_SetupJoinsElement, ...] = ()
-
- _label_style: SelectLabelStyle = SelectLabelStyle.LABEL_STYLE_LEGACY_ORM
-
- _memoized_select_entities = ()
-
- _compile_options: Union[Type[CacheableOptions], CacheableOptions] = (
- ORMCompileState.default_compile_options
- )
-
- _with_options: Tuple[ExecutableOption, ...]
- load_options = QueryContext.default_load_options + {
- "_legacy_uniquing": True
- }
-
- _params: util.immutabledict[str, Any] = util.EMPTY_DICT
-
- # local Query builder state, not needed for
- # compilation or execution
- _enable_assertions = True
-
- _statement: Optional[ExecutableReturnsRows] = None
-
- session: Session
-
- dispatch: dispatcher[Query[_T]]
-
- # mirrors that of ClauseElement, used to propagate the "orm"
- # plugin as well as the "subject" of the plugin, e.g. the mapper
- # we are querying against.
- @util.memoized_property
- def _propagate_attrs(self) -> _PropagateAttrsType:
- return util.EMPTY_DICT
-
- def __init__(
- self,
- entities: Union[
- _ColumnsClauseArgument[Any], Sequence[_ColumnsClauseArgument[Any]]
- ],
- session: Optional[Session] = None,
- ):
- """Construct a :class:`_query.Query` directly.
-
- E.g.::
-
- q = Query([User, Address], session=some_session)
-
- The above is equivalent to::
-
- q = some_session.query(User, Address)
-
- :param entities: a sequence of entities and/or SQL expressions.
-
- :param session: a :class:`.Session` with which the
- :class:`_query.Query`
- will be associated. Optional; a :class:`_query.Query`
- can be associated
- with a :class:`.Session` generatively via the
- :meth:`_query.Query.with_session` method as well.
-
- .. seealso::
-
- :meth:`.Session.query`
-
- :meth:`_query.Query.with_session`
-
- """
-
- # session is usually present. There's one case in subqueryloader
- # where it stores a Query without a Session and also there are tests
- # for the query(Entity).with_session(session) API which is likely in
- # some old recipes, however these are legacy as select() can now be
- # used.
- self.session = session # type: ignore
- self._set_entities(entities)
-
- def _set_propagate_attrs(self, values: Mapping[str, Any]) -> Self:
- self._propagate_attrs = util.immutabledict(values)
- return self
-
- def _set_entities(
- self,
- entities: Union[
- _ColumnsClauseArgument[Any], Iterable[_ColumnsClauseArgument[Any]]
- ],
- ) -> None:
- self._raw_columns = [
- coercions.expect(
- roles.ColumnsClauseRole,
- ent,
- apply_propagate_attrs=self,
- post_inspect=True,
- )
- for ent in util.to_list(entities)
- ]
-
- def tuples(self: Query[_O]) -> Query[Tuple[_O]]:
- """return a tuple-typed form of this :class:`.Query`.
-
- This method invokes the :meth:`.Query.only_return_tuples`
- method with a value of ``True``, which by itself ensures that this
- :class:`.Query` will always return :class:`.Row` objects, even
- if the query is made against a single entity. It then also
- at the typing level will return a "typed" query, if possible,
- that will type result rows as ``Tuple`` objects with typed
- elements.
-
- This method can be compared to the :meth:`.Result.tuples` method,
- which returns "self", but from a typing perspective returns an object
- that will yield typed ``Tuple`` objects for results. Typing
- takes effect only if this :class:`.Query` object is a typed
- query object already.
-
- .. versionadded:: 2.0
-
- .. seealso::
-
- :meth:`.Result.tuples` - v2 equivalent method.
-
- """
- return self.only_return_tuples(True) # type: ignore
-
- def _entity_from_pre_ent_zero(self) -> Optional[_InternalEntityType[Any]]:
- if not self._raw_columns:
- return None
-
- ent = self._raw_columns[0]
-
- if "parententity" in ent._annotations:
- return ent._annotations["parententity"] # type: ignore
- elif "bundle" in ent._annotations:
- return ent._annotations["bundle"] # type: ignore
- else:
- # label, other SQL expression
- for element in visitors.iterate(ent):
- if "parententity" in element._annotations:
- return element._annotations["parententity"] # type: ignore # noqa: E501
- else:
- return None
-
- def _only_full_mapper_zero(self, methname: str) -> Mapper[Any]:
- if (
- len(self._raw_columns) != 1
- or "parententity" not in self._raw_columns[0]._annotations
- or not self._raw_columns[0].is_selectable
- ):
- raise sa_exc.InvalidRequestError(
- "%s() can only be used against "
- "a single mapped class." % methname
- )
-
- return self._raw_columns[0]._annotations["parententity"] # type: ignore # noqa: E501
-
- def _set_select_from(
- self, obj: Iterable[_FromClauseArgument], set_base_alias: bool
- ) -> None:
- fa = [
- coercions.expect(
- roles.StrictFromClauseRole,
- elem,
- allow_select=True,
- apply_propagate_attrs=self,
- )
- for elem in obj
- ]
-
- self._compile_options += {"_set_base_alias": set_base_alias}
- self._from_obj = tuple(fa)
-
- @_generative
- def _set_lazyload_from(self, state: InstanceState[Any]) -> Self:
- self.load_options += {"_lazy_loaded_from": state}
- return self
-
- def _get_condition(self) -> None:
- """used by legacy BakedQuery"""
- self._no_criterion_condition("get", order_by=False, distinct=False)
-
- def _get_existing_condition(self) -> None:
- self._no_criterion_assertion("get", order_by=False, distinct=False)
-
- def _no_criterion_assertion(
- self, meth: str, order_by: bool = True, distinct: bool = True
- ) -> None:
- if not self._enable_assertions:
- return
- if (
- self._where_criteria
- or self._statement is not None
- or self._from_obj
- or self._setup_joins
- or self._limit_clause is not None
- or self._offset_clause is not None
- or self._group_by_clauses
- or (order_by and self._order_by_clauses)
- or (distinct and self._distinct)
- ):
- raise sa_exc.InvalidRequestError(
- "Query.%s() being called on a "
- "Query with existing criterion. " % meth
- )
-
- def _no_criterion_condition(
- self, meth: str, order_by: bool = True, distinct: bool = True
- ) -> None:
- self._no_criterion_assertion(meth, order_by, distinct)
-
- self._from_obj = self._setup_joins = ()
- if self._statement is not None:
- self._compile_options += {"_statement": None}
- self._where_criteria = ()
- self._distinct = False
-
- self._order_by_clauses = self._group_by_clauses = ()
-
- def _no_clauseelement_condition(self, meth: str) -> None:
- if not self._enable_assertions:
- return
- if self._order_by_clauses:
- raise sa_exc.InvalidRequestError(
- "Query.%s() being called on a "
- "Query with existing criterion. " % meth
- )
- self._no_criterion_condition(meth)
-
- def _no_statement_condition(self, meth: str) -> None:
- if not self._enable_assertions:
- return
- if self._statement is not None:
- raise sa_exc.InvalidRequestError(
- (
- "Query.%s() being called on a Query with an existing full "
- "statement - can't apply criterion."
- )
- % meth
- )
-
- def _no_limit_offset(self, meth: str) -> None:
- if not self._enable_assertions:
- return
- if self._limit_clause is not None or self._offset_clause is not None:
- raise sa_exc.InvalidRequestError(
- "Query.%s() being called on a Query which already has LIMIT "
- "or OFFSET applied. Call %s() before limit() or offset() "
- "are applied." % (meth, meth)
- )
-
- @property
- def _has_row_limiting_clause(self) -> bool:
- return (
- self._limit_clause is not None or self._offset_clause is not None
- )
-
- def _get_options(
- self,
- populate_existing: Optional[bool] = None,
- version_check: Optional[bool] = None,
- only_load_props: Optional[Sequence[str]] = None,
- refresh_state: Optional[InstanceState[Any]] = None,
- identity_token: Optional[Any] = None,
- ) -> Self:
- load_options: Dict[str, Any] = {}
- compile_options: Dict[str, Any] = {}
-
- if version_check:
- load_options["_version_check"] = version_check
- if populate_existing:
- load_options["_populate_existing"] = populate_existing
- if refresh_state:
- load_options["_refresh_state"] = refresh_state
- compile_options["_for_refresh_state"] = True
- if only_load_props:
- compile_options["_only_load_props"] = frozenset(only_load_props)
- if identity_token:
- load_options["_identity_token"] = identity_token
-
- if load_options:
- self.load_options += load_options
- if compile_options:
- self._compile_options += compile_options
-
- return self
-
- def _clone(self, **kw: Any) -> Self:
- return self._generate()
-
- def _get_select_statement_only(self) -> Select[_T]:
- if self._statement is not None:
- raise sa_exc.InvalidRequestError(
- "Can't call this method on a Query that uses from_statement()"
- )
- return cast("Select[_T]", self.statement)
-
- @property
- def statement(self) -> Union[Select[_T], FromStatement[_T]]:
- """The full SELECT statement represented by this Query.
-
- The statement by default will not have disambiguating labels
- applied to the construct unless with_labels(True) is called
- first.
-
- """
-
- # .statement can return the direct future.Select() construct here, as
- # long as we are not using subsequent adaption features that
- # are made against raw entities, e.g. from_self(), with_polymorphic(),
- # select_entity_from(). If these features are being used, then
- # the Select() we return will not have the correct .selected_columns
- # collection and will not embed in subsequent queries correctly.
- # We could find a way to make this collection "correct", however
- # this would not be too different from doing the full compile as
- # we are doing in any case, the Select() would still not have the
- # proper state for other attributes like whereclause, order_by,
- # and these features are all deprecated in any case.
- #
- # for these reasons, Query is not a Select, it remains an ORM
- # object for which __clause_element__() must be called in order for
- # it to provide a real expression object.
- #
- # from there, it starts to look much like Query itself won't be
- # passed into the execute process and won't generate its own cache
- # key; this will all occur in terms of the ORM-enabled Select.
- if not self._compile_options._set_base_alias:
- # if we don't have legacy top level aliasing features in use
- # then convert to a future select() directly
- stmt = self._statement_20(for_statement=True)
- else:
- stmt = self._compile_state(for_statement=True).statement
-
- if self._params:
- stmt = stmt.params(self._params)
-
- return stmt
-
- def _final_statement(self, legacy_query_style: bool = True) -> Select[Any]:
- """Return the 'final' SELECT statement for this :class:`.Query`.
-
- This is used by the testing suite only and is fairly inefficient.
-
- This is the Core-only select() that will be rendered by a complete
- compilation of this query, and is what .statement used to return
- in 1.3.
-
-
- """
-
- q = self._clone()
-
- return q._compile_state(
- use_legacy_query_style=legacy_query_style
- ).statement # type: ignore
-
- def _statement_20(
- self, for_statement: bool = False, use_legacy_query_style: bool = True
- ) -> Union[Select[_T], FromStatement[_T]]:
- # TODO: this event needs to be deprecated, as it currently applies
- # only to ORM query and occurs at this spot that is now more
- # or less an artificial spot
- if self.dispatch.before_compile:
- for fn in self.dispatch.before_compile:
- new_query = fn(self)
- if new_query is not None and new_query is not self:
- self = new_query
- if not fn._bake_ok: # type: ignore
- self._compile_options += {"_bake_ok": False}
-
- compile_options = self._compile_options
- compile_options += {
- "_for_statement": for_statement,
- "_use_legacy_query_style": use_legacy_query_style,
- }
-
- stmt: Union[Select[_T], FromStatement[_T]]
-
- if self._statement is not None:
- stmt = FromStatement(self._raw_columns, self._statement)
- stmt.__dict__.update(
- _with_options=self._with_options,
- _with_context_options=self._with_context_options,
- _compile_options=compile_options,
- _execution_options=self._execution_options,
- _propagate_attrs=self._propagate_attrs,
- )
- else:
- # Query / select() internal attributes are 99% cross-compatible
- stmt = Select._create_raw_select(**self.__dict__)
- stmt.__dict__.update(
- _label_style=self._label_style,
- _compile_options=compile_options,
- _propagate_attrs=self._propagate_attrs,
- )
- stmt.__dict__.pop("session", None)
-
- # ensure the ORM context is used to compile the statement, even
- # if it has no ORM entities. This is so ORM-only things like
- # _legacy_joins are picked up that wouldn't be picked up by the
- # Core statement context
- if "compile_state_plugin" not in stmt._propagate_attrs:
- stmt._propagate_attrs = stmt._propagate_attrs.union(
- {"compile_state_plugin": "orm", "plugin_subject": None}
- )
-
- return stmt
-
- def subquery(
- self,
- name: Optional[str] = None,
- with_labels: bool = False,
- reduce_columns: bool = False,
- ) -> Subquery:
- """Return the full SELECT statement represented by
- this :class:`_query.Query`, embedded within an
- :class:`_expression.Alias`.
-
- Eager JOIN generation within the query is disabled.
-
- .. seealso::
-
- :meth:`_sql.Select.subquery` - v2 comparable method.
-
- :param name: string name to be assigned as the alias;
- this is passed through to :meth:`_expression.FromClause.alias`.
- If ``None``, a name will be deterministically generated
- at compile time.
-
- :param with_labels: if True, :meth:`.with_labels` will be called
- on the :class:`_query.Query` first to apply table-qualified labels
- to all columns.
-
- :param reduce_columns: if True,
- :meth:`_expression.Select.reduce_columns` will
- be called on the resulting :func:`_expression.select` construct,
- to remove same-named columns where one also refers to the other
- via foreign key or WHERE clause equivalence.
-
- """
- q = self.enable_eagerloads(False)
- if with_labels:
- q = q.set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
-
- stmt = q._get_select_statement_only()
-
- if TYPE_CHECKING:
- assert isinstance(stmt, Select)
-
- if reduce_columns:
- stmt = stmt.reduce_columns()
- return stmt.subquery(name=name)
-
- def cte(
- self,
- name: Optional[str] = None,
- recursive: bool = False,
- nesting: bool = False,
- ) -> CTE:
- r"""Return the full SELECT statement represented by this
- :class:`_query.Query` represented as a common table expression (CTE).
-
- Parameters and usage are the same as those of the
- :meth:`_expression.SelectBase.cte` method; see that method for
- further details.
-
- Here is the `PostgreSQL WITH
- RECURSIVE example
- <https://www.postgresql.org/docs/current/static/queries-with.html>`_.
- Note that, in this example, the ``included_parts`` cte and the
- ``incl_alias`` alias of it are Core selectables, which
- means the columns are accessed via the ``.c.`` attribute. The
- ``parts_alias`` object is an :func:`_orm.aliased` instance of the
- ``Part`` entity, so column-mapped attributes are available
- directly::
-
- from sqlalchemy.orm import aliased
-
- class Part(Base):
- __tablename__ = 'part'
- part = Column(String, primary_key=True)
- sub_part = Column(String, primary_key=True)
- quantity = Column(Integer)
-
- included_parts = session.query(
- Part.sub_part,
- Part.part,
- Part.quantity).\
- filter(Part.part=="our part").\
- cte(name="included_parts", recursive=True)
-
- incl_alias = aliased(included_parts, name="pr")
- parts_alias = aliased(Part, name="p")
- included_parts = included_parts.union_all(
- session.query(
- parts_alias.sub_part,
- parts_alias.part,
- parts_alias.quantity).\
- filter(parts_alias.part==incl_alias.c.sub_part)
- )
-
- q = session.query(
- included_parts.c.sub_part,
- func.sum(included_parts.c.quantity).
- label('total_quantity')
- ).\
- group_by(included_parts.c.sub_part)
-
- .. seealso::
-
- :meth:`_sql.Select.cte` - v2 equivalent method.
-
- """
- return (
- self.enable_eagerloads(False)
- ._get_select_statement_only()
- .cte(name=name, recursive=recursive, nesting=nesting)
- )
-
- def label(self, name: Optional[str]) -> Label[Any]:
- """Return the full SELECT statement represented by this
- :class:`_query.Query`, converted
- to a scalar subquery with a label of the given name.
-
- .. seealso::
-
- :meth:`_sql.Select.label` - v2 comparable method.
-
- """
-
- return (
- self.enable_eagerloads(False)
- ._get_select_statement_only()
- .label(name)
- )
-
- @overload
- def as_scalar(
- self: Query[Tuple[_MAYBE_ENTITY]],
- ) -> ScalarSelect[_MAYBE_ENTITY]: ...
-
- @overload
- def as_scalar(
- self: Query[Tuple[_NOT_ENTITY]],
- ) -> ScalarSelect[_NOT_ENTITY]: ...
-
- @overload
- def as_scalar(self) -> ScalarSelect[Any]: ...
-
- @util.deprecated(
- "1.4",
- "The :meth:`_query.Query.as_scalar` method is deprecated and will be "
- "removed in a future release. Please refer to "
- ":meth:`_query.Query.scalar_subquery`.",
- )
- def as_scalar(self) -> ScalarSelect[Any]:
- """Return the full SELECT statement represented by this
- :class:`_query.Query`, converted to a scalar subquery.
-
- """
- return self.scalar_subquery()
-
- @overload
- def scalar_subquery(
- self: Query[Tuple[_MAYBE_ENTITY]],
- ) -> ScalarSelect[Any]: ...
-
- @overload
- def scalar_subquery(
- self: Query[Tuple[_NOT_ENTITY]],
- ) -> ScalarSelect[_NOT_ENTITY]: ...
-
- @overload
- def scalar_subquery(self) -> ScalarSelect[Any]: ...
-
- def scalar_subquery(self) -> ScalarSelect[Any]:
- """Return the full SELECT statement represented by this
- :class:`_query.Query`, converted to a scalar subquery.
-
- Analogous to
- :meth:`sqlalchemy.sql.expression.SelectBase.scalar_subquery`.
-
- .. versionchanged:: 1.4 The :meth:`_query.Query.scalar_subquery`
- method replaces the :meth:`_query.Query.as_scalar` method.
-
- .. seealso::
-
- :meth:`_sql.Select.scalar_subquery` - v2 comparable method.
-
- """
-
- return (
- self.enable_eagerloads(False)
- ._get_select_statement_only()
- .scalar_subquery()
- )
-
- @property
- def selectable(self) -> Union[Select[_T], FromStatement[_T]]:
- """Return the :class:`_expression.Select` object emitted by this
- :class:`_query.Query`.
-
- Used for :func:`_sa.inspect` compatibility, this is equivalent to::
-
- query.enable_eagerloads(False).with_labels().statement
-
- """
- return self.__clause_element__()
-
- def __clause_element__(self) -> Union[Select[_T], FromStatement[_T]]:
- return (
- self._with_compile_options(
- _enable_eagerloads=False, _render_for_subquery=True
- )
- .set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
- .statement
- )
-
- @overload
- def only_return_tuples(
- self: Query[_O], value: Literal[True]
- ) -> RowReturningQuery[Tuple[_O]]: ...
-
- @overload
- def only_return_tuples(
- self: Query[_O], value: Literal[False]
- ) -> Query[_O]: ...
-
- @_generative
- def only_return_tuples(self, value: bool) -> Query[Any]:
- """When set to True, the query results will always be a
- :class:`.Row` object.
-
- This can change a query that normally returns a single entity
- as a scalar to return a :class:`.Row` result in all cases.
-
- .. seealso::
-
- :meth:`.Query.tuples` - returns tuples, but also at the typing
- level will type results as ``Tuple``.
-
- :meth:`_query.Query.is_single_entity`
-
- :meth:`_engine.Result.tuples` - v2 comparable method.
-
- """
- self.load_options += dict(_only_return_tuples=value)
- return self
-
- @property
- def is_single_entity(self) -> bool:
- """Indicates if this :class:`_query.Query`
- returns tuples or single entities.
-
- Returns True if this query returns a single entity for each instance
- in its result list, and False if this query returns a tuple of entities
- for each result.
-
- .. versionadded:: 1.3.11
-
- .. seealso::
-
- :meth:`_query.Query.only_return_tuples`
-
- """
- return (
- not self.load_options._only_return_tuples
- and len(self._raw_columns) == 1
- and "parententity" in self._raw_columns[0]._annotations
- and isinstance(
- self._raw_columns[0]._annotations["parententity"],
- ORMColumnsClauseRole,
- )
- )
-
- @_generative
- def enable_eagerloads(self, value: bool) -> Self:
- """Control whether or not eager joins and subqueries are
- rendered.
-
- When set to False, the returned Query will not render
- eager joins regardless of :func:`~sqlalchemy.orm.joinedload`,
- :func:`~sqlalchemy.orm.subqueryload` options
- or mapper-level ``lazy='joined'``/``lazy='subquery'``
- configurations.
-
- This is used primarily when nesting the Query's
- statement into a subquery or other
- selectable, or when using :meth:`_query.Query.yield_per`.
-
- """
- self._compile_options += {"_enable_eagerloads": value}
- return self
-
- @_generative
- def _with_compile_options(self, **opt: Any) -> Self:
- self._compile_options += opt
- return self
-
- @util.became_legacy_20(
- ":meth:`_orm.Query.with_labels` and :meth:`_orm.Query.apply_labels`",
- alternative="Use set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL) "
- "instead.",
- )
- def with_labels(self) -> Self:
- return self.set_label_style(
- SelectLabelStyle.LABEL_STYLE_TABLENAME_PLUS_COL
- )
-
- apply_labels = with_labels
-
- @property
- def get_label_style(self) -> SelectLabelStyle:
- """
- Retrieve the current label style.
-
- .. versionadded:: 1.4
-
- .. seealso::
-
- :meth:`_sql.Select.get_label_style` - v2 equivalent method.
-
- """
- return self._label_style
-
- def set_label_style(self, style: SelectLabelStyle) -> Self:
- """Apply column labels to the return value of Query.statement.
-
- Indicates that this Query's `statement` accessor should return
- a SELECT statement that applies labels to all columns in the
- form <tablename>_<columnname>; this is commonly used to
- disambiguate columns from multiple tables which have the same
- name.
-
- When the `Query` actually issues SQL to load rows, it always
- uses column labeling.
-
- .. note:: The :meth:`_query.Query.set_label_style` method *only* applies
- the output of :attr:`_query.Query.statement`, and *not* to any of
- the result-row invoking systems of :class:`_query.Query` itself,
- e.g.
- :meth:`_query.Query.first`, :meth:`_query.Query.all`, etc.
- To execute
- a query using :meth:`_query.Query.set_label_style`, invoke the
- :attr:`_query.Query.statement` using :meth:`.Session.execute`::
-
- result = session.execute(
- query
- .set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
- .statement
- )
-
- .. versionadded:: 1.4
-
-
- .. seealso::
-
- :meth:`_sql.Select.set_label_style` - v2 equivalent method.
-
- """ # noqa
- if self._label_style is not style:
- self = self._generate()
- self._label_style = style
- return self
-
- @_generative
- def enable_assertions(self, value: bool) -> Self:
- """Control whether assertions are generated.
-
- When set to False, the returned Query will
- not assert its state before certain operations,
- including that LIMIT/OFFSET has not been applied
- when filter() is called, no criterion exists
- when get() is called, and no "from_statement()"
- exists when filter()/order_by()/group_by() etc.
- is called. This more permissive mode is used by
- custom Query subclasses to specify criterion or
- other modifiers outside of the usual usage patterns.
-
- Care should be taken to ensure that the usage
- pattern is even possible. A statement applied
- by from_statement() will override any criterion
- set by filter() or order_by(), for example.
-
- """
- self._enable_assertions = value
- return self
-
- @property
- def whereclause(self) -> Optional[ColumnElement[bool]]:
- """A readonly attribute which returns the current WHERE criterion for
- this Query.
-
- This returned value is a SQL expression construct, or ``None`` if no
- criterion has been established.
-
- .. seealso::
-
- :attr:`_sql.Select.whereclause` - v2 equivalent property.
-
- """
- return BooleanClauseList._construct_for_whereclause(
- self._where_criteria
- )
-
- @_generative
- def _with_current_path(self, path: PathRegistry) -> Self:
- """indicate that this query applies to objects loaded
- within a certain path.
-
- Used by deferred loaders (see strategies.py) which transfer
- query options from an originating query to a newly generated
- query intended for the deferred load.
-
- """
- self._compile_options += {"_current_path": path}
- return self
-
- @_generative
- def yield_per(self, count: int) -> Self:
- r"""Yield only ``count`` rows at a time.
-
- The purpose of this method is when fetching very large result sets
- (> 10K rows), to batch results in sub-collections and yield them
- out partially, so that the Python interpreter doesn't need to declare
- very large areas of memory which is both time consuming and leads
- to excessive memory use. The performance from fetching hundreds of
- thousands of rows can often double when a suitable yield-per setting
- (e.g. approximately 1000) is used, even with DBAPIs that buffer
- rows (which are most).
-
- As of SQLAlchemy 1.4, the :meth:`_orm.Query.yield_per` method is
- equivalent to using the ``yield_per`` execution option at the ORM
- level. See the section :ref:`orm_queryguide_yield_per` for further
- background on this option.
-
- .. seealso::
-
- :ref:`orm_queryguide_yield_per`
-
- """
- self.load_options += {"_yield_per": count}
- return self
-
- @util.became_legacy_20(
- ":meth:`_orm.Query.get`",
- alternative="The method is now available as :meth:`_orm.Session.get`",
- )
- def get(self, ident: _PKIdentityArgument) -> Optional[Any]:
- """Return an instance based on the given primary key identifier,
- or ``None`` if not found.
-
- E.g.::
-
- my_user = session.query(User).get(5)
-
- some_object = session.query(VersionedFoo).get((5, 10))
-
- some_object = session.query(VersionedFoo).get(
- {"id": 5, "version_id": 10})
-
- :meth:`_query.Query.get` is special in that it provides direct
- access to the identity map of the owning :class:`.Session`.
- If the given primary key identifier is present
- in the local identity map, the object is returned
- directly from this collection and no SQL is emitted,
- unless the object has been marked fully expired.
- If not present,
- a SELECT is performed in order to locate the object.
-
- :meth:`_query.Query.get` also will perform a check if
- the object is present in the identity map and
- marked as expired - a SELECT
- is emitted to refresh the object as well as to
- ensure that the row is still present.
- If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
-
- :meth:`_query.Query.get` is only used to return a single
- mapped instance, not multiple instances or
- individual column constructs, and strictly
- on a single primary key value. The originating
- :class:`_query.Query` must be constructed in this way,
- i.e. against a single mapped entity,
- with no additional filtering criterion. Loading
- options via :meth:`_query.Query.options` may be applied
- however, and will be used if the object is not
- yet locally present.
-
- :param ident: A scalar, tuple, or dictionary representing the
- primary key. For a composite (e.g. multiple column) primary key,
- a tuple or dictionary should be passed.
-
- For a single-column primary key, the scalar calling form is typically
- the most expedient. If the primary key of a row is the value "5",
- the call looks like::
-
- my_object = query.get(5)
-
- The tuple form contains primary key values typically in
- the order in which they correspond to the mapped
- :class:`_schema.Table`
- object's primary key columns, or if the
- :paramref:`_orm.Mapper.primary_key` configuration parameter were
- used, in
- the order used for that parameter. For example, if the primary key
- of a row is represented by the integer
- digits "5, 10" the call would look like::
-
- my_object = query.get((5, 10))
-
- The dictionary form should include as keys the mapped attribute names
- corresponding to each element of the primary key. If the mapped class
- has the attributes ``id``, ``version_id`` as the attributes which
- store the object's primary key value, the call would look like::
-
- my_object = query.get({"id": 5, "version_id": 10})
-
- .. versionadded:: 1.3 the :meth:`_query.Query.get`
- method now optionally
- accepts a dictionary of attribute names to values in order to
- indicate a primary key identifier.
-
-
- :return: The object instance, or ``None``.
-
- """
- self._no_criterion_assertion("get", order_by=False, distinct=False)
-
- # we still implement _get_impl() so that baked query can override
- # it
- return self._get_impl(ident, loading.load_on_pk_identity)
-
- def _get_impl(
- self,
- primary_key_identity: _PKIdentityArgument,
- db_load_fn: Callable[..., Any],
- identity_token: Optional[Any] = None,
- ) -> Optional[Any]:
- mapper = self._only_full_mapper_zero("get")
- return self.session._get_impl(
- mapper,
- primary_key_identity,
- db_load_fn,
- populate_existing=self.load_options._populate_existing,
- with_for_update=self._for_update_arg,
- options=self._with_options,
- identity_token=identity_token,
- execution_options=self._execution_options,
- )
-
- @property
- def lazy_loaded_from(self) -> Optional[InstanceState[Any]]:
- """An :class:`.InstanceState` that is using this :class:`_query.Query`
- for a lazy load operation.
-
- .. deprecated:: 1.4 This attribute should be viewed via the
- :attr:`.ORMExecuteState.lazy_loaded_from` attribute, within
- the context of the :meth:`.SessionEvents.do_orm_execute`
- event.
-
- .. seealso::
-
- :attr:`.ORMExecuteState.lazy_loaded_from`
-
- """
- return self.load_options._lazy_loaded_from # type: ignore
-
- @property
- def _current_path(self) -> PathRegistry:
- return self._compile_options._current_path # type: ignore
-
- @_generative
- def correlate(
- self,
- *fromclauses: Union[Literal[None, False], _FromClauseArgument],
- ) -> Self:
- """Return a :class:`.Query` construct which will correlate the given
- FROM clauses to that of an enclosing :class:`.Query` or
- :func:`~.expression.select`.
-
- The method here accepts mapped classes, :func:`.aliased` constructs,
- and :class:`_orm.Mapper` constructs as arguments, which are resolved
- into expression constructs, in addition to appropriate expression
- constructs.
-
- The correlation arguments are ultimately passed to
- :meth:`_expression.Select.correlate`
- after coercion to expression constructs.
-
- The correlation arguments take effect in such cases
- as when :meth:`_query.Query.from_self` is used, or when
- a subquery as returned by :meth:`_query.Query.subquery` is
- embedded in another :func:`_expression.select` construct.
-
- .. seealso::
-
- :meth:`_sql.Select.correlate` - v2 equivalent method.
-
- """
-
- self._auto_correlate = False
- if fromclauses and fromclauses[0] in {None, False}:
- self._correlate = ()
- else:
- self._correlate = self._correlate + tuple(
- coercions.expect(roles.FromClauseRole, f) for f in fromclauses
- )
- return self
-
- @_generative
- def autoflush(self, setting: bool) -> Self:
- """Return a Query with a specific 'autoflush' setting.
-
- As of SQLAlchemy 1.4, the :meth:`_orm.Query.autoflush` method
- is equivalent to using the ``autoflush`` execution option at the
- ORM level. See the section :ref:`orm_queryguide_autoflush` for
- further background on this option.
-
- """
- self.load_options += {"_autoflush": setting}
- return self
-
- @_generative
- def populate_existing(self) -> Self:
- """Return a :class:`_query.Query`
- that will expire and refresh all instances
- as they are loaded, or reused from the current :class:`.Session`.
-
- As of SQLAlchemy 1.4, the :meth:`_orm.Query.populate_existing` method
- is equivalent to using the ``populate_existing`` execution option at
- the ORM level. See the section :ref:`orm_queryguide_populate_existing`
- for further background on this option.
-
- """
- self.load_options += {"_populate_existing": True}
- return self
-
- @_generative
- def _with_invoke_all_eagers(self, value: bool) -> Self:
- """Set the 'invoke all eagers' flag which causes joined- and
- subquery loaders to traverse into already-loaded related objects
- and collections.
-
- Default is that of :attr:`_query.Query._invoke_all_eagers`.
-
- """
- self.load_options += {"_invoke_all_eagers": value}
- return self
-
- @util.became_legacy_20(
- ":meth:`_orm.Query.with_parent`",
- alternative="Use the :func:`_orm.with_parent` standalone construct.",
- )
- @util.preload_module("sqlalchemy.orm.relationships")
- def with_parent(
- self,
- instance: object,
- property: Optional[ # noqa: A002
- attributes.QueryableAttribute[Any]
- ] = None,
- from_entity: Optional[_ExternalEntityType[Any]] = None,
- ) -> Self:
- """Add filtering criterion that relates the given instance
- to a child object or collection, using its attribute state
- as well as an established :func:`_orm.relationship()`
- configuration.
-
- The method uses the :func:`.with_parent` function to generate
- the clause, the result of which is passed to
- :meth:`_query.Query.filter`.
-
- Parameters are the same as :func:`.with_parent`, with the exception
- that the given property can be None, in which case a search is
- performed against this :class:`_query.Query` object's target mapper.
-
- :param instance:
- An instance which has some :func:`_orm.relationship`.
-
- :param property:
- Class bound attribute which indicates
- what relationship from the instance should be used to reconcile the
- parent/child relationship.
-
- :param from_entity:
- Entity in which to consider as the left side. This defaults to the
- "zero" entity of the :class:`_query.Query` itself.
-
- """
- relationships = util.preloaded.orm_relationships
-
- if from_entity:
- entity_zero = inspect(from_entity)
- else:
- entity_zero = _legacy_filter_by_entity_zero(self)
- if property is None:
- # TODO: deprecate, property has to be supplied
- mapper = object_mapper(instance)
-
- for prop in mapper.iterate_properties:
- if (
- isinstance(prop, relationships.RelationshipProperty)
- and prop.mapper is entity_zero.mapper # type: ignore
- ):
- property = prop # type: ignore # noqa: A001
- break
- else:
- raise sa_exc.InvalidRequestError(
- "Could not locate a property which relates instances "
- "of class '%s' to instances of class '%s'"
- % (
- entity_zero.mapper.class_.__name__, # type: ignore
- instance.__class__.__name__,
- )
- )
-
- return self.filter(
- with_parent(
- instance,
- property, # type: ignore
- entity_zero.entity, # type: ignore
- )
- )
-
- @_generative
- def add_entity(
- self,
- entity: _EntityType[Any],
- alias: Optional[Union[Alias, Subquery]] = None,
- ) -> Query[Any]:
- """add a mapped entity to the list of result columns
- to be returned.
-
- .. seealso::
-
- :meth:`_sql.Select.add_columns` - v2 comparable method.
- """
-
- if alias is not None:
- # TODO: deprecate
- entity = AliasedClass(entity, alias)
-
- self._raw_columns = list(self._raw_columns)
-
- self._raw_columns.append(
- coercions.expect(
- roles.ColumnsClauseRole, entity, apply_propagate_attrs=self
- )
- )
- return self
-
- @_generative
- def with_session(self, session: Session) -> Self:
- """Return a :class:`_query.Query` that will use the given
- :class:`.Session`.
-
- While the :class:`_query.Query`
- object is normally instantiated using the
- :meth:`.Session.query` method, it is legal to build the
- :class:`_query.Query`
- directly without necessarily using a :class:`.Session`. Such a
- :class:`_query.Query` object, or any :class:`_query.Query`
- already associated
- with a different :class:`.Session`, can produce a new
- :class:`_query.Query`
- object associated with a target session using this method::
-
- from sqlalchemy.orm import Query
-
- query = Query([MyClass]).filter(MyClass.id == 5)
-
- result = query.with_session(my_session).one()
-
- """
-
- self.session = session
- return self
-
- def _legacy_from_self(
- self, *entities: _ColumnsClauseArgument[Any]
- ) -> Self:
- # used for query.count() as well as for the same
- # function in BakedQuery, as well as some old tests in test_baked.py.
-
- fromclause = (
- self.set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
- .correlate(None)
- .subquery()
- ._anonymous_fromclause()
- )
-
- q = self._from_selectable(fromclause)
-
- if entities:
- q._set_entities(entities)
- return q
-
- @_generative
- def _set_enable_single_crit(self, val: bool) -> Self:
- self._compile_options += {"_enable_single_crit": val}
- return self
-
- @_generative
- def _from_selectable(
- self, fromclause: FromClause, set_entity_from: bool = True
- ) -> Self:
- for attr in (
- "_where_criteria",
- "_order_by_clauses",
- "_group_by_clauses",
- "_limit_clause",
- "_offset_clause",
- "_last_joined_entity",
- "_setup_joins",
- "_memoized_select_entities",
- "_distinct",
- "_distinct_on",
- "_having_criteria",
- "_prefixes",
- "_suffixes",
- ):
- self.__dict__.pop(attr, None)
- self._set_select_from([fromclause], set_entity_from)
- self._compile_options += {
- "_enable_single_crit": False,
- }
-
- return self
-
- @util.deprecated(
- "1.4",
- ":meth:`_query.Query.values` "
- "is deprecated and will be removed in a "
- "future release. Please use :meth:`_query.Query.with_entities`",
- )
- def values(self, *columns: _ColumnsClauseArgument[Any]) -> Iterable[Any]:
- """Return an iterator yielding result tuples corresponding
- to the given list of columns
-
- """
- return self._values_no_warn(*columns)
-
- _values = values
-
- def _values_no_warn(
- self, *columns: _ColumnsClauseArgument[Any]
- ) -> Iterable[Any]:
- if not columns:
- return iter(())
- q = self._clone().enable_eagerloads(False)
- q._set_entities(columns)
- if not q.load_options._yield_per:
- q.load_options += {"_yield_per": 10}
- return iter(q)
-
- @util.deprecated(
- "1.4",
- ":meth:`_query.Query.value` "
- "is deprecated and will be removed in a "
- "future release. Please use :meth:`_query.Query.with_entities` "
- "in combination with :meth:`_query.Query.scalar`",
- )
- def value(self, column: _ColumnExpressionArgument[Any]) -> Any:
- """Return a scalar result corresponding to the given
- column expression.
-
- """
- try:
- return next(self._values_no_warn(column))[0] # type: ignore
- except StopIteration:
- return None
-
- @overload
- def with_entities(self, _entity: _EntityType[_O]) -> Query[_O]: ...
-
- @overload
- def with_entities(
- self,
- _colexpr: roles.TypedColumnsClauseRole[_T],
- ) -> RowReturningQuery[Tuple[_T]]: ...
-
- # START OVERLOADED FUNCTIONS self.with_entities RowReturningQuery 2-8
-
- # code within this block is **programmatically,
- # statically generated** by tools/generate_tuple_map_overloads.py
-
- @overload
- def with_entities(
- self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1]
- ) -> RowReturningQuery[Tuple[_T0, _T1]]: ...
-
- @overload
- def with_entities(
- self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2]
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2]]: ...
-
- @overload
- def with_entities(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3]]: ...
-
- @overload
- def with_entities(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- __ent4: _TCCA[_T4],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4]]: ...
-
- @overload
- def with_entities(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- __ent4: _TCCA[_T4],
- __ent5: _TCCA[_T5],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]: ...
-
- @overload
- def with_entities(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- __ent4: _TCCA[_T4],
- __ent5: _TCCA[_T5],
- __ent6: _TCCA[_T6],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]: ...
-
- @overload
- def with_entities(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- __ent4: _TCCA[_T4],
- __ent5: _TCCA[_T5],
- __ent6: _TCCA[_T6],
- __ent7: _TCCA[_T7],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]: ...
-
- # END OVERLOADED FUNCTIONS self.with_entities
-
- @overload
- def with_entities(
- self, *entities: _ColumnsClauseArgument[Any]
- ) -> Query[Any]: ...
-
- @_generative
- def with_entities(
- self, *entities: _ColumnsClauseArgument[Any], **__kw: Any
- ) -> Query[Any]:
- r"""Return a new :class:`_query.Query`
- replacing the SELECT list with the
- given entities.
-
- e.g.::
-
- # Users, filtered on some arbitrary criterion
- # and then ordered by related email address
- q = session.query(User).\
- join(User.address).\
- filter(User.name.like('%ed%')).\
- order_by(Address.email)
-
- # given *only* User.id==5, Address.email, and 'q', what
- # would the *next* User in the result be ?
- subq = q.with_entities(Address.email).\
- order_by(None).\
- filter(User.id==5).\
- subquery()
- q = q.join((subq, subq.c.email < Address.email)).\
- limit(1)
-
- .. seealso::
-
- :meth:`_sql.Select.with_only_columns` - v2 comparable method.
- """
- if __kw:
- raise _no_kw()
-
- # Query has all the same fields as Select for this operation
- # this could in theory be based on a protocol but not sure if it's
- # worth it
- _MemoizedSelectEntities._generate_for_statement(self) # type: ignore
- self._set_entities(entities)
- return self
-
- @_generative
- def add_columns(
- self, *column: _ColumnExpressionArgument[Any]
- ) -> Query[Any]:
- """Add one or more column expressions to the list
- of result columns to be returned.
-
- .. seealso::
-
- :meth:`_sql.Select.add_columns` - v2 comparable method.
- """
-
- self._raw_columns = list(self._raw_columns)
-
- self._raw_columns.extend(
- coercions.expect(
- roles.ColumnsClauseRole,
- c,
- apply_propagate_attrs=self,
- post_inspect=True,
- )
- for c in column
- )
- return self
-
- @util.deprecated(
- "1.4",
- ":meth:`_query.Query.add_column` "
- "is deprecated and will be removed in a "
- "future release. Please use :meth:`_query.Query.add_columns`",
- )
- def add_column(self, column: _ColumnExpressionArgument[Any]) -> Query[Any]:
- """Add a column expression to the list of result columns to be
- returned.
-
- """
- return self.add_columns(column)
-
- @_generative
- def options(self, *args: ExecutableOption) -> Self:
- """Return a new :class:`_query.Query` object,
- applying the given list of
- mapper options.
-
- Most supplied options regard changing how column- and
- relationship-mapped attributes are loaded.
-
- .. seealso::
-
- :ref:`loading_columns`
-
- :ref:`relationship_loader_options`
-
- """
-
- opts = tuple(util.flatten_iterator(args))
- if self._compile_options._current_path:
- # opting for lower method overhead for the checks
- for opt in opts:
- if not opt._is_core and opt._is_legacy_option: # type: ignore
- opt.process_query_conditionally(self) # type: ignore
- else:
- for opt in opts:
- if not opt._is_core and opt._is_legacy_option: # type: ignore
- opt.process_query(self) # type: ignore
-
- self._with_options += opts
- return self
-
- def with_transformation(
- self, fn: Callable[[Query[Any]], Query[Any]]
- ) -> Query[Any]:
- """Return a new :class:`_query.Query` object transformed by
- the given function.
-
- E.g.::
-
- def filter_something(criterion):
- def transform(q):
- return q.filter(criterion)
- return transform
-
- q = q.with_transformation(filter_something(x==5))
-
- This allows ad-hoc recipes to be created for :class:`_query.Query`
- objects.
-
- """
- return fn(self)
-
- def get_execution_options(self) -> _ImmutableExecuteOptions:
- """Get the non-SQL options which will take effect during execution.
-
- .. versionadded:: 1.3
-
- .. seealso::
-
- :meth:`_query.Query.execution_options`
-
- :meth:`_sql.Select.get_execution_options` - v2 comparable method.
-
- """
- return self._execution_options
-
- @overload
- def execution_options(
- self,
- *,
- compiled_cache: Optional[CompiledCacheType] = ...,
- logging_token: str = ...,
- isolation_level: IsolationLevel = ...,
- no_parameters: bool = False,
- stream_results: bool = False,
- max_row_buffer: int = ...,
- yield_per: int = ...,
- insertmanyvalues_page_size: int = ...,
- schema_translate_map: Optional[SchemaTranslateMapType] = ...,
- populate_existing: bool = False,
- autoflush: bool = False,
- preserve_rowcount: bool = False,
- **opt: Any,
- ) -> Self: ...
-
- @overload
- def execution_options(self, **opt: Any) -> Self: ...
-
- @_generative
- def execution_options(self, **kwargs: Any) -> Self:
- """Set non-SQL options which take effect during execution.
-
- Options allowed here include all of those accepted by
- :meth:`_engine.Connection.execution_options`, as well as a series
- of ORM specific options:
-
- ``populate_existing=True`` - equivalent to using
- :meth:`_orm.Query.populate_existing`
-
- ``autoflush=True|False`` - equivalent to using
- :meth:`_orm.Query.autoflush`
-
- ``yield_per=<value>`` - equivalent to using
- :meth:`_orm.Query.yield_per`
-
- Note that the ``stream_results`` execution option is enabled
- automatically if the :meth:`~sqlalchemy.orm.query.Query.yield_per()`
- method or execution option is used.
-
- .. versionadded:: 1.4 - added ORM options to
- :meth:`_orm.Query.execution_options`
-
- The execution options may also be specified on a per execution basis
- when using :term:`2.0 style` queries via the
- :paramref:`_orm.Session.execution_options` parameter.
-
- .. warning:: The
- :paramref:`_engine.Connection.execution_options.stream_results`
- parameter should not be used at the level of individual ORM
- statement executions, as the :class:`_orm.Session` will not track
- objects from different schema translate maps within a single
- session. For multiple schema translate maps within the scope of a
- single :class:`_orm.Session`, see :ref:`examples_sharding`.
-
-
- .. seealso::
-
- :ref:`engine_stream_results`
-
- :meth:`_query.Query.get_execution_options`
-
- :meth:`_sql.Select.execution_options` - v2 equivalent method.
-
- """
- self._execution_options = self._execution_options.union(kwargs)
- return self
-
- @_generative
- def with_for_update(
- self,
- *,
- nowait: bool = False,
- read: bool = False,
- of: Optional[_ForUpdateOfArgument] = None,
- skip_locked: bool = False,
- key_share: bool = False,
- ) -> Self:
- """return a new :class:`_query.Query`
- with the specified options for the
- ``FOR UPDATE`` clause.
-
- The behavior of this method is identical to that of
- :meth:`_expression.GenerativeSelect.with_for_update`.
- When called with no arguments,
- the resulting ``SELECT`` statement will have a ``FOR UPDATE`` clause
- appended. When additional arguments are specified, backend-specific
- options such as ``FOR UPDATE NOWAIT`` or ``LOCK IN SHARE MODE``
- can take effect.
-
- E.g.::
-
- q = sess.query(User).populate_existing().with_for_update(nowait=True, of=User)
-
- The above query on a PostgreSQL backend will render like::
-
- SELECT users.id AS users_id FROM users FOR UPDATE OF users NOWAIT
-
- .. warning::
-
- Using ``with_for_update`` in the context of eager loading
- relationships is not officially supported or recommended by
- SQLAlchemy and may not work with certain queries on various
- database backends. When ``with_for_update`` is successfully used
- with a query that involves :func:`_orm.joinedload`, SQLAlchemy will
- attempt to emit SQL that locks all involved tables.
-
- .. note:: It is generally a good idea to combine the use of the
- :meth:`_orm.Query.populate_existing` method when using the
- :meth:`_orm.Query.with_for_update` method. The purpose of
- :meth:`_orm.Query.populate_existing` is to force all the data read
- from the SELECT to be populated into the ORM objects returned,
- even if these objects are already in the :term:`identity map`.
-
- .. seealso::
-
- :meth:`_expression.GenerativeSelect.with_for_update`
- - Core level method with
- full argument and behavioral description.
-
- :meth:`_orm.Query.populate_existing` - overwrites attributes of
- objects already loaded in the identity map.
-
- """ # noqa: E501
-
- self._for_update_arg = ForUpdateArg(
- read=read,
- nowait=nowait,
- of=of,
- skip_locked=skip_locked,
- key_share=key_share,
- )
- return self
-
- @_generative
- def params(
- self, __params: Optional[Dict[str, Any]] = None, **kw: Any
- ) -> Self:
- r"""Add values for bind parameters which may have been
- specified in filter().
-
- Parameters may be specified using \**kwargs, or optionally a single
- dictionary as the first positional argument. The reason for both is
- that \**kwargs is convenient, however some parameter dictionaries
- contain unicode keys in which case \**kwargs cannot be used.
-
- """
- if __params:
- kw.update(__params)
- self._params = self._params.union(kw)
- return self
-
- def where(self, *criterion: _ColumnExpressionArgument[bool]) -> Self:
- """A synonym for :meth:`.Query.filter`.
-
- .. versionadded:: 1.4
-
- .. seealso::
-
- :meth:`_sql.Select.where` - v2 equivalent method.
-
- """
- return self.filter(*criterion)
-
- @_generative
- @_assertions(_no_statement_condition, _no_limit_offset)
- def filter(self, *criterion: _ColumnExpressionArgument[bool]) -> Self:
- r"""Apply the given filtering criterion to a copy
- of this :class:`_query.Query`, using SQL expressions.
-
- e.g.::
-
- session.query(MyClass).filter(MyClass.name == 'some name')
-
- Multiple criteria may be specified as comma separated; the effect
- is that they will be joined together using the :func:`.and_`
- function::
-
- session.query(MyClass).\
- filter(MyClass.name == 'some name', MyClass.id > 5)
-
- The criterion is any SQL expression object applicable to the
- WHERE clause of a select. String expressions are coerced
- into SQL expression constructs via the :func:`_expression.text`
- construct.
-
- .. seealso::
-
- :meth:`_query.Query.filter_by` - filter on keyword expressions.
-
- :meth:`_sql.Select.where` - v2 equivalent method.
-
- """
- for crit in list(criterion):
- crit = coercions.expect(
- roles.WhereHavingRole, crit, apply_propagate_attrs=self
- )
-
- self._where_criteria += (crit,)
- return self
-
- @util.memoized_property
- def _last_joined_entity(
- self,
- ) -> Optional[Union[_InternalEntityType[Any], _JoinTargetElement]]:
- if self._setup_joins:
- return _determine_last_joined_entity(
- self._setup_joins,
- )
- else:
- return None
-
- def _filter_by_zero(self) -> Any:
- """for the filter_by() method, return the target entity for which
- we will attempt to derive an expression from based on string name.
-
- """
-
- if self._setup_joins:
- _last_joined_entity = self._last_joined_entity
- if _last_joined_entity is not None:
- return _last_joined_entity
-
- # discussion related to #7239
- # special check determines if we should try to derive attributes
- # for filter_by() from the "from object", i.e., if the user
- # called query.select_from(some selectable).filter_by(some_attr=value).
- # We don't want to do that in the case that methods like
- # from_self(), select_entity_from(), or a set op like union() were
- # called; while these methods also place a
- # selectable in the _from_obj collection, they also set up
- # the _set_base_alias boolean which turns on the whole "adapt the
- # entity to this selectable" thing, meaning the query still continues
- # to construct itself in terms of the lead entity that was passed
- # to query(), e.g. query(User).from_self() is still in terms of User,
- # and not the subquery that from_self() created. This feature of
- # "implicitly adapt all occurrences of entity X to some arbitrary
- # subquery" is the main thing I am trying to do away with in 2.0 as
- # users should now used aliased() for that, but I can't entirely get
- # rid of it due to query.union() and other set ops relying upon it.
- #
- # compare this to the base Select()._filter_by_zero() which can
- # just return self._from_obj[0] if present, because there is no
- # "_set_base_alias" feature.
- #
- # IOW, this conditional essentially detects if
- # "select_from(some_selectable)" has been called, as opposed to
- # "select_entity_from()", "from_self()"
- # or "union() / some_set_op()".
- if self._from_obj and not self._compile_options._set_base_alias:
- return self._from_obj[0]
-
- return self._raw_columns[0]
-
- def filter_by(self, **kwargs: Any) -> Self:
- r"""Apply the given filtering criterion to a copy
- of this :class:`_query.Query`, using keyword expressions.
-
- e.g.::
-
- session.query(MyClass).filter_by(name = 'some name')
-
- Multiple criteria may be specified as comma separated; the effect
- is that they will be joined together using the :func:`.and_`
- function::
-
- session.query(MyClass).\
- filter_by(name = 'some name', id = 5)
-
- The keyword expressions are extracted from the primary
- entity of the query, or the last entity that was the
- target of a call to :meth:`_query.Query.join`.
-
- .. seealso::
-
- :meth:`_query.Query.filter` - filter on SQL expressions.
-
- :meth:`_sql.Select.filter_by` - v2 comparable method.
-
- """
- from_entity = self._filter_by_zero()
-
- clauses = [
- _entity_namespace_key(from_entity, key) == value
- for key, value in kwargs.items()
- ]
- return self.filter(*clauses)
-
- @_generative
- def order_by(
- self,
- __first: Union[
- Literal[None, False, _NoArg.NO_ARG],
- _ColumnExpressionOrStrLabelArgument[Any],
- ] = _NoArg.NO_ARG,
- *clauses: _ColumnExpressionOrStrLabelArgument[Any],
- ) -> Self:
- """Apply one or more ORDER BY criteria to the query and return
- the newly resulting :class:`_query.Query`.
-
- e.g.::
-
- q = session.query(Entity).order_by(Entity.id, Entity.name)
-
- Calling this method multiple times is equivalent to calling it once
- with all the clauses concatenated. All existing ORDER BY criteria may
- be cancelled by passing ``None`` by itself. New ORDER BY criteria may
- then be added by invoking :meth:`_orm.Query.order_by` again, e.g.::
-
- # will erase all ORDER BY and ORDER BY new_col alone
- q = q.order_by(None).order_by(new_col)
-
- .. seealso::
-
- These sections describe ORDER BY in terms of :term:`2.0 style`
- invocation but apply to :class:`_orm.Query` as well:
-
- :ref:`tutorial_order_by` - in the :ref:`unified_tutorial`
-
- :ref:`tutorial_order_by_label` - in the :ref:`unified_tutorial`
-
- :meth:`_sql.Select.order_by` - v2 equivalent method.
-
- """
-
- for assertion in (self._no_statement_condition, self._no_limit_offset):
- assertion("order_by")
-
- if not clauses and (__first is None or __first is False):
- self._order_by_clauses = ()
- elif __first is not _NoArg.NO_ARG:
- criterion = tuple(
- coercions.expect(roles.OrderByRole, clause)
- for clause in (__first,) + clauses
- )
- self._order_by_clauses += criterion
-
- return self
-
- @_generative
- def group_by(
- self,
- __first: Union[
- Literal[None, False, _NoArg.NO_ARG],
- _ColumnExpressionOrStrLabelArgument[Any],
- ] = _NoArg.NO_ARG,
- *clauses: _ColumnExpressionOrStrLabelArgument[Any],
- ) -> Self:
- """Apply one or more GROUP BY criterion to the query and return
- the newly resulting :class:`_query.Query`.
-
- All existing GROUP BY settings can be suppressed by
- passing ``None`` - this will suppress any GROUP BY configured
- on mappers as well.
-
- .. seealso::
-
- These sections describe GROUP BY in terms of :term:`2.0 style`
- invocation but apply to :class:`_orm.Query` as well:
-
- :ref:`tutorial_group_by_w_aggregates` - in the
- :ref:`unified_tutorial`
-
- :ref:`tutorial_order_by_label` - in the :ref:`unified_tutorial`
-
- :meth:`_sql.Select.group_by` - v2 equivalent method.
-
- """
-
- for assertion in (self._no_statement_condition, self._no_limit_offset):
- assertion("group_by")
-
- if not clauses and (__first is None or __first is False):
- self._group_by_clauses = ()
- elif __first is not _NoArg.NO_ARG:
- criterion = tuple(
- coercions.expect(roles.GroupByRole, clause)
- for clause in (__first,) + clauses
- )
- self._group_by_clauses += criterion
- return self
-
- @_generative
- @_assertions(_no_statement_condition, _no_limit_offset)
- def having(self, *having: _ColumnExpressionArgument[bool]) -> Self:
- r"""Apply a HAVING criterion to the query and return the
- newly resulting :class:`_query.Query`.
-
- :meth:`_query.Query.having` is used in conjunction with
- :meth:`_query.Query.group_by`.
-
- HAVING criterion makes it possible to use filters on aggregate
- functions like COUNT, SUM, AVG, MAX, and MIN, eg.::
-
- q = session.query(User.id).\
- join(User.addresses).\
- group_by(User.id).\
- having(func.count(Address.id) > 2)
-
- .. seealso::
-
- :meth:`_sql.Select.having` - v2 equivalent method.
-
- """
-
- for criterion in having:
- having_criteria = coercions.expect(
- roles.WhereHavingRole, criterion
- )
- self._having_criteria += (having_criteria,)
- return self
-
- def _set_op(self, expr_fn: Any, *q: Query[Any]) -> Self:
- list_of_queries = (self,) + q
- return self._from_selectable(expr_fn(*(list_of_queries)).subquery())
-
- def union(self, *q: Query[Any]) -> Self:
- """Produce a UNION of this Query against one or more queries.
-
- e.g.::
-
- q1 = sess.query(SomeClass).filter(SomeClass.foo=='bar')
- q2 = sess.query(SomeClass).filter(SomeClass.bar=='foo')
-
- q3 = q1.union(q2)
-
- The method accepts multiple Query objects so as to control
- the level of nesting. A series of ``union()`` calls such as::
-
- x.union(y).union(z).all()
-
- will nest on each ``union()``, and produces::
-
- SELECT * FROM (SELECT * FROM (SELECT * FROM X UNION
- SELECT * FROM y) UNION SELECT * FROM Z)
-
- Whereas::
-
- x.union(y, z).all()
-
- produces::
-
- SELECT * FROM (SELECT * FROM X UNION SELECT * FROM y UNION
- SELECT * FROM Z)
-
- Note that many database backends do not allow ORDER BY to
- be rendered on a query called within UNION, EXCEPT, etc.
- To disable all ORDER BY clauses including those configured
- on mappers, issue ``query.order_by(None)`` - the resulting
- :class:`_query.Query` object will not render ORDER BY within
- its SELECT statement.
-
- .. seealso::
-
- :meth:`_sql.Select.union` - v2 equivalent method.
-
- """
- return self._set_op(expression.union, *q)
-
- def union_all(self, *q: Query[Any]) -> Self:
- """Produce a UNION ALL of this Query against one or more queries.
-
- Works the same way as :meth:`~sqlalchemy.orm.query.Query.union`. See
- that method for usage examples.
-
- .. seealso::
-
- :meth:`_sql.Select.union_all` - v2 equivalent method.
-
- """
- return self._set_op(expression.union_all, *q)
-
- def intersect(self, *q: Query[Any]) -> Self:
- """Produce an INTERSECT of this Query against one or more queries.
-
- Works the same way as :meth:`~sqlalchemy.orm.query.Query.union`. See
- that method for usage examples.
-
- .. seealso::
-
- :meth:`_sql.Select.intersect` - v2 equivalent method.
-
- """
- return self._set_op(expression.intersect, *q)
-
- def intersect_all(self, *q: Query[Any]) -> Self:
- """Produce an INTERSECT ALL of this Query against one or more queries.
-
- Works the same way as :meth:`~sqlalchemy.orm.query.Query.union`. See
- that method for usage examples.
-
- .. seealso::
-
- :meth:`_sql.Select.intersect_all` - v2 equivalent method.
-
- """
- return self._set_op(expression.intersect_all, *q)
-
- def except_(self, *q: Query[Any]) -> Self:
- """Produce an EXCEPT of this Query against one or more queries.
-
- Works the same way as :meth:`~sqlalchemy.orm.query.Query.union`. See
- that method for usage examples.
-
- .. seealso::
-
- :meth:`_sql.Select.except_` - v2 equivalent method.
-
- """
- return self._set_op(expression.except_, *q)
-
- def except_all(self, *q: Query[Any]) -> Self:
- """Produce an EXCEPT ALL of this Query against one or more queries.
-
- Works the same way as :meth:`~sqlalchemy.orm.query.Query.union`. See
- that method for usage examples.
-
- .. seealso::
-
- :meth:`_sql.Select.except_all` - v2 equivalent method.
-
- """
- return self._set_op(expression.except_all, *q)
-
- @_generative
- @_assertions(_no_statement_condition, _no_limit_offset)
- def join(
- self,
- target: _JoinTargetArgument,
- onclause: Optional[_OnClauseArgument] = None,
- *,
- isouter: bool = False,
- full: bool = False,
- ) -> Self:
- r"""Create a SQL JOIN against this :class:`_query.Query`
- object's criterion
- and apply generatively, returning the newly resulting
- :class:`_query.Query`.
-
- **Simple Relationship Joins**
-
- Consider a mapping between two classes ``User`` and ``Address``,
- with a relationship ``User.addresses`` representing a collection
- of ``Address`` objects associated with each ``User``. The most
- common usage of :meth:`_query.Query.join`
- is to create a JOIN along this
- relationship, using the ``User.addresses`` attribute as an indicator
- for how this should occur::
-
- q = session.query(User).join(User.addresses)
-
- Where above, the call to :meth:`_query.Query.join` along
- ``User.addresses`` will result in SQL approximately equivalent to::
-
- SELECT user.id, user.name
- FROM user JOIN address ON user.id = address.user_id
-
- In the above example we refer to ``User.addresses`` as passed to
- :meth:`_query.Query.join` as the "on clause", that is, it indicates
- how the "ON" portion of the JOIN should be constructed.
-
- To construct a chain of joins, multiple :meth:`_query.Query.join`
- calls may be used. The relationship-bound attribute implies both
- the left and right side of the join at once::
-
- q = session.query(User).\
- join(User.orders).\
- join(Order.items).\
- join(Item.keywords)
-
- .. note:: as seen in the above example, **the order in which each
- call to the join() method occurs is important**. Query would not,
- for example, know how to join correctly if we were to specify
- ``User``, then ``Item``, then ``Order``, in our chain of joins; in
- such a case, depending on the arguments passed, it may raise an
- error that it doesn't know how to join, or it may produce invalid
- SQL in which case the database will raise an error. In correct
- practice, the
- :meth:`_query.Query.join` method is invoked in such a way that lines
- up with how we would want the JOIN clauses in SQL to be
- rendered, and each call should represent a clear link from what
- precedes it.
-
- **Joins to a Target Entity or Selectable**
-
- A second form of :meth:`_query.Query.join` allows any mapped entity or
- core selectable construct as a target. In this usage,
- :meth:`_query.Query.join` will attempt to create a JOIN along the
- natural foreign key relationship between two entities::
-
- q = session.query(User).join(Address)
-
- In the above calling form, :meth:`_query.Query.join` is called upon to
- create the "on clause" automatically for us. This calling form will
- ultimately raise an error if either there are no foreign keys between
- the two entities, or if there are multiple foreign key linkages between
- the target entity and the entity or entities already present on the
- left side such that creating a join requires more information. Note
- that when indicating a join to a target without any ON clause, ORM
- configured relationships are not taken into account.
-
- **Joins to a Target with an ON Clause**
-
- The third calling form allows both the target entity as well
- as the ON clause to be passed explicitly. A example that includes
- a SQL expression as the ON clause is as follows::
-
- q = session.query(User).join(Address, User.id==Address.user_id)
-
- The above form may also use a relationship-bound attribute as the
- ON clause as well::
-
- q = session.query(User).join(Address, User.addresses)
-
- The above syntax can be useful for the case where we wish
- to join to an alias of a particular target entity. If we wanted
- to join to ``Address`` twice, it could be achieved using two
- aliases set up using the :func:`~sqlalchemy.orm.aliased` function::
-
- a1 = aliased(Address)
- a2 = aliased(Address)
-
- q = session.query(User).\
- join(a1, User.addresses).\
- join(a2, User.addresses).\
- filter(a1.email_address=='ed@foo.com').\
- filter(a2.email_address=='ed@bar.com')
-
- The relationship-bound calling form can also specify a target entity
- using the :meth:`_orm.PropComparator.of_type` method; a query
- equivalent to the one above would be::
-
- a1 = aliased(Address)
- a2 = aliased(Address)
-
- q = session.query(User).\
- join(User.addresses.of_type(a1)).\
- join(User.addresses.of_type(a2)).\
- filter(a1.email_address == 'ed@foo.com').\
- filter(a2.email_address == 'ed@bar.com')
-
- **Augmenting Built-in ON Clauses**
-
- As a substitute for providing a full custom ON condition for an
- existing relationship, the :meth:`_orm.PropComparator.and_` function
- may be applied to a relationship attribute to augment additional
- criteria into the ON clause; the additional criteria will be combined
- with the default criteria using AND::
-
- q = session.query(User).join(
- User.addresses.and_(Address.email_address != 'foo@bar.com')
- )
-
- .. versionadded:: 1.4
-
- **Joining to Tables and Subqueries**
-
-
- The target of a join may also be any table or SELECT statement,
- which may be related to a target entity or not. Use the
- appropriate ``.subquery()`` method in order to make a subquery
- out of a query::
-
- subq = session.query(Address).\
- filter(Address.email_address == 'ed@foo.com').\
- subquery()
-
-
- q = session.query(User).join(
- subq, User.id == subq.c.user_id
- )
-
- Joining to a subquery in terms of a specific relationship and/or
- target entity may be achieved by linking the subquery to the
- entity using :func:`_orm.aliased`::
-
- subq = session.query(Address).\
- filter(Address.email_address == 'ed@foo.com').\
- subquery()
-
- address_subq = aliased(Address, subq)
-
- q = session.query(User).join(
- User.addresses.of_type(address_subq)
- )
-
-
- **Controlling what to Join From**
-
- In cases where the left side of the current state of
- :class:`_query.Query` is not in line with what we want to join from,
- the :meth:`_query.Query.select_from` method may be used::
-
- q = session.query(Address).select_from(User).\
- join(User.addresses).\
- filter(User.name == 'ed')
-
- Which will produce SQL similar to::
-
- SELECT address.* FROM user
- JOIN address ON user.id=address.user_id
- WHERE user.name = :name_1
-
- .. seealso::
-
- :meth:`_sql.Select.join` - v2 equivalent method.
-
- :param \*props: Incoming arguments for :meth:`_query.Query.join`,
- the props collection in modern use should be considered to be a one
- or two argument form, either as a single "target" entity or ORM
- attribute-bound relationship, or as a target entity plus an "on
- clause" which may be a SQL expression or ORM attribute-bound
- relationship.
-
- :param isouter=False: If True, the join used will be a left outer join,
- just as if the :meth:`_query.Query.outerjoin` method were called.
-
- :param full=False: render FULL OUTER JOIN; implies ``isouter``.
-
- """
-
- join_target = coercions.expect(
- roles.JoinTargetRole,
- target,
- apply_propagate_attrs=self,
- legacy=True,
- )
- if onclause is not None:
- onclause_element = coercions.expect(
- roles.OnClauseRole, onclause, legacy=True
- )
- else:
- onclause_element = None
-
- self._setup_joins += (
- (
- join_target,
- onclause_element,
- None,
- {
- "isouter": isouter,
- "full": full,
- },
- ),
- )
-
- self.__dict__.pop("_last_joined_entity", None)
- return self
-
- def outerjoin(
- self,
- target: _JoinTargetArgument,
- onclause: Optional[_OnClauseArgument] = None,
- *,
- full: bool = False,
- ) -> Self:
- """Create a left outer join against this ``Query`` object's criterion
- and apply generatively, returning the newly resulting ``Query``.
-
- Usage is the same as the ``join()`` method.
-
- .. seealso::
-
- :meth:`_sql.Select.outerjoin` - v2 equivalent method.
-
- """
- return self.join(target, onclause=onclause, isouter=True, full=full)
-
- @_generative
- @_assertions(_no_statement_condition)
- def reset_joinpoint(self) -> Self:
- """Return a new :class:`.Query`, where the "join point" has
- been reset back to the base FROM entities of the query.
-
- This method is usually used in conjunction with the
- ``aliased=True`` feature of the :meth:`~.Query.join`
- method. See the example in :meth:`~.Query.join` for how
- this is used.
-
- """
- self._last_joined_entity = None
-
- return self
-
- @_generative
- @_assertions(_no_clauseelement_condition)
- def select_from(self, *from_obj: _FromClauseArgument) -> Self:
- r"""Set the FROM clause of this :class:`.Query` explicitly.
-
- :meth:`.Query.select_from` is often used in conjunction with
- :meth:`.Query.join` in order to control which entity is selected
- from on the "left" side of the join.
-
- The entity or selectable object here effectively replaces the
- "left edge" of any calls to :meth:`~.Query.join`, when no
- joinpoint is otherwise established - usually, the default "join
- point" is the leftmost entity in the :class:`~.Query` object's
- list of entities to be selected.
-
- A typical example::
-
- q = session.query(Address).select_from(User).\
- join(User.addresses).\
- filter(User.name == 'ed')
-
- Which produces SQL equivalent to::
-
- SELECT address.* FROM user
- JOIN address ON user.id=address.user_id
- WHERE user.name = :name_1
-
- :param \*from_obj: collection of one or more entities to apply
- to the FROM clause. Entities can be mapped classes,
- :class:`.AliasedClass` objects, :class:`.Mapper` objects
- as well as core :class:`.FromClause` elements like subqueries.
-
- .. seealso::
-
- :meth:`~.Query.join`
-
- :meth:`.Query.select_entity_from`
-
- :meth:`_sql.Select.select_from` - v2 equivalent method.
-
- """
-
- self._set_select_from(from_obj, False)
- return self
-
- def __getitem__(self, item: Any) -> Any:
- return orm_util._getitem(
- self,
- item,
- )
-
- @_generative
- @_assertions(_no_statement_condition)
- def slice(
- self,
- start: int,
- stop: int,
- ) -> Self:
- """Computes the "slice" of the :class:`_query.Query` represented by
- the given indices and returns the resulting :class:`_query.Query`.
-
- The start and stop indices behave like the argument to Python's
- built-in :func:`range` function. This method provides an
- alternative to using ``LIMIT``/``OFFSET`` to get a slice of the
- query.
-
- For example, ::
-
- session.query(User).order_by(User.id).slice(1, 3)
-
- renders as
-
- .. sourcecode:: sql
-
- SELECT users.id AS users_id,
- users.name AS users_name
- FROM users ORDER BY users.id
- LIMIT ? OFFSET ?
- (2, 1)
-
- .. seealso::
-
- :meth:`_query.Query.limit`
-
- :meth:`_query.Query.offset`
-
- :meth:`_sql.Select.slice` - v2 equivalent method.
-
- """
-
- self._limit_clause, self._offset_clause = sql_util._make_slice(
- self._limit_clause, self._offset_clause, start, stop
- )
- return self
-
- @_generative
- @_assertions(_no_statement_condition)
- def limit(self, limit: _LimitOffsetType) -> Self:
- """Apply a ``LIMIT`` to the query and return the newly resulting
- ``Query``.
-
- .. seealso::
-
- :meth:`_sql.Select.limit` - v2 equivalent method.
-
- """
- self._limit_clause = sql_util._offset_or_limit_clause(limit)
- return self
-
- @_generative
- @_assertions(_no_statement_condition)
- def offset(self, offset: _LimitOffsetType) -> Self:
- """Apply an ``OFFSET`` to the query and return the newly resulting
- ``Query``.
-
- .. seealso::
-
- :meth:`_sql.Select.offset` - v2 equivalent method.
- """
- self._offset_clause = sql_util._offset_or_limit_clause(offset)
- return self
-
- @_generative
- @_assertions(_no_statement_condition)
- def distinct(self, *expr: _ColumnExpressionArgument[Any]) -> Self:
- r"""Apply a ``DISTINCT`` to the query and return the newly resulting
- ``Query``.
-
-
- .. note::
-
- The ORM-level :meth:`.distinct` call includes logic that will
- automatically add columns from the ORDER BY of the query to the
- columns clause of the SELECT statement, to satisfy the common need
- of the database backend that ORDER BY columns be part of the SELECT
- list when DISTINCT is used. These columns *are not* added to the
- list of columns actually fetched by the :class:`_query.Query`,
- however,
- so would not affect results. The columns are passed through when
- using the :attr:`_query.Query.statement` accessor, however.
-
- .. deprecated:: 2.0 This logic is deprecated and will be removed
- in SQLAlchemy 2.0. See :ref:`migration_20_query_distinct`
- for a description of this use case in 2.0.
-
- .. seealso::
-
- :meth:`_sql.Select.distinct` - v2 equivalent method.
-
- :param \*expr: optional column expressions. When present,
- the PostgreSQL dialect will render a ``DISTINCT ON (<expressions>)``
- construct.
-
- .. deprecated:: 1.4 Using \*expr in other dialects is deprecated
- and will raise :class:`_exc.CompileError` in a future version.
-
- """
- if expr:
- self._distinct = True
- self._distinct_on = self._distinct_on + tuple(
- coercions.expect(roles.ByOfRole, e) for e in expr
- )
- else:
- self._distinct = True
- return self
-
- def all(self) -> List[_T]:
- """Return the results represented by this :class:`_query.Query`
- as a list.
-
- This results in an execution of the underlying SQL statement.
-
- .. warning:: The :class:`_query.Query` object,
- when asked to return either
- a sequence or iterator that consists of full ORM-mapped entities,
- will **deduplicate entries based on primary key**. See the FAQ for
- more details.
-
- .. seealso::
-
- :ref:`faq_query_deduplicating`
-
- .. seealso::
-
- :meth:`_engine.Result.all` - v2 comparable method.
-
- :meth:`_engine.Result.scalars` - v2 comparable method.
- """
- return self._iter().all() # type: ignore
-
- @_generative
- @_assertions(_no_clauseelement_condition)
- def from_statement(self, statement: ExecutableReturnsRows) -> Self:
- """Execute the given SELECT statement and return results.
-
- This method bypasses all internal statement compilation, and the
- statement is executed without modification.
-
- The statement is typically either a :func:`_expression.text`
- or :func:`_expression.select` construct, and should return the set
- of columns
- appropriate to the entity class represented by this
- :class:`_query.Query`.
-
- .. seealso::
-
- :meth:`_sql.Select.from_statement` - v2 comparable method.
-
- """
- statement = coercions.expect(
- roles.SelectStatementRole, statement, apply_propagate_attrs=self
- )
- self._statement = statement
- return self
-
- def first(self) -> Optional[_T]:
- """Return the first result of this ``Query`` or
- None if the result doesn't contain any row.
-
- first() applies a limit of one within the generated SQL, so that
- only one primary entity row is generated on the server side
- (note this may consist of multiple result rows if join-loaded
- collections are present).
-
- Calling :meth:`_query.Query.first`
- results in an execution of the underlying
- query.
-
- .. seealso::
-
- :meth:`_query.Query.one`
-
- :meth:`_query.Query.one_or_none`
-
- :meth:`_engine.Result.first` - v2 comparable method.
-
- :meth:`_engine.Result.scalars` - v2 comparable method.
-
- """
- # replicates limit(1) behavior
- if self._statement is not None:
- return self._iter().first() # type: ignore
- else:
- return self.limit(1)._iter().first() # type: ignore
-
- def one_or_none(self) -> Optional[_T]:
- """Return at most one result or raise an exception.
-
- Returns ``None`` if the query selects
- no rows. Raises ``sqlalchemy.orm.exc.MultipleResultsFound``
- if multiple object identities are returned, or if multiple
- rows are returned for a query that returns only scalar values
- as opposed to full identity-mapped entities.
-
- Calling :meth:`_query.Query.one_or_none`
- results in an execution of the
- underlying query.
-
- .. seealso::
-
- :meth:`_query.Query.first`
-
- :meth:`_query.Query.one`
-
- :meth:`_engine.Result.one_or_none` - v2 comparable method.
-
- :meth:`_engine.Result.scalar_one_or_none` - v2 comparable method.
-
- """
- return self._iter().one_or_none() # type: ignore
-
- def one(self) -> _T:
- """Return exactly one result or raise an exception.
-
- Raises ``sqlalchemy.orm.exc.NoResultFound`` if the query selects
- no rows. Raises ``sqlalchemy.orm.exc.MultipleResultsFound``
- if multiple object identities are returned, or if multiple
- rows are returned for a query that returns only scalar values
- as opposed to full identity-mapped entities.
-
- Calling :meth:`.one` results in an execution of the underlying query.
-
- .. seealso::
-
- :meth:`_query.Query.first`
-
- :meth:`_query.Query.one_or_none`
-
- :meth:`_engine.Result.one` - v2 comparable method.
-
- :meth:`_engine.Result.scalar_one` - v2 comparable method.
-
- """
- return self._iter().one() # type: ignore
-
- def scalar(self) -> Any:
- """Return the first element of the first result or None
- if no rows present. If multiple rows are returned,
- raises MultipleResultsFound.
-
- >>> session.query(Item).scalar()
- <Item>
- >>> session.query(Item.id).scalar()
- 1
- >>> session.query(Item.id).filter(Item.id < 0).scalar()
- None
- >>> session.query(Item.id, Item.name).scalar()
- 1
- >>> session.query(func.count(Parent.id)).scalar()
- 20
-
- This results in an execution of the underlying query.
-
- .. seealso::
-
- :meth:`_engine.Result.scalar` - v2 comparable method.
-
- """
- # TODO: not sure why we can't use result.scalar() here
- try:
- ret = self.one()
- if not isinstance(ret, collections_abc.Sequence):
- return ret
- return ret[0]
- except sa_exc.NoResultFound:
- return None
-
- def __iter__(self) -> Iterator[_T]:
- result = self._iter()
- try:
- yield from result # type: ignore
- except GeneratorExit:
- # issue #8710 - direct iteration is not re-usable after
- # an iterable block is broken, so close the result
- result._soft_close()
- raise
-
- def _iter(self) -> Union[ScalarResult[_T], Result[_T]]:
- # new style execution.
- params = self._params
-
- statement = self._statement_20()
- result: Union[ScalarResult[_T], Result[_T]] = self.session.execute(
- statement,
- params,
- execution_options={"_sa_orm_load_options": self.load_options},
- )
-
- # legacy: automatically set scalars, unique
- if result._attributes.get("is_single_entity", False):
- result = cast("Result[_T]", result).scalars()
-
- if (
- result._attributes.get("filtered", False)
- and not self.load_options._yield_per
- ):
- result = result.unique()
-
- return result
-
- def __str__(self) -> str:
- statement = self._statement_20()
-
- try:
- bind = (
- self._get_bind_args(statement, self.session.get_bind)
- if self.session
- else None
- )
- except sa_exc.UnboundExecutionError:
- bind = None
-
- return str(statement.compile(bind))
-
- def _get_bind_args(self, statement: Any, fn: Any, **kw: Any) -> Any:
- return fn(clause=statement, **kw)
-
- @property
- def column_descriptions(self) -> List[ORMColumnDescription]:
- """Return metadata about the columns which would be
- returned by this :class:`_query.Query`.
-
- Format is a list of dictionaries::
-
- user_alias = aliased(User, name='user2')
- q = sess.query(User, User.id, user_alias)
-
- # this expression:
- q.column_descriptions
-
- # would return:
- [
- {
- 'name':'User',
- 'type':User,
- 'aliased':False,
- 'expr':User,
- 'entity': User
- },
- {
- 'name':'id',
- 'type':Integer(),
- 'aliased':False,
- 'expr':User.id,
- 'entity': User
- },
- {
- 'name':'user2',
- 'type':User,
- 'aliased':True,
- 'expr':user_alias,
- 'entity': user_alias
- }
- ]
-
- .. seealso::
-
- This API is available using :term:`2.0 style` queries as well,
- documented at:
-
- * :ref:`queryguide_inspection`
-
- * :attr:`.Select.column_descriptions`
-
- """
-
- return _column_descriptions(self, legacy=True)
-
- @util.deprecated(
- "2.0",
- "The :meth:`_orm.Query.instances` method is deprecated and will "
- "be removed in a future release. "
- "Use the Select.from_statement() method or aliased() construct in "
- "conjunction with Session.execute() instead.",
- )
- def instances(
- self,
- result_proxy: CursorResult[Any],
- context: Optional[QueryContext] = None,
- ) -> Any:
- """Return an ORM result given a :class:`_engine.CursorResult` and
- :class:`.QueryContext`.
-
- """
- if context is None:
- util.warn_deprecated(
- "Using the Query.instances() method without a context "
- "is deprecated and will be disallowed in a future release. "
- "Please make use of :meth:`_query.Query.from_statement` "
- "for linking ORM results to arbitrary select constructs.",
- version="1.4",
- )
- compile_state = self._compile_state(for_statement=False)
-
- context = QueryContext(
- compile_state,
- compile_state.statement,
- self._params,
- self.session,
- self.load_options,
- )
-
- result = loading.instances(result_proxy, context)
-
- # legacy: automatically set scalars, unique
- if result._attributes.get("is_single_entity", False):
- result = result.scalars() # type: ignore
-
- if result._attributes.get("filtered", False):
- result = result.unique()
-
- # TODO: isn't this supposed to be a list?
- return result
-
- @util.became_legacy_20(
- ":meth:`_orm.Query.merge_result`",
- alternative="The method is superseded by the "
- ":func:`_orm.merge_frozen_result` function.",
- enable_warnings=False, # warnings occur via loading.merge_result
- )
- def merge_result(
- self,
- iterator: Union[
- FrozenResult[Any], Iterable[Sequence[Any]], Iterable[object]
- ],
- load: bool = True,
- ) -> Union[FrozenResult[Any], Iterable[Any]]:
- """Merge a result into this :class:`_query.Query` object's Session.
-
- Given an iterator returned by a :class:`_query.Query`
- of the same structure
- as this one, return an identical iterator of results, with all mapped
- instances merged into the session using :meth:`.Session.merge`. This
- is an optimized method which will merge all mapped instances,
- preserving the structure of the result rows and unmapped columns with
- less method overhead than that of calling :meth:`.Session.merge`
- explicitly for each value.
-
- The structure of the results is determined based on the column list of
- this :class:`_query.Query` - if these do not correspond,
- unchecked errors
- will occur.
-
- The 'load' argument is the same as that of :meth:`.Session.merge`.
-
- For an example of how :meth:`_query.Query.merge_result` is used, see
- the source code for the example :ref:`examples_caching`, where
- :meth:`_query.Query.merge_result` is used to efficiently restore state
- from a cache back into a target :class:`.Session`.
-
- """
-
- return loading.merge_result(self, iterator, load)
-
- def exists(self) -> Exists:
- """A convenience method that turns a query into an EXISTS subquery
- of the form EXISTS (SELECT 1 FROM ... WHERE ...).
-
- e.g.::
-
- q = session.query(User).filter(User.name == 'fred')
- session.query(q.exists())
-
- Producing SQL similar to::
-
- SELECT EXISTS (
- SELECT 1 FROM users WHERE users.name = :name_1
- ) AS anon_1
-
- The EXISTS construct is usually used in the WHERE clause::
-
- session.query(User.id).filter(q.exists()).scalar()
-
- Note that some databases such as SQL Server don't allow an
- EXISTS expression to be present in the columns clause of a
- SELECT. To select a simple boolean value based on the exists
- as a WHERE, use :func:`.literal`::
-
- from sqlalchemy import literal
-
- session.query(literal(True)).filter(q.exists()).scalar()
-
- .. seealso::
-
- :meth:`_sql.Select.exists` - v2 comparable method.
-
- """
-
- # .add_columns() for the case that we are a query().select_from(X),
- # so that ".statement" can be produced (#2995) but also without
- # omitting the FROM clause from a query(X) (#2818);
- # .with_only_columns() after we have a core select() so that
- # we get just "SELECT 1" without any entities.
-
- inner = (
- self.enable_eagerloads(False)
- .add_columns(sql.literal_column("1"))
- .set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
- ._get_select_statement_only()
- .with_only_columns(1)
- )
-
- ezero = self._entity_from_pre_ent_zero()
- if ezero is not None:
- inner = inner.select_from(ezero)
-
- return sql.exists(inner)
-
- def count(self) -> int:
- r"""Return a count of rows this the SQL formed by this :class:`Query`
- would return.
-
- This generates the SQL for this Query as follows::
-
- SELECT count(1) AS count_1 FROM (
- SELECT <rest of query follows...>
- ) AS anon_1
-
- The above SQL returns a single row, which is the aggregate value
- of the count function; the :meth:`_query.Query.count`
- method then returns
- that single integer value.
-
- .. warning::
-
- It is important to note that the value returned by
- count() is **not the same as the number of ORM objects that this
- Query would return from a method such as the .all() method**.
- The :class:`_query.Query` object,
- when asked to return full entities,
- will **deduplicate entries based on primary key**, meaning if the
- same primary key value would appear in the results more than once,
- only one object of that primary key would be present. This does
- not apply to a query that is against individual columns.
-
- .. seealso::
-
- :ref:`faq_query_deduplicating`
-
- For fine grained control over specific columns to count, to skip the
- usage of a subquery or otherwise control of the FROM clause, or to use
- other aggregate functions, use :attr:`~sqlalchemy.sql.expression.func`
- expressions in conjunction with :meth:`~.Session.query`, i.e.::
-
- from sqlalchemy import func
-
- # count User records, without
- # using a subquery.
- session.query(func.count(User.id))
-
- # return count of user "id" grouped
- # by "name"
- session.query(func.count(User.id)).\
- group_by(User.name)
-
- from sqlalchemy import distinct
-
- # count distinct "name" values
- session.query(func.count(distinct(User.name)))
-
- .. seealso::
-
- :ref:`migration_20_query_usage`
-
- """
- col = sql.func.count(sql.literal_column("*"))
- return ( # type: ignore
- self._legacy_from_self(col).enable_eagerloads(False).scalar()
- )
-
- def delete(
- self, synchronize_session: SynchronizeSessionArgument = "auto"
- ) -> int:
- r"""Perform a DELETE with an arbitrary WHERE clause.
-
- Deletes rows matched by this query from the database.
-
- E.g.::
-
- sess.query(User).filter(User.age == 25).\
- delete(synchronize_session=False)
-
- sess.query(User).filter(User.age == 25).\
- delete(synchronize_session='evaluate')
-
- .. warning::
-
- See the section :ref:`orm_expression_update_delete` for important
- caveats and warnings, including limitations when using bulk UPDATE
- and DELETE with mapper inheritance configurations.
-
- :param synchronize_session: chooses the strategy to update the
- attributes on objects in the session. See the section
- :ref:`orm_expression_update_delete` for a discussion of these
- strategies.
-
- :return: the count of rows matched as returned by the database's
- "row count" feature.
-
- .. seealso::
-
- :ref:`orm_expression_update_delete`
-
- """
-
- bulk_del = BulkDelete(self)
- if self.dispatch.before_compile_delete:
- for fn in self.dispatch.before_compile_delete:
- new_query = fn(bulk_del.query, bulk_del)
- if new_query is not None:
- bulk_del.query = new_query
-
- self = bulk_del.query
-
- delete_ = sql.delete(*self._raw_columns) # type: ignore
- delete_._where_criteria = self._where_criteria
- result: CursorResult[Any] = self.session.execute(
- delete_,
- self._params,
- execution_options=self._execution_options.union(
- {"synchronize_session": synchronize_session}
- ),
- )
- bulk_del.result = result # type: ignore
- self.session.dispatch.after_bulk_delete(bulk_del)
- result.close()
-
- return result.rowcount
-
- def update(
- self,
- values: Dict[_DMLColumnArgument, Any],
- synchronize_session: SynchronizeSessionArgument = "auto",
- update_args: Optional[Dict[Any, Any]] = None,
- ) -> int:
- r"""Perform an UPDATE with an arbitrary WHERE clause.
-
- Updates rows matched by this query in the database.
-
- E.g.::
-
- sess.query(User).filter(User.age == 25).\
- update({User.age: User.age - 10}, synchronize_session=False)
-
- sess.query(User).filter(User.age == 25).\
- update({"age": User.age - 10}, synchronize_session='evaluate')
-
- .. warning::
-
- See the section :ref:`orm_expression_update_delete` for important
- caveats and warnings, including limitations when using arbitrary
- UPDATE and DELETE with mapper inheritance configurations.
-
- :param values: a dictionary with attributes names, or alternatively
- mapped attributes or SQL expressions, as keys, and literal
- values or sql expressions as values. If :ref:`parameter-ordered
- mode <tutorial_parameter_ordered_updates>` is desired, the values can
- be passed as a list of 2-tuples; this requires that the
- :paramref:`~sqlalchemy.sql.expression.update.preserve_parameter_order`
- flag is passed to the :paramref:`.Query.update.update_args` dictionary
- as well.
-
- :param synchronize_session: chooses the strategy to update the
- attributes on objects in the session. See the section
- :ref:`orm_expression_update_delete` for a discussion of these
- strategies.
-
- :param update_args: Optional dictionary, if present will be passed
- to the underlying :func:`_expression.update`
- construct as the ``**kw`` for
- the object. May be used to pass dialect-specific arguments such
- as ``mysql_limit``, as well as other special arguments such as
- :paramref:`~sqlalchemy.sql.expression.update.preserve_parameter_order`.
-
- :return: the count of rows matched as returned by the database's
- "row count" feature.
-
-
- .. seealso::
-
- :ref:`orm_expression_update_delete`
-
- """
-
- update_args = update_args or {}
-
- bulk_ud = BulkUpdate(self, values, update_args)
-
- if self.dispatch.before_compile_update:
- for fn in self.dispatch.before_compile_update:
- new_query = fn(bulk_ud.query, bulk_ud)
- if new_query is not None:
- bulk_ud.query = new_query
- self = bulk_ud.query
-
- upd = sql.update(*self._raw_columns) # type: ignore
-
- ppo = update_args.pop("preserve_parameter_order", False)
- if ppo:
- upd = upd.ordered_values(*values) # type: ignore
- else:
- upd = upd.values(values)
- if update_args:
- upd = upd.with_dialect_options(**update_args)
-
- upd._where_criteria = self._where_criteria
- result: CursorResult[Any] = self.session.execute(
- upd,
- self._params,
- execution_options=self._execution_options.union(
- {"synchronize_session": synchronize_session}
- ),
- )
- bulk_ud.result = result # type: ignore
- self.session.dispatch.after_bulk_update(bulk_ud)
- result.close()
- return result.rowcount
-
- def _compile_state(
- self, for_statement: bool = False, **kw: Any
- ) -> ORMCompileState:
- """Create an out-of-compiler ORMCompileState object.
-
- The ORMCompileState object is normally created directly as a result
- of the SQLCompiler.process() method being handed a Select()
- or FromStatement() object that uses the "orm" plugin. This method
- provides a means of creating this ORMCompileState object directly
- without using the compiler.
-
- This method is used only for deprecated cases, which include
- the .from_self() method for a Query that has multiple levels
- of .from_self() in use, as well as the instances() method. It is
- also used within the test suite to generate ORMCompileState objects
- for test purposes.
-
- """
-
- stmt = self._statement_20(for_statement=for_statement, **kw)
- assert for_statement == stmt._compile_options._for_statement
-
- # this chooses between ORMFromStatementCompileState and
- # ORMSelectCompileState. We could also base this on
- # query._statement is not None as we have the ORM Query here
- # however this is the more general path.
- compile_state_cls = cast(
- ORMCompileState,
- ORMCompileState._get_plugin_class_for_plugin(stmt, "orm"),
- )
-
- return compile_state_cls.create_for_statement(stmt, None)
-
- def _compile_context(self, for_statement: bool = False) -> QueryContext:
- compile_state = self._compile_state(for_statement=for_statement)
- context = QueryContext(
- compile_state,
- compile_state.statement,
- self._params,
- self.session,
- self.load_options,
- )
-
- return context
-
-
-class AliasOption(interfaces.LoaderOption):
- inherit_cache = False
-
- @util.deprecated(
- "1.4",
- "The :class:`.AliasOption` object is not necessary "
- "for entities to be matched up to a query that is established "
- "via :meth:`.Query.from_statement` and now does nothing.",
- )
- def __init__(self, alias: Union[Alias, Subquery]):
- r"""Return a :class:`.MapperOption` that will indicate to the
- :class:`_query.Query`
- that the main table has been aliased.
-
- """
-
- def process_compile_state(self, compile_state: ORMCompileState) -> None:
- pass
-
-
-class BulkUD:
- """State used for the orm.Query version of update() / delete().
-
- This object is now specific to Query only.
-
- """
-
- def __init__(self, query: Query[Any]):
- self.query = query.enable_eagerloads(False)
- self._validate_query_state()
- self.mapper = self.query._entity_from_pre_ent_zero()
-
- def _validate_query_state(self) -> None:
- for attr, methname, notset, op in (
- ("_limit_clause", "limit()", None, operator.is_),
- ("_offset_clause", "offset()", None, operator.is_),
- ("_order_by_clauses", "order_by()", (), operator.eq),
- ("_group_by_clauses", "group_by()", (), operator.eq),
- ("_distinct", "distinct()", False, operator.is_),
- (
- "_from_obj",
- "join(), outerjoin(), select_from(), or from_self()",
- (),
- operator.eq,
- ),
- (
- "_setup_joins",
- "join(), outerjoin(), select_from(), or from_self()",
- (),
- operator.eq,
- ),
- ):
- if not op(getattr(self.query, attr), notset):
- raise sa_exc.InvalidRequestError(
- "Can't call Query.update() or Query.delete() "
- "when %s has been called" % (methname,)
- )
-
- @property
- def session(self) -> Session:
- return self.query.session
-
-
-class BulkUpdate(BulkUD):
- """BulkUD which handles UPDATEs."""
-
- def __init__(
- self,
- query: Query[Any],
- values: Dict[_DMLColumnArgument, Any],
- update_kwargs: Optional[Dict[Any, Any]],
- ):
- super().__init__(query)
- self.values = values
- self.update_kwargs = update_kwargs
-
-
-class BulkDelete(BulkUD):
- """BulkUD which handles DELETEs."""
-
-
-class RowReturningQuery(Query[Row[_TP]]):
- if TYPE_CHECKING:
-
- def tuples(self) -> Query[_TP]: # type: ignore
- ...
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py
deleted file mode 100644
index b5e33ff..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py
+++ /dev/null
@@ -1,3500 +0,0 @@
-# orm/relationships.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
-
-"""Heuristics related to join conditions as used in
-:func:`_orm.relationship`.
-
-Provides the :class:`.JoinCondition` object, which encapsulates
-SQL annotation and aliasing behavior focused on the `primaryjoin`
-and `secondaryjoin` aspects of :func:`_orm.relationship`.
-
-"""
-from __future__ import annotations
-
-import collections
-from collections import abc
-import dataclasses
-import inspect as _py_inspect
-import itertools
-import re
-import typing
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Collection
-from typing import Dict
-from typing import FrozenSet
-from typing import Generic
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import NamedTuple
-from typing import NoReturn
-from typing import Optional
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TypeVar
-from typing import Union
-import weakref
-
-from . import attributes
-from . import strategy_options
-from ._typing import insp_is_aliased_class
-from ._typing import is_has_collection_adapter
-from .base import _DeclarativeMapped
-from .base import _is_mapped_class
-from .base import class_mapper
-from .base import DynamicMapped
-from .base import LoaderCallableStatus
-from .base import PassiveFlag
-from .base import state_str
-from .base import WriteOnlyMapped
-from .interfaces import _AttributeOptions
-from .interfaces import _IntrospectsAnnotations
-from .interfaces import MANYTOMANY
-from .interfaces import MANYTOONE
-from .interfaces import ONETOMANY
-from .interfaces import PropComparator
-from .interfaces import RelationshipDirection
-from .interfaces import StrategizedProperty
-from .util import _orm_annotate
-from .util import _orm_deannotate
-from .util import CascadeOptions
-from .. import exc as sa_exc
-from .. import Exists
-from .. import log
-from .. import schema
-from .. import sql
-from .. import util
-from ..inspection import inspect
-from ..sql import coercions
-from ..sql import expression
-from ..sql import operators
-from ..sql import roles
-from ..sql import visitors
-from ..sql._typing import _ColumnExpressionArgument
-from ..sql._typing import _HasClauseElement
-from ..sql.annotation import _safe_annotate
-from ..sql.elements import ColumnClause
-from ..sql.elements import ColumnElement
-from ..sql.util import _deep_annotate
-from ..sql.util import _deep_deannotate
-from ..sql.util import _shallow_annotate
-from ..sql.util import adapt_criterion_to_null
-from ..sql.util import ClauseAdapter
-from ..sql.util import join_condition
-from ..sql.util import selectables_overlap
-from ..sql.util import visit_binary_product
-from ..util.typing import de_optionalize_union_types
-from ..util.typing import Literal
-from ..util.typing import resolve_name_to_real_class_name
-
-if typing.TYPE_CHECKING:
- from ._typing import _EntityType
- from ._typing import _ExternalEntityType
- from ._typing import _IdentityKeyType
- from ._typing import _InstanceDict
- from ._typing import _InternalEntityType
- from ._typing import _O
- from ._typing import _RegistryType
- from .base import Mapped
- from .clsregistry import _class_resolver
- from .clsregistry import _ModNS
- from .decl_base import _ClassScanMapperConfig
- from .dependency import DependencyProcessor
- from .mapper import Mapper
- from .query import Query
- from .session import Session
- from .state import InstanceState
- from .strategies import LazyLoader
- from .util import AliasedClass
- from .util import AliasedInsp
- from ..sql._typing import _CoreAdapterProto
- from ..sql._typing import _EquivalentColumnMap
- from ..sql._typing import _InfoType
- from ..sql.annotation import _AnnotationDict
- from ..sql.annotation import SupportsAnnotations
- from ..sql.elements import BinaryExpression
- from ..sql.elements import BindParameter
- from ..sql.elements import ClauseElement
- from ..sql.schema import Table
- from ..sql.selectable import FromClause
- from ..util.typing import _AnnotationScanType
- from ..util.typing import RODescriptorReference
-
-_T = TypeVar("_T", bound=Any)
-_T1 = TypeVar("_T1", bound=Any)
-_T2 = TypeVar("_T2", bound=Any)
-
-_PT = TypeVar("_PT", bound=Any)
-
-_PT2 = TypeVar("_PT2", bound=Any)
-
-
-_RelationshipArgumentType = Union[
- str,
- Type[_T],
- Callable[[], Type[_T]],
- "Mapper[_T]",
- "AliasedClass[_T]",
- Callable[[], "Mapper[_T]"],
- Callable[[], "AliasedClass[_T]"],
-]
-
-_LazyLoadArgumentType = Literal[
- "select",
- "joined",
- "selectin",
- "subquery",
- "raise",
- "raise_on_sql",
- "noload",
- "immediate",
- "write_only",
- "dynamic",
- True,
- False,
- None,
-]
-
-
-_RelationshipJoinConditionArgument = Union[
- str, _ColumnExpressionArgument[bool]
-]
-_RelationshipSecondaryArgument = Union[
- "FromClause", str, Callable[[], "FromClause"]
-]
-_ORMOrderByArgument = Union[
- Literal[False],
- str,
- _ColumnExpressionArgument[Any],
- Callable[[], _ColumnExpressionArgument[Any]],
- Callable[[], Iterable[_ColumnExpressionArgument[Any]]],
- Iterable[Union[str, _ColumnExpressionArgument[Any]]],
-]
-ORMBackrefArgument = Union[str, Tuple[str, Dict[str, Any]]]
-
-_ORMColCollectionElement = Union[
- ColumnClause[Any],
- _HasClauseElement[Any],
- roles.DMLColumnRole,
- "Mapped[Any]",
-]
-_ORMColCollectionArgument = Union[
- str,
- Sequence[_ORMColCollectionElement],
- Callable[[], Sequence[_ORMColCollectionElement]],
- Callable[[], _ORMColCollectionElement],
- _ORMColCollectionElement,
-]
-
-
-_CEA = TypeVar("_CEA", bound=_ColumnExpressionArgument[Any])
-
-_CE = TypeVar("_CE", bound="ColumnElement[Any]")
-
-
-_ColumnPairIterable = Iterable[Tuple[ColumnElement[Any], ColumnElement[Any]]]
-
-_ColumnPairs = Sequence[Tuple[ColumnElement[Any], ColumnElement[Any]]]
-
-_MutableColumnPairs = List[Tuple[ColumnElement[Any], ColumnElement[Any]]]
-
-
-def remote(expr: _CEA) -> _CEA:
- """Annotate a portion of a primaryjoin expression
- with a 'remote' annotation.
-
- See the section :ref:`relationship_custom_foreign` for a
- description of use.
-
- .. seealso::
-
- :ref:`relationship_custom_foreign`
-
- :func:`.foreign`
-
- """
- return _annotate_columns( # type: ignore
- coercions.expect(roles.ColumnArgumentRole, expr), {"remote": True}
- )
-
-
-def foreign(expr: _CEA) -> _CEA:
- """Annotate a portion of a primaryjoin expression
- with a 'foreign' annotation.
-
- See the section :ref:`relationship_custom_foreign` for a
- description of use.
-
- .. seealso::
-
- :ref:`relationship_custom_foreign`
-
- :func:`.remote`
-
- """
-
- return _annotate_columns( # type: ignore
- coercions.expect(roles.ColumnArgumentRole, expr), {"foreign": True}
- )
-
-
-@dataclasses.dataclass
-class _RelationshipArg(Generic[_T1, _T2]):
- """stores a user-defined parameter value that must be resolved and
- parsed later at mapper configuration time.
-
- """
-
- __slots__ = "name", "argument", "resolved"
- name: str
- argument: _T1
- resolved: Optional[_T2]
-
- def _is_populated(self) -> bool:
- return self.argument is not None
-
- def _resolve_against_registry(
- self, clsregistry_resolver: Callable[[str, bool], _class_resolver]
- ) -> None:
- attr_value = self.argument
-
- if isinstance(attr_value, str):
- self.resolved = clsregistry_resolver(
- attr_value, self.name == "secondary"
- )()
- elif callable(attr_value) and not _is_mapped_class(attr_value):
- self.resolved = attr_value()
- else:
- self.resolved = attr_value
-
-
-_RelationshipOrderByArg = Union[Literal[False], Tuple[ColumnElement[Any], ...]]
-
-
-class _RelationshipArgs(NamedTuple):
- """stores user-passed parameters that are resolved at mapper configuration
- time.
-
- """
-
- secondary: _RelationshipArg[
- Optional[_RelationshipSecondaryArgument],
- Optional[FromClause],
- ]
- primaryjoin: _RelationshipArg[
- Optional[_RelationshipJoinConditionArgument],
- Optional[ColumnElement[Any]],
- ]
- secondaryjoin: _RelationshipArg[
- Optional[_RelationshipJoinConditionArgument],
- Optional[ColumnElement[Any]],
- ]
- order_by: _RelationshipArg[_ORMOrderByArgument, _RelationshipOrderByArg]
- foreign_keys: _RelationshipArg[
- Optional[_ORMColCollectionArgument], Set[ColumnElement[Any]]
- ]
- remote_side: _RelationshipArg[
- Optional[_ORMColCollectionArgument], Set[ColumnElement[Any]]
- ]
-
-
-@log.class_logger
-class RelationshipProperty(
- _IntrospectsAnnotations, StrategizedProperty[_T], log.Identified
-):
- """Describes an object property that holds a single item or list
- of items that correspond to a related database table.
-
- Public constructor is the :func:`_orm.relationship` function.
-
- .. seealso::
-
- :ref:`relationship_config_toplevel`
-
- """
-
- strategy_wildcard_key = strategy_options._RELATIONSHIP_TOKEN
- inherit_cache = True
- """:meta private:"""
-
- _links_to_entity = True
- _is_relationship = True
-
- _overlaps: Sequence[str]
-
- _lazy_strategy: LazyLoader
-
- _persistence_only = dict(
- passive_deletes=False,
- passive_updates=True,
- enable_typechecks=True,
- active_history=False,
- cascade_backrefs=False,
- )
-
- _dependency_processor: Optional[DependencyProcessor] = None
-
- primaryjoin: ColumnElement[bool]
- secondaryjoin: Optional[ColumnElement[bool]]
- secondary: Optional[FromClause]
- _join_condition: JoinCondition
- order_by: _RelationshipOrderByArg
-
- _user_defined_foreign_keys: Set[ColumnElement[Any]]
- _calculated_foreign_keys: Set[ColumnElement[Any]]
-
- remote_side: Set[ColumnElement[Any]]
- local_columns: Set[ColumnElement[Any]]
-
- synchronize_pairs: _ColumnPairs
- secondary_synchronize_pairs: Optional[_ColumnPairs]
-
- local_remote_pairs: Optional[_ColumnPairs]
-
- direction: RelationshipDirection
-
- _init_args: _RelationshipArgs
-
- def __init__(
- self,
- argument: Optional[_RelationshipArgumentType[_T]] = None,
- secondary: Optional[_RelationshipSecondaryArgument] = None,
- *,
- uselist: Optional[bool] = None,
- collection_class: Optional[
- Union[Type[Collection[Any]], Callable[[], Collection[Any]]]
- ] = None,
- primaryjoin: Optional[_RelationshipJoinConditionArgument] = None,
- secondaryjoin: Optional[_RelationshipJoinConditionArgument] = None,
- back_populates: Optional[str] = None,
- order_by: _ORMOrderByArgument = False,
- backref: Optional[ORMBackrefArgument] = None,
- overlaps: Optional[str] = None,
- post_update: bool = False,
- cascade: str = "save-update, merge",
- viewonly: bool = False,
- attribute_options: Optional[_AttributeOptions] = None,
- lazy: _LazyLoadArgumentType = "select",
- passive_deletes: Union[Literal["all"], bool] = False,
- passive_updates: bool = True,
- active_history: bool = False,
- enable_typechecks: bool = True,
- foreign_keys: Optional[_ORMColCollectionArgument] = None,
- remote_side: Optional[_ORMColCollectionArgument] = None,
- join_depth: Optional[int] = None,
- comparator_factory: Optional[
- Type[RelationshipProperty.Comparator[Any]]
- ] = None,
- single_parent: bool = False,
- innerjoin: bool = False,
- distinct_target_key: Optional[bool] = None,
- load_on_pending: bool = False,
- query_class: Optional[Type[Query[Any]]] = None,
- info: Optional[_InfoType] = None,
- omit_join: Literal[None, False] = None,
- sync_backref: Optional[bool] = None,
- doc: Optional[str] = None,
- bake_queries: Literal[True] = True,
- cascade_backrefs: Literal[False] = False,
- _local_remote_pairs: Optional[_ColumnPairs] = None,
- _legacy_inactive_history_style: bool = False,
- ):
- super().__init__(attribute_options=attribute_options)
-
- self.uselist = uselist
- self.argument = argument
-
- self._init_args = _RelationshipArgs(
- _RelationshipArg("secondary", secondary, None),
- _RelationshipArg("primaryjoin", primaryjoin, None),
- _RelationshipArg("secondaryjoin", secondaryjoin, None),
- _RelationshipArg("order_by", order_by, None),
- _RelationshipArg("foreign_keys", foreign_keys, None),
- _RelationshipArg("remote_side", remote_side, None),
- )
-
- self.post_update = post_update
- self.viewonly = viewonly
- if viewonly:
- self._warn_for_persistence_only_flags(
- passive_deletes=passive_deletes,
- passive_updates=passive_updates,
- enable_typechecks=enable_typechecks,
- active_history=active_history,
- cascade_backrefs=cascade_backrefs,
- )
- if viewonly and sync_backref:
- raise sa_exc.ArgumentError(
- "sync_backref and viewonly cannot both be True"
- )
- self.sync_backref = sync_backref
- self.lazy = lazy
- self.single_parent = single_parent
- self.collection_class = collection_class
- self.passive_deletes = passive_deletes
-
- if cascade_backrefs:
- raise sa_exc.ArgumentError(
- "The 'cascade_backrefs' parameter passed to "
- "relationship() may only be set to False."
- )
-
- self.passive_updates = passive_updates
- self.enable_typechecks = enable_typechecks
- self.query_class = query_class
- self.innerjoin = innerjoin
- self.distinct_target_key = distinct_target_key
- self.doc = doc
- self.active_history = active_history
- self._legacy_inactive_history_style = _legacy_inactive_history_style
-
- self.join_depth = join_depth
- if omit_join:
- util.warn(
- "setting omit_join to True is not supported; selectin "
- "loading of this relationship may not work correctly if this "
- "flag is set explicitly. omit_join optimization is "
- "automatically detected for conditions under which it is "
- "supported."
- )
-
- self.omit_join = omit_join
- self.local_remote_pairs = _local_remote_pairs
- self.load_on_pending = load_on_pending
- self.comparator_factory = (
- comparator_factory or RelationshipProperty.Comparator
- )
- util.set_creation_order(self)
-
- if info is not None:
- self.info.update(info)
-
- self.strategy_key = (("lazy", self.lazy),)
-
- self._reverse_property: Set[RelationshipProperty[Any]] = set()
-
- if overlaps:
- self._overlaps = set(re.split(r"\s*,\s*", overlaps)) # type: ignore # noqa: E501
- else:
- self._overlaps = ()
-
- # mypy ignoring the @property setter
- self.cascade = cascade # type: ignore
-
- self.back_populates = back_populates
-
- if self.back_populates:
- if backref:
- raise sa_exc.ArgumentError(
- "backref and back_populates keyword arguments "
- "are mutually exclusive"
- )
- self.backref = None
- else:
- self.backref = backref
-
- def _warn_for_persistence_only_flags(self, **kw: Any) -> None:
- for k, v in kw.items():
- if v != self._persistence_only[k]:
- # we are warning here rather than warn deprecated as this is a
- # configuration mistake, and Python shows regular warnings more
- # aggressively than deprecation warnings by default. Unlike the
- # case of setting viewonly with cascade, the settings being
- # warned about here are not actively doing the wrong thing
- # against viewonly=True, so it is not as urgent to have these
- # raise an error.
- util.warn(
- "Setting %s on relationship() while also "
- "setting viewonly=True does not make sense, as a "
- "viewonly=True relationship does not perform persistence "
- "operations. This configuration may raise an error "
- "in a future release." % (k,)
- )
-
- def instrument_class(self, mapper: Mapper[Any]) -> None:
- attributes.register_descriptor(
- mapper.class_,
- self.key,
- comparator=self.comparator_factory(self, mapper),
- parententity=mapper,
- doc=self.doc,
- )
-
- class Comparator(util.MemoizedSlots, PropComparator[_PT]):
- """Produce boolean, comparison, and other operators for
- :class:`.RelationshipProperty` attributes.
-
- See the documentation for :class:`.PropComparator` for a brief
- overview of ORM level operator definition.
-
- .. seealso::
-
- :class:`.PropComparator`
-
- :class:`.ColumnProperty.Comparator`
-
- :class:`.ColumnOperators`
-
- :ref:`types_operators`
-
- :attr:`.TypeEngine.comparator_factory`
-
- """
-
- __slots__ = (
- "entity",
- "mapper",
- "property",
- "_of_type",
- "_extra_criteria",
- )
-
- prop: RODescriptorReference[RelationshipProperty[_PT]]
- _of_type: Optional[_EntityType[_PT]]
-
- def __init__(
- self,
- prop: RelationshipProperty[_PT],
- parentmapper: _InternalEntityType[Any],
- adapt_to_entity: Optional[AliasedInsp[Any]] = None,
- of_type: Optional[_EntityType[_PT]] = None,
- extra_criteria: Tuple[ColumnElement[bool], ...] = (),
- ):
- """Construction of :class:`.RelationshipProperty.Comparator`
- is internal to the ORM's attribute mechanics.
-
- """
- self.prop = prop
- self._parententity = parentmapper
- self._adapt_to_entity = adapt_to_entity
- if of_type:
- self._of_type = of_type
- else:
- self._of_type = None
- self._extra_criteria = extra_criteria
-
- def adapt_to_entity(
- self, adapt_to_entity: AliasedInsp[Any]
- ) -> RelationshipProperty.Comparator[Any]:
- return self.__class__(
- self.prop,
- self._parententity,
- adapt_to_entity=adapt_to_entity,
- of_type=self._of_type,
- )
-
- entity: _InternalEntityType[_PT]
- """The target entity referred to by this
- :class:`.RelationshipProperty.Comparator`.
-
- This is either a :class:`_orm.Mapper` or :class:`.AliasedInsp`
- object.
-
- This is the "target" or "remote" side of the
- :func:`_orm.relationship`.
-
- """
-
- mapper: Mapper[_PT]
- """The target :class:`_orm.Mapper` referred to by this
- :class:`.RelationshipProperty.Comparator`.
-
- This is the "target" or "remote" side of the
- :func:`_orm.relationship`.
-
- """
-
- def _memoized_attr_entity(self) -> _InternalEntityType[_PT]:
- if self._of_type:
- return inspect(self._of_type) # type: ignore
- else:
- return self.prop.entity
-
- def _memoized_attr_mapper(self) -> Mapper[_PT]:
- return self.entity.mapper
-
- def _source_selectable(self) -> FromClause:
- if self._adapt_to_entity:
- return self._adapt_to_entity.selectable
- else:
- return self.property.parent._with_polymorphic_selectable
-
- def __clause_element__(self) -> ColumnElement[bool]:
- adapt_from = self._source_selectable()
- if self._of_type:
- of_type_entity = inspect(self._of_type)
- else:
- of_type_entity = None
-
- (
- pj,
- sj,
- source,
- dest,
- secondary,
- target_adapter,
- ) = self.prop._create_joins(
- source_selectable=adapt_from,
- source_polymorphic=True,
- of_type_entity=of_type_entity,
- alias_secondary=True,
- extra_criteria=self._extra_criteria,
- )
- if sj is not None:
- return pj & sj
- else:
- return pj
-
- def of_type(self, class_: _EntityType[Any]) -> PropComparator[_PT]:
- r"""Redefine this object in terms of a polymorphic subclass.
-
- See :meth:`.PropComparator.of_type` for an example.
-
-
- """
- return RelationshipProperty.Comparator(
- self.prop,
- self._parententity,
- adapt_to_entity=self._adapt_to_entity,
- of_type=class_,
- extra_criteria=self._extra_criteria,
- )
-
- def and_(
- self, *criteria: _ColumnExpressionArgument[bool]
- ) -> PropComparator[Any]:
- """Add AND criteria.
-
- See :meth:`.PropComparator.and_` for an example.
-
- .. versionadded:: 1.4
-
- """
- exprs = tuple(
- coercions.expect(roles.WhereHavingRole, clause)
- for clause in util.coerce_generator_arg(criteria)
- )
-
- return RelationshipProperty.Comparator(
- self.prop,
- self._parententity,
- adapt_to_entity=self._adapt_to_entity,
- of_type=self._of_type,
- extra_criteria=self._extra_criteria + exprs,
- )
-
- def in_(self, other: Any) -> NoReturn:
- """Produce an IN clause - this is not implemented
- for :func:`_orm.relationship`-based attributes at this time.
-
- """
- raise NotImplementedError(
- "in_() not yet supported for "
- "relationships. For a simple "
- "many-to-one, use in_() against "
- "the set of foreign key values."
- )
-
- # https://github.com/python/mypy/issues/4266
- __hash__ = None # type: ignore
-
- def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
- """Implement the ``==`` operator.
-
- In a many-to-one context, such as::
-
- MyClass.some_prop == <some object>
-
- this will typically produce a
- clause such as::
-
- mytable.related_id == <some id>
-
- Where ``<some id>`` is the primary key of the given
- object.
-
- The ``==`` operator provides partial functionality for non-
- many-to-one comparisons:
-
- * Comparisons against collections are not supported.
- Use :meth:`~.Relationship.Comparator.contains`.
- * Compared to a scalar one-to-many, will produce a
- clause that compares the target columns in the parent to
- the given target.
- * Compared to a scalar many-to-many, an alias
- of the association table will be rendered as
- well, forming a natural join that is part of the
- main body of the query. This will not work for
- queries that go beyond simple AND conjunctions of
- comparisons, such as those which use OR. Use
- explicit joins, outerjoins, or
- :meth:`~.Relationship.Comparator.has` for
- more comprehensive non-many-to-one scalar
- membership tests.
- * Comparisons against ``None`` given in a one-to-many
- or many-to-many context produce a NOT EXISTS clause.
-
- """
- if other is None or isinstance(other, expression.Null):
- if self.property.direction in [ONETOMANY, MANYTOMANY]:
- return ~self._criterion_exists()
- else:
- return _orm_annotate(
- self.property._optimized_compare(
- None, adapt_source=self.adapter
- )
- )
- elif self.property.uselist:
- raise sa_exc.InvalidRequestError(
- "Can't compare a collection to an object or collection; "
- "use contains() to test for membership."
- )
- else:
- return _orm_annotate(
- self.property._optimized_compare(
- other, adapt_source=self.adapter
- )
- )
-
- def _criterion_exists(
- self,
- criterion: Optional[_ColumnExpressionArgument[bool]] = None,
- **kwargs: Any,
- ) -> Exists:
- where_criteria = (
- coercions.expect(roles.WhereHavingRole, criterion)
- if criterion is not None
- else None
- )
-
- if getattr(self, "_of_type", None):
- info: Optional[_InternalEntityType[Any]] = inspect(
- self._of_type
- )
- assert info is not None
- target_mapper, to_selectable, is_aliased_class = (
- info.mapper,
- info.selectable,
- info.is_aliased_class,
- )
- if self.property._is_self_referential and not is_aliased_class:
- to_selectable = to_selectable._anonymous_fromclause()
-
- single_crit = target_mapper._single_table_criterion
- if single_crit is not None:
- if where_criteria is not None:
- where_criteria = single_crit & where_criteria
- else:
- where_criteria = single_crit
- else:
- is_aliased_class = False
- to_selectable = None
-
- if self.adapter:
- source_selectable = self._source_selectable()
- else:
- source_selectable = None
-
- (
- pj,
- sj,
- source,
- dest,
- secondary,
- target_adapter,
- ) = self.property._create_joins(
- dest_selectable=to_selectable,
- source_selectable=source_selectable,
- )
-
- for k in kwargs:
- crit = getattr(self.property.mapper.class_, k) == kwargs[k]
- if where_criteria is None:
- where_criteria = crit
- else:
- where_criteria = where_criteria & crit
-
- # annotate the *local* side of the join condition, in the case
- # of pj + sj this is the full primaryjoin, in the case of just
- # pj its the local side of the primaryjoin.
- if sj is not None:
- j = _orm_annotate(pj) & sj
- else:
- j = _orm_annotate(pj, exclude=self.property.remote_side)
-
- if (
- where_criteria is not None
- and target_adapter
- and not is_aliased_class
- ):
- # limit this adapter to annotated only?
- where_criteria = target_adapter.traverse(where_criteria)
-
- # only have the "joined left side" of what we
- # return be subject to Query adaption. The right
- # side of it is used for an exists() subquery and
- # should not correlate or otherwise reach out
- # to anything in the enclosing query.
- if where_criteria is not None:
- where_criteria = where_criteria._annotate(
- {"no_replacement_traverse": True}
- )
-
- crit = j & sql.True_._ifnone(where_criteria)
-
- if secondary is not None:
- ex = (
- sql.exists(1)
- .where(crit)
- .select_from(dest, secondary)
- .correlate_except(dest, secondary)
- )
- else:
- ex = (
- sql.exists(1)
- .where(crit)
- .select_from(dest)
- .correlate_except(dest)
- )
- return ex
-
- def any(
- self,
- criterion: Optional[_ColumnExpressionArgument[bool]] = None,
- **kwargs: Any,
- ) -> ColumnElement[bool]:
- """Produce an expression that tests a collection against
- particular criterion, using EXISTS.
-
- An expression like::
-
- session.query(MyClass).filter(
- MyClass.somereference.any(SomeRelated.x==2)
- )
-
-
- Will produce a query like::
-
- SELECT * FROM my_table WHERE
- EXISTS (SELECT 1 FROM related WHERE related.my_id=my_table.id
- AND related.x=2)
-
- Because :meth:`~.Relationship.Comparator.any` uses
- a correlated subquery, its performance is not nearly as
- good when compared against large target tables as that of
- using a join.
-
- :meth:`~.Relationship.Comparator.any` is particularly
- useful for testing for empty collections::
-
- session.query(MyClass).filter(
- ~MyClass.somereference.any()
- )
-
- will produce::
-
- SELECT * FROM my_table WHERE
- NOT (EXISTS (SELECT 1 FROM related WHERE
- related.my_id=my_table.id))
-
- :meth:`~.Relationship.Comparator.any` is only
- valid for collections, i.e. a :func:`_orm.relationship`
- that has ``uselist=True``. For scalar references,
- use :meth:`~.Relationship.Comparator.has`.
-
- """
- if not self.property.uselist:
- raise sa_exc.InvalidRequestError(
- "'any()' not implemented for scalar "
- "attributes. Use has()."
- )
-
- return self._criterion_exists(criterion, **kwargs)
-
- def has(
- self,
- criterion: Optional[_ColumnExpressionArgument[bool]] = None,
- **kwargs: Any,
- ) -> ColumnElement[bool]:
- """Produce an expression that tests a scalar reference against
- particular criterion, using EXISTS.
-
- An expression like::
-
- session.query(MyClass).filter(
- MyClass.somereference.has(SomeRelated.x==2)
- )
-
-
- Will produce a query like::
-
- SELECT * FROM my_table WHERE
- EXISTS (SELECT 1 FROM related WHERE
- related.id==my_table.related_id AND related.x=2)
-
- Because :meth:`~.Relationship.Comparator.has` uses
- a correlated subquery, its performance is not nearly as
- good when compared against large target tables as that of
- using a join.
-
- :meth:`~.Relationship.Comparator.has` is only
- valid for scalar references, i.e. a :func:`_orm.relationship`
- that has ``uselist=False``. For collection references,
- use :meth:`~.Relationship.Comparator.any`.
-
- """
- if self.property.uselist:
- raise sa_exc.InvalidRequestError(
- "'has()' not implemented for collections. Use any()."
- )
- return self._criterion_exists(criterion, **kwargs)
-
- def contains(
- self, other: _ColumnExpressionArgument[Any], **kwargs: Any
- ) -> ColumnElement[bool]:
- """Return a simple expression that tests a collection for
- containment of a particular item.
-
- :meth:`~.Relationship.Comparator.contains` is
- only valid for a collection, i.e. a
- :func:`_orm.relationship` that implements
- one-to-many or many-to-many with ``uselist=True``.
-
- When used in a simple one-to-many context, an
- expression like::
-
- MyClass.contains(other)
-
- Produces a clause like::
-
- mytable.id == <some id>
-
- Where ``<some id>`` is the value of the foreign key
- attribute on ``other`` which refers to the primary
- key of its parent object. From this it follows that
- :meth:`~.Relationship.Comparator.contains` is
- very useful when used with simple one-to-many
- operations.
-
- For many-to-many operations, the behavior of
- :meth:`~.Relationship.Comparator.contains`
- has more caveats. The association table will be
- rendered in the statement, producing an "implicit"
- join, that is, includes multiple tables in the FROM
- clause which are equated in the WHERE clause::
-
- query(MyClass).filter(MyClass.contains(other))
-
- Produces a query like::
-
- SELECT * FROM my_table, my_association_table AS
- my_association_table_1 WHERE
- my_table.id = my_association_table_1.parent_id
- AND my_association_table_1.child_id = <some id>
-
- Where ``<some id>`` would be the primary key of
- ``other``. From the above, it is clear that
- :meth:`~.Relationship.Comparator.contains`
- will **not** work with many-to-many collections when
- used in queries that move beyond simple AND
- conjunctions, such as multiple
- :meth:`~.Relationship.Comparator.contains`
- expressions joined by OR. In such cases subqueries or
- explicit "outer joins" will need to be used instead.
- See :meth:`~.Relationship.Comparator.any` for
- a less-performant alternative using EXISTS, or refer
- to :meth:`_query.Query.outerjoin`
- as well as :ref:`orm_queryguide_joins`
- for more details on constructing outer joins.
-
- kwargs may be ignored by this operator but are required for API
- conformance.
- """
- if not self.prop.uselist:
- raise sa_exc.InvalidRequestError(
- "'contains' not implemented for scalar "
- "attributes. Use =="
- )
-
- clause = self.prop._optimized_compare(
- other, adapt_source=self.adapter
- )
-
- if self.prop.secondaryjoin is not None:
- clause.negation_clause = self.__negated_contains_or_equals(
- other
- )
-
- return clause
-
- def __negated_contains_or_equals(
- self, other: Any
- ) -> ColumnElement[bool]:
- if self.prop.direction == MANYTOONE:
- state = attributes.instance_state(other)
-
- def state_bindparam(
- local_col: ColumnElement[Any],
- state: InstanceState[Any],
- remote_col: ColumnElement[Any],
- ) -> BindParameter[Any]:
- dict_ = state.dict
- return sql.bindparam(
- local_col.key,
- type_=local_col.type,
- unique=True,
- callable_=self.prop._get_attr_w_warn_on_none(
- self.prop.mapper, state, dict_, remote_col
- ),
- )
-
- def adapt(col: _CE) -> _CE:
- if self.adapter:
- return self.adapter(col)
- else:
- return col
-
- if self.property._use_get:
- return sql.and_(
- *[
- sql.or_(
- adapt(x)
- != state_bindparam(adapt(x), state, y),
- adapt(x) == None,
- )
- for (x, y) in self.property.local_remote_pairs
- ]
- )
-
- criterion = sql.and_(
- *[
- x == y
- for (x, y) in zip(
- self.property.mapper.primary_key,
- self.property.mapper.primary_key_from_instance(other),
- )
- ]
- )
-
- return ~self._criterion_exists(criterion)
-
- def __ne__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
- """Implement the ``!=`` operator.
-
- In a many-to-one context, such as::
-
- MyClass.some_prop != <some object>
-
- This will typically produce a clause such as::
-
- mytable.related_id != <some id>
-
- Where ``<some id>`` is the primary key of the
- given object.
-
- The ``!=`` operator provides partial functionality for non-
- many-to-one comparisons:
-
- * Comparisons against collections are not supported.
- Use
- :meth:`~.Relationship.Comparator.contains`
- in conjunction with :func:`_expression.not_`.
- * Compared to a scalar one-to-many, will produce a
- clause that compares the target columns in the parent to
- the given target.
- * Compared to a scalar many-to-many, an alias
- of the association table will be rendered as
- well, forming a natural join that is part of the
- main body of the query. This will not work for
- queries that go beyond simple AND conjunctions of
- comparisons, such as those which use OR. Use
- explicit joins, outerjoins, or
- :meth:`~.Relationship.Comparator.has` in
- conjunction with :func:`_expression.not_` for
- more comprehensive non-many-to-one scalar
- membership tests.
- * Comparisons against ``None`` given in a one-to-many
- or many-to-many context produce an EXISTS clause.
-
- """
- if other is None or isinstance(other, expression.Null):
- if self.property.direction == MANYTOONE:
- return _orm_annotate(
- ~self.property._optimized_compare(
- None, adapt_source=self.adapter
- )
- )
-
- else:
- return self._criterion_exists()
- elif self.property.uselist:
- raise sa_exc.InvalidRequestError(
- "Can't compare a collection"
- " to an object or collection; use "
- "contains() to test for membership."
- )
- else:
- return _orm_annotate(self.__negated_contains_or_equals(other))
-
- def _memoized_attr_property(self) -> RelationshipProperty[_PT]:
- self.prop.parent._check_configure()
- return self.prop
-
- def _with_parent(
- self,
- instance: object,
- alias_secondary: bool = True,
- from_entity: Optional[_EntityType[Any]] = None,
- ) -> ColumnElement[bool]:
- assert instance is not None
- adapt_source: Optional[_CoreAdapterProto] = None
- if from_entity is not None:
- insp: Optional[_InternalEntityType[Any]] = inspect(from_entity)
- assert insp is not None
- if insp_is_aliased_class(insp):
- adapt_source = insp._adapter.adapt_clause
- return self._optimized_compare(
- instance,
- value_is_parent=True,
- adapt_source=adapt_source,
- alias_secondary=alias_secondary,
- )
-
- def _optimized_compare(
- self,
- state: Any,
- value_is_parent: bool = False,
- adapt_source: Optional[_CoreAdapterProto] = None,
- alias_secondary: bool = True,
- ) -> ColumnElement[bool]:
- if state is not None:
- try:
- state = inspect(state)
- except sa_exc.NoInspectionAvailable:
- state = None
-
- if state is None or not getattr(state, "is_instance", False):
- raise sa_exc.ArgumentError(
- "Mapped instance expected for relationship "
- "comparison to object. Classes, queries and other "
- "SQL elements are not accepted in this context; for "
- "comparison with a subquery, "
- "use %s.has(**criteria)." % self
- )
- reverse_direction = not value_is_parent
-
- if state is None:
- return self._lazy_none_clause(
- reverse_direction, adapt_source=adapt_source
- )
-
- if not reverse_direction:
- criterion, bind_to_col = (
- self._lazy_strategy._lazywhere,
- self._lazy_strategy._bind_to_col,
- )
- else:
- criterion, bind_to_col = (
- self._lazy_strategy._rev_lazywhere,
- self._lazy_strategy._rev_bind_to_col,
- )
-
- if reverse_direction:
- mapper = self.mapper
- else:
- mapper = self.parent
-
- dict_ = attributes.instance_dict(state.obj())
-
- def visit_bindparam(bindparam: BindParameter[Any]) -> None:
- if bindparam._identifying_key in bind_to_col:
- bindparam.callable = self._get_attr_w_warn_on_none(
- mapper,
- state,
- dict_,
- bind_to_col[bindparam._identifying_key],
- )
-
- if self.secondary is not None and alias_secondary:
- criterion = ClauseAdapter(
- self.secondary._anonymous_fromclause()
- ).traverse(criterion)
-
- criterion = visitors.cloned_traverse(
- criterion, {}, {"bindparam": visit_bindparam}
- )
-
- if adapt_source:
- criterion = adapt_source(criterion)
- return criterion
-
- def _get_attr_w_warn_on_none(
- self,
- mapper: Mapper[Any],
- state: InstanceState[Any],
- dict_: _InstanceDict,
- column: ColumnElement[Any],
- ) -> Callable[[], Any]:
- """Create the callable that is used in a many-to-one expression.
-
- E.g.::
-
- u1 = s.query(User).get(5)
-
- expr = Address.user == u1
-
- Above, the SQL should be "address.user_id = 5". The callable
- returned by this method produces the value "5" based on the identity
- of ``u1``.
-
- """
-
- # in this callable, we're trying to thread the needle through
- # a wide variety of scenarios, including:
- #
- # * the object hasn't been flushed yet and there's no value for
- # the attribute as of yet
- #
- # * the object hasn't been flushed yet but it has a user-defined
- # value
- #
- # * the object has a value but it's expired and not locally present
- #
- # * the object has a value but it's expired and not locally present,
- # and the object is also detached
- #
- # * The object hadn't been flushed yet, there was no value, but
- # later, the object has been expired and detached, and *now*
- # they're trying to evaluate it
- #
- # * the object had a value, but it was changed to a new value, and
- # then expired
- #
- # * the object had a value, but it was changed to a new value, and
- # then expired, then the object was detached
- #
- # * the object has a user-set value, but it's None and we don't do
- # the comparison correctly for that so warn
- #
-
- prop = mapper.get_property_by_column(column)
-
- # by invoking this method, InstanceState will track the last known
- # value for this key each time the attribute is to be expired.
- # this feature was added explicitly for use in this method.
- state._track_last_known_value(prop.key)
-
- lkv_fixed = state._last_known_values
-
- def _go() -> Any:
- assert lkv_fixed is not None
- last_known = to_return = lkv_fixed[prop.key]
- existing_is_available = (
- last_known is not LoaderCallableStatus.NO_VALUE
- )
-
- # we support that the value may have changed. so here we
- # try to get the most recent value including re-fetching.
- # only if we can't get a value now due to detachment do we return
- # the last known value
- current_value = mapper._get_state_attr_by_column(
- state,
- dict_,
- column,
- passive=(
- PassiveFlag.PASSIVE_OFF
- if state.persistent
- else PassiveFlag.PASSIVE_NO_FETCH ^ PassiveFlag.INIT_OK
- ),
- )
-
- if current_value is LoaderCallableStatus.NEVER_SET:
- if not existing_is_available:
- raise sa_exc.InvalidRequestError(
- "Can't resolve value for column %s on object "
- "%s; no value has been set for this column"
- % (column, state_str(state))
- )
- elif current_value is LoaderCallableStatus.PASSIVE_NO_RESULT:
- if not existing_is_available:
- raise sa_exc.InvalidRequestError(
- "Can't resolve value for column %s on object "
- "%s; the object is detached and the value was "
- "expired" % (column, state_str(state))
- )
- else:
- to_return = current_value
- if to_return is None:
- util.warn(
- "Got None for value of column %s; this is unsupported "
- "for a relationship comparison and will not "
- "currently produce an IS comparison "
- "(but may in a future release)" % column
- )
- return to_return
-
- return _go
-
- def _lazy_none_clause(
- self,
- reverse_direction: bool = False,
- adapt_source: Optional[_CoreAdapterProto] = None,
- ) -> ColumnElement[bool]:
- if not reverse_direction:
- criterion, bind_to_col = (
- self._lazy_strategy._lazywhere,
- self._lazy_strategy._bind_to_col,
- )
- else:
- criterion, bind_to_col = (
- self._lazy_strategy._rev_lazywhere,
- self._lazy_strategy._rev_bind_to_col,
- )
-
- criterion = adapt_criterion_to_null(criterion, bind_to_col)
-
- if adapt_source:
- criterion = adapt_source(criterion)
- return criterion
-
- def __str__(self) -> str:
- return str(self.parent.class_.__name__) + "." + self.key
-
- def merge(
- self,
- session: Session,
- source_state: InstanceState[Any],
- source_dict: _InstanceDict,
- dest_state: InstanceState[Any],
- dest_dict: _InstanceDict,
- load: bool,
- _recursive: Dict[Any, object],
- _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
- ) -> None:
- if load:
- for r in self._reverse_property:
- if (source_state, r) in _recursive:
- return
-
- if "merge" not in self._cascade:
- return
-
- if self.key not in source_dict:
- return
-
- if self.uselist:
- impl = source_state.get_impl(self.key)
-
- assert is_has_collection_adapter(impl)
- instances_iterable = impl.get_collection(source_state, source_dict)
-
- # if this is a CollectionAttributeImpl, then empty should
- # be False, otherwise "self.key in source_dict" should not be
- # True
- assert not instances_iterable.empty if impl.collection else True
-
- if load:
- # for a full merge, pre-load the destination collection,
- # so that individual _merge of each item pulls from identity
- # map for those already present.
- # also assumes CollectionAttributeImpl behavior of loading
- # "old" list in any case
- dest_state.get_impl(self.key).get(
- dest_state, dest_dict, passive=PassiveFlag.PASSIVE_MERGE
- )
-
- dest_list = []
- for current in instances_iterable:
- current_state = attributes.instance_state(current)
- current_dict = attributes.instance_dict(current)
- _recursive[(current_state, self)] = True
- obj = session._merge(
- current_state,
- current_dict,
- load=load,
- _recursive=_recursive,
- _resolve_conflict_map=_resolve_conflict_map,
- )
- if obj is not None:
- dest_list.append(obj)
-
- if not load:
- coll = attributes.init_state_collection(
- dest_state, dest_dict, self.key
- )
- for c in dest_list:
- coll.append_without_event(c)
- else:
- dest_impl = dest_state.get_impl(self.key)
- assert is_has_collection_adapter(dest_impl)
- dest_impl.set(
- dest_state,
- dest_dict,
- dest_list,
- _adapt=False,
- passive=PassiveFlag.PASSIVE_MERGE,
- )
- else:
- current = source_dict[self.key]
- if current is not None:
- current_state = attributes.instance_state(current)
- current_dict = attributes.instance_dict(current)
- _recursive[(current_state, self)] = True
- obj = session._merge(
- current_state,
- current_dict,
- load=load,
- _recursive=_recursive,
- _resolve_conflict_map=_resolve_conflict_map,
- )
- else:
- obj = None
-
- if not load:
- dest_dict[self.key] = obj
- else:
- dest_state.get_impl(self.key).set(
- dest_state, dest_dict, obj, None
- )
-
- def _value_as_iterable(
- self,
- state: InstanceState[_O],
- dict_: _InstanceDict,
- key: str,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- ) -> Sequence[Tuple[InstanceState[_O], _O]]:
- """Return a list of tuples (state, obj) for the given
- key.
-
- returns an empty list if the value is None/empty/PASSIVE_NO_RESULT
- """
-
- impl = state.manager[key].impl
- x = impl.get(state, dict_, passive=passive)
- if x is LoaderCallableStatus.PASSIVE_NO_RESULT or x is None:
- return []
- elif is_has_collection_adapter(impl):
- return [
- (attributes.instance_state(o), o)
- for o in impl.get_collection(state, dict_, x, passive=passive)
- ]
- else:
- return [(attributes.instance_state(x), x)]
-
- def cascade_iterator(
- self,
- type_: str,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- visited_states: Set[InstanceState[Any]],
- halt_on: Optional[Callable[[InstanceState[Any]], bool]] = None,
- ) -> Iterator[Tuple[Any, Mapper[Any], InstanceState[Any], _InstanceDict]]:
- # assert type_ in self._cascade
-
- # only actively lazy load on the 'delete' cascade
- if type_ != "delete" or self.passive_deletes:
- passive = PassiveFlag.PASSIVE_NO_INITIALIZE
- else:
- passive = PassiveFlag.PASSIVE_OFF | PassiveFlag.NO_RAISE
-
- if type_ == "save-update":
- tuples = state.manager[self.key].impl.get_all_pending(state, dict_)
- else:
- tuples = self._value_as_iterable(
- state, dict_, self.key, passive=passive
- )
-
- skip_pending = (
- type_ == "refresh-expire" and "delete-orphan" not in self._cascade
- )
-
- for instance_state, c in tuples:
- if instance_state in visited_states:
- continue
-
- if c is None:
- # would like to emit a warning here, but
- # would not be consistent with collection.append(None)
- # current behavior of silently skipping.
- # see [ticket:2229]
- continue
-
- assert instance_state is not None
- instance_dict = attributes.instance_dict(c)
-
- if halt_on and halt_on(instance_state):
- continue
-
- if skip_pending and not instance_state.key:
- continue
-
- instance_mapper = instance_state.manager.mapper
-
- if not instance_mapper.isa(self.mapper.class_manager.mapper):
- raise AssertionError(
- "Attribute '%s' on class '%s' "
- "doesn't handle objects "
- "of type '%s'"
- % (self.key, self.parent.class_, c.__class__)
- )
-
- visited_states.add(instance_state)
-
- yield c, instance_mapper, instance_state, instance_dict
-
- @property
- def _effective_sync_backref(self) -> bool:
- if self.viewonly:
- return False
- else:
- return self.sync_backref is not False
-
- @staticmethod
- def _check_sync_backref(
- rel_a: RelationshipProperty[Any], rel_b: RelationshipProperty[Any]
- ) -> None:
- if rel_a.viewonly and rel_b.sync_backref:
- raise sa_exc.InvalidRequestError(
- "Relationship %s cannot specify sync_backref=True since %s "
- "includes viewonly=True." % (rel_b, rel_a)
- )
- if (
- rel_a.viewonly
- and not rel_b.viewonly
- and rel_b.sync_backref is not False
- ):
- rel_b.sync_backref = False
-
- def _add_reverse_property(self, key: str) -> None:
- other = self.mapper.get_property(key, _configure_mappers=False)
- if not isinstance(other, RelationshipProperty):
- raise sa_exc.InvalidRequestError(
- "back_populates on relationship '%s' refers to attribute '%s' "
- "that is not a relationship. The back_populates parameter "
- "should refer to the name of a relationship on the target "
- "class." % (self, other)
- )
- # viewonly and sync_backref cases
- # 1. self.viewonly==True and other.sync_backref==True -> error
- # 2. self.viewonly==True and other.viewonly==False and
- # other.sync_backref==None -> warn sync_backref=False, set to False
- self._check_sync_backref(self, other)
- # 3. other.viewonly==True and self.sync_backref==True -> error
- # 4. other.viewonly==True and self.viewonly==False and
- # self.sync_backref==None -> warn sync_backref=False, set to False
- self._check_sync_backref(other, self)
-
- self._reverse_property.add(other)
- other._reverse_property.add(self)
-
- other._setup_entity()
-
- if not other.mapper.common_parent(self.parent):
- raise sa_exc.ArgumentError(
- "reverse_property %r on "
- "relationship %s references relationship %s, which "
- "does not reference mapper %s"
- % (key, self, other, self.parent)
- )
-
- if (
- other._configure_started
- and self.direction in (ONETOMANY, MANYTOONE)
- and self.direction == other.direction
- ):
- raise sa_exc.ArgumentError(
- "%s and back-reference %s are "
- "both of the same direction %r. Did you mean to "
- "set remote_side on the many-to-one side ?"
- % (other, self, self.direction)
- )
-
- @util.memoized_property
- def entity(self) -> _InternalEntityType[_T]:
- """Return the target mapped entity, which is an inspect() of the
- class or aliased class that is referenced by this
- :class:`.RelationshipProperty`.
-
- """
- self.parent._check_configure()
- return self.entity
-
- @util.memoized_property
- def mapper(self) -> Mapper[_T]:
- """Return the targeted :class:`_orm.Mapper` for this
- :class:`.RelationshipProperty`.
-
- """
- return self.entity.mapper
-
- def do_init(self) -> None:
- self._check_conflicts()
- self._process_dependent_arguments()
- self._setup_entity()
- self._setup_registry_dependencies()
- self._setup_join_conditions()
- self._check_cascade_settings(self._cascade)
- self._post_init()
- self._generate_backref()
- self._join_condition._warn_for_conflicting_sync_targets()
- super().do_init()
- self._lazy_strategy = cast(
- "LazyLoader", self._get_strategy((("lazy", "select"),))
- )
-
- def _setup_registry_dependencies(self) -> None:
- self.parent.mapper.registry._set_depends_on(
- self.entity.mapper.registry
- )
-
- def _process_dependent_arguments(self) -> None:
- """Convert incoming configuration arguments to their
- proper form.
-
- Callables are resolved, ORM annotations removed.
-
- """
-
- # accept callables for other attributes which may require
- # deferred initialization. This technique is used
- # by declarative "string configs" and some recipes.
- init_args = self._init_args
-
- for attr in (
- "order_by",
- "primaryjoin",
- "secondaryjoin",
- "secondary",
- "foreign_keys",
- "remote_side",
- ):
- rel_arg = getattr(init_args, attr)
-
- rel_arg._resolve_against_registry(self._clsregistry_resolvers[1])
-
- # remove "annotations" which are present if mapped class
- # descriptors are used to create the join expression.
- for attr in "primaryjoin", "secondaryjoin":
- rel_arg = getattr(init_args, attr)
- val = rel_arg.resolved
- if val is not None:
- rel_arg.resolved = _orm_deannotate(
- coercions.expect(
- roles.ColumnArgumentRole, val, argname=attr
- )
- )
-
- secondary = init_args.secondary.resolved
- if secondary is not None and _is_mapped_class(secondary):
- raise sa_exc.ArgumentError(
- "secondary argument %s passed to to relationship() %s must "
- "be a Table object or other FROM clause; can't send a mapped "
- "class directly as rows in 'secondary' are persisted "
- "independently of a class that is mapped "
- "to that same table." % (secondary, self)
- )
-
- # ensure expressions in self.order_by, foreign_keys,
- # remote_side are all columns, not strings.
- if (
- init_args.order_by.resolved is not False
- and init_args.order_by.resolved is not None
- ):
- self.order_by = tuple(
- coercions.expect(
- roles.ColumnArgumentRole, x, argname="order_by"
- )
- for x in util.to_list(init_args.order_by.resolved)
- )
- else:
- self.order_by = False
-
- self._user_defined_foreign_keys = util.column_set(
- coercions.expect(
- roles.ColumnArgumentRole, x, argname="foreign_keys"
- )
- for x in util.to_column_set(init_args.foreign_keys.resolved)
- )
-
- self.remote_side = util.column_set(
- coercions.expect(
- roles.ColumnArgumentRole, x, argname="remote_side"
- )
- for x in util.to_column_set(init_args.remote_side.resolved)
- )
-
- def declarative_scan(
- self,
- decl_scan: _ClassScanMapperConfig,
- registry: _RegistryType,
- cls: Type[Any],
- originating_module: Optional[str],
- key: str,
- mapped_container: Optional[Type[Mapped[Any]]],
- annotation: Optional[_AnnotationScanType],
- extracted_mapped_annotation: Optional[_AnnotationScanType],
- is_dataclass_field: bool,
- ) -> None:
- argument = extracted_mapped_annotation
-
- if extracted_mapped_annotation is None:
- if self.argument is None:
- self._raise_for_required(key, cls)
- else:
- return
-
- argument = extracted_mapped_annotation
- assert originating_module is not None
-
- if mapped_container is not None:
- is_write_only = issubclass(mapped_container, WriteOnlyMapped)
- is_dynamic = issubclass(mapped_container, DynamicMapped)
- if is_write_only:
- self.lazy = "write_only"
- self.strategy_key = (("lazy", self.lazy),)
- elif is_dynamic:
- self.lazy = "dynamic"
- self.strategy_key = (("lazy", self.lazy),)
- else:
- is_write_only = is_dynamic = False
-
- argument = de_optionalize_union_types(argument)
-
- if hasattr(argument, "__origin__"):
- arg_origin = argument.__origin__
- if isinstance(arg_origin, type) and issubclass(
- arg_origin, abc.Collection
- ):
- if self.collection_class is None:
- if _py_inspect.isabstract(arg_origin):
- raise sa_exc.ArgumentError(
- f"Collection annotation type {arg_origin} cannot "
- "be instantiated; please provide an explicit "
- "'collection_class' parameter "
- "(e.g. list, set, etc.) to the "
- "relationship() function to accompany this "
- "annotation"
- )
-
- self.collection_class = arg_origin
-
- elif not is_write_only and not is_dynamic:
- self.uselist = False
-
- if argument.__args__: # type: ignore
- if isinstance(arg_origin, type) and issubclass(
- arg_origin, typing.Mapping
- ):
- type_arg = argument.__args__[-1] # type: ignore
- else:
- type_arg = argument.__args__[0] # type: ignore
- if hasattr(type_arg, "__forward_arg__"):
- str_argument = type_arg.__forward_arg__
-
- argument = resolve_name_to_real_class_name(
- str_argument, originating_module
- )
- else:
- argument = type_arg
- else:
- raise sa_exc.ArgumentError(
- f"Generic alias {argument} requires an argument"
- )
- elif hasattr(argument, "__forward_arg__"):
- argument = argument.__forward_arg__
-
- argument = resolve_name_to_real_class_name(
- argument, originating_module
- )
-
- if (
- self.collection_class is None
- and not is_write_only
- and not is_dynamic
- ):
- self.uselist = False
-
- # ticket #8759
- # if a lead argument was given to relationship(), like
- # `relationship("B")`, use that, don't replace it with class we
- # found in the annotation. The declarative_scan() method call here is
- # still useful, as we continue to derive collection type and do
- # checking of the annotation in any case.
- if self.argument is None:
- self.argument = cast("_RelationshipArgumentType[_T]", argument)
-
- @util.preload_module("sqlalchemy.orm.mapper")
- def _setup_entity(self, __argument: Any = None) -> None:
- if "entity" in self.__dict__:
- return
-
- mapperlib = util.preloaded.orm_mapper
-
- if __argument:
- argument = __argument
- else:
- argument = self.argument
-
- resolved_argument: _ExternalEntityType[Any]
-
- if isinstance(argument, str):
- # we might want to cleanup clsregistry API to make this
- # more straightforward
- resolved_argument = cast(
- "_ExternalEntityType[Any]",
- self._clsregistry_resolve_name(argument)(),
- )
- elif callable(argument) and not isinstance(
- argument, (type, mapperlib.Mapper)
- ):
- resolved_argument = argument()
- else:
- resolved_argument = argument
-
- entity: _InternalEntityType[Any]
-
- if isinstance(resolved_argument, type):
- entity = class_mapper(resolved_argument, configure=False)
- else:
- try:
- entity = inspect(resolved_argument)
- except sa_exc.NoInspectionAvailable:
- entity = None # type: ignore
-
- if not hasattr(entity, "mapper"):
- raise sa_exc.ArgumentError(
- "relationship '%s' expects "
- "a class or a mapper argument (received: %s)"
- % (self.key, type(resolved_argument))
- )
-
- self.entity = entity
- self.target = self.entity.persist_selectable
-
- def _setup_join_conditions(self) -> None:
- self._join_condition = jc = JoinCondition(
- parent_persist_selectable=self.parent.persist_selectable,
- child_persist_selectable=self.entity.persist_selectable,
- parent_local_selectable=self.parent.local_table,
- child_local_selectable=self.entity.local_table,
- primaryjoin=self._init_args.primaryjoin.resolved,
- secondary=self._init_args.secondary.resolved,
- secondaryjoin=self._init_args.secondaryjoin.resolved,
- parent_equivalents=self.parent._equivalent_columns,
- child_equivalents=self.mapper._equivalent_columns,
- consider_as_foreign_keys=self._user_defined_foreign_keys,
- local_remote_pairs=self.local_remote_pairs,
- remote_side=self.remote_side,
- self_referential=self._is_self_referential,
- prop=self,
- support_sync=not self.viewonly,
- can_be_synced_fn=self._columns_are_mapped,
- )
- self.primaryjoin = jc.primaryjoin
- self.secondaryjoin = jc.secondaryjoin
- self.secondary = jc.secondary
- self.direction = jc.direction
- self.local_remote_pairs = jc.local_remote_pairs
- self.remote_side = jc.remote_columns
- self.local_columns = jc.local_columns
- self.synchronize_pairs = jc.synchronize_pairs
- self._calculated_foreign_keys = jc.foreign_key_columns
- self.secondary_synchronize_pairs = jc.secondary_synchronize_pairs
-
- @property
- def _clsregistry_resolve_arg(
- self,
- ) -> Callable[[str, bool], _class_resolver]:
- return self._clsregistry_resolvers[1]
-
- @property
- def _clsregistry_resolve_name(
- self,
- ) -> Callable[[str], Callable[[], Union[Type[Any], Table, _ModNS]]]:
- return self._clsregistry_resolvers[0]
-
- @util.memoized_property
- @util.preload_module("sqlalchemy.orm.clsregistry")
- def _clsregistry_resolvers(
- self,
- ) -> Tuple[
- Callable[[str], Callable[[], Union[Type[Any], Table, _ModNS]]],
- Callable[[str, bool], _class_resolver],
- ]:
- _resolver = util.preloaded.orm_clsregistry._resolver
-
- return _resolver(self.parent.class_, self)
-
- def _check_conflicts(self) -> None:
- """Test that this relationship is legal, warn about
- inheritance conflicts."""
- if self.parent.non_primary and not class_mapper(
- self.parent.class_, configure=False
- ).has_property(self.key):
- raise sa_exc.ArgumentError(
- "Attempting to assign a new "
- "relationship '%s' to a non-primary mapper on "
- "class '%s'. New relationships can only be added "
- "to the primary mapper, i.e. the very first mapper "
- "created for class '%s' "
- % (
- self.key,
- self.parent.class_.__name__,
- self.parent.class_.__name__,
- )
- )
-
- @property
- def cascade(self) -> CascadeOptions:
- """Return the current cascade setting for this
- :class:`.RelationshipProperty`.
- """
- return self._cascade
-
- @cascade.setter
- def cascade(self, cascade: Union[str, CascadeOptions]) -> None:
- self._set_cascade(cascade)
-
- def _set_cascade(self, cascade_arg: Union[str, CascadeOptions]) -> None:
- cascade = CascadeOptions(cascade_arg)
-
- if self.viewonly:
- cascade = CascadeOptions(
- cascade.intersection(CascadeOptions._viewonly_cascades)
- )
-
- if "mapper" in self.__dict__:
- self._check_cascade_settings(cascade)
- self._cascade = cascade
-
- if self._dependency_processor:
- self._dependency_processor.cascade = cascade
-
- def _check_cascade_settings(self, cascade: CascadeOptions) -> None:
- if (
- cascade.delete_orphan
- and not self.single_parent
- and (self.direction is MANYTOMANY or self.direction is MANYTOONE)
- ):
- raise sa_exc.ArgumentError(
- "For %(direction)s relationship %(rel)s, delete-orphan "
- "cascade is normally "
- 'configured only on the "one" side of a one-to-many '
- "relationship, "
- 'and not on the "many" side of a many-to-one or many-to-many '
- "relationship. "
- "To force this relationship to allow a particular "
- '"%(relatedcls)s" object to be referenced by only '
- 'a single "%(clsname)s" object at a time via the '
- "%(rel)s relationship, which "
- "would allow "
- "delete-orphan cascade to take place in this direction, set "
- "the single_parent=True flag."
- % {
- "rel": self,
- "direction": (
- "many-to-one"
- if self.direction is MANYTOONE
- else "many-to-many"
- ),
- "clsname": self.parent.class_.__name__,
- "relatedcls": self.mapper.class_.__name__,
- },
- code="bbf0",
- )
-
- if self.passive_deletes == "all" and (
- "delete" in cascade or "delete-orphan" in cascade
- ):
- raise sa_exc.ArgumentError(
- "On %s, can't set passive_deletes='all' in conjunction "
- "with 'delete' or 'delete-orphan' cascade" % self
- )
-
- if cascade.delete_orphan:
- self.mapper.primary_mapper()._delete_orphans.append(
- (self.key, self.parent.class_)
- )
-
- def _persists_for(self, mapper: Mapper[Any]) -> bool:
- """Return True if this property will persist values on behalf
- of the given mapper.
-
- """
-
- return (
- self.key in mapper.relationships
- and mapper.relationships[self.key] is self
- )
-
- def _columns_are_mapped(self, *cols: ColumnElement[Any]) -> bool:
- """Return True if all columns in the given collection are
- mapped by the tables referenced by this :class:`.RelationshipProperty`.
-
- """
-
- secondary = self._init_args.secondary.resolved
- for c in cols:
- if secondary is not None and secondary.c.contains_column(c):
- continue
- if not self.parent.persist_selectable.c.contains_column(
- c
- ) and not self.target.c.contains_column(c):
- return False
- return True
-
- def _generate_backref(self) -> None:
- """Interpret the 'backref' instruction to create a
- :func:`_orm.relationship` complementary to this one."""
-
- if self.parent.non_primary:
- return
- if self.backref is not None and not self.back_populates:
- kwargs: Dict[str, Any]
- if isinstance(self.backref, str):
- backref_key, kwargs = self.backref, {}
- else:
- backref_key, kwargs = self.backref
- mapper = self.mapper.primary_mapper()
-
- if not mapper.concrete:
- check = set(mapper.iterate_to_root()).union(
- mapper.self_and_descendants
- )
- for m in check:
- if m.has_property(backref_key) and not m.concrete:
- raise sa_exc.ArgumentError(
- "Error creating backref "
- "'%s' on relationship '%s': property of that "
- "name exists on mapper '%s'"
- % (backref_key, self, m)
- )
-
- # determine primaryjoin/secondaryjoin for the
- # backref. Use the one we had, so that
- # a custom join doesn't have to be specified in
- # both directions.
- if self.secondary is not None:
- # for many to many, just switch primaryjoin/
- # secondaryjoin. use the annotated
- # pj/sj on the _join_condition.
- pj = kwargs.pop(
- "primaryjoin",
- self._join_condition.secondaryjoin_minus_local,
- )
- sj = kwargs.pop(
- "secondaryjoin",
- self._join_condition.primaryjoin_minus_local,
- )
- else:
- pj = kwargs.pop(
- "primaryjoin",
- self._join_condition.primaryjoin_reverse_remote,
- )
- sj = kwargs.pop("secondaryjoin", None)
- if sj:
- raise sa_exc.InvalidRequestError(
- "Can't assign 'secondaryjoin' on a backref "
- "against a non-secondary relationship."
- )
-
- foreign_keys = kwargs.pop(
- "foreign_keys", self._user_defined_foreign_keys
- )
- parent = self.parent.primary_mapper()
- kwargs.setdefault("viewonly", self.viewonly)
- kwargs.setdefault("post_update", self.post_update)
- kwargs.setdefault("passive_updates", self.passive_updates)
- kwargs.setdefault("sync_backref", self.sync_backref)
- self.back_populates = backref_key
- relationship = RelationshipProperty(
- parent,
- self.secondary,
- primaryjoin=pj,
- secondaryjoin=sj,
- foreign_keys=foreign_keys,
- back_populates=self.key,
- **kwargs,
- )
- mapper._configure_property(
- backref_key, relationship, warn_for_existing=True
- )
-
- if self.back_populates:
- self._add_reverse_property(self.back_populates)
-
- @util.preload_module("sqlalchemy.orm.dependency")
- def _post_init(self) -> None:
- dependency = util.preloaded.orm_dependency
-
- if self.uselist is None:
- self.uselist = self.direction is not MANYTOONE
- if not self.viewonly:
- self._dependency_processor = ( # type: ignore
- dependency.DependencyProcessor.from_relationship
- )(self)
-
- @util.memoized_property
- def _use_get(self) -> bool:
- """memoize the 'use_get' attribute of this RelationshipLoader's
- lazyloader."""
-
- strategy = self._lazy_strategy
- return strategy.use_get
-
- @util.memoized_property
- def _is_self_referential(self) -> bool:
- return self.mapper.common_parent(self.parent)
-
- def _create_joins(
- self,
- source_polymorphic: bool = False,
- source_selectable: Optional[FromClause] = None,
- dest_selectable: Optional[FromClause] = None,
- of_type_entity: Optional[_InternalEntityType[Any]] = None,
- alias_secondary: bool = False,
- extra_criteria: Tuple[ColumnElement[bool], ...] = (),
- ) -> Tuple[
- ColumnElement[bool],
- Optional[ColumnElement[bool]],
- FromClause,
- FromClause,
- Optional[FromClause],
- Optional[ClauseAdapter],
- ]:
- aliased = False
-
- if alias_secondary and self.secondary is not None:
- aliased = True
-
- if source_selectable is None:
- if source_polymorphic and self.parent.with_polymorphic:
- source_selectable = self.parent._with_polymorphic_selectable
-
- if of_type_entity:
- dest_mapper = of_type_entity.mapper
- if dest_selectable is None:
- dest_selectable = of_type_entity.selectable
- aliased = True
- else:
- dest_mapper = self.mapper
-
- if dest_selectable is None:
- dest_selectable = self.entity.selectable
- if self.mapper.with_polymorphic:
- aliased = True
-
- if self._is_self_referential and source_selectable is None:
- dest_selectable = dest_selectable._anonymous_fromclause()
- aliased = True
- elif (
- dest_selectable is not self.mapper._with_polymorphic_selectable
- or self.mapper.with_polymorphic
- ):
- aliased = True
-
- single_crit = dest_mapper._single_table_criterion
- aliased = aliased or (
- source_selectable is not None
- and (
- source_selectable
- is not self.parent._with_polymorphic_selectable
- or source_selectable._is_subquery
- )
- )
-
- (
- primaryjoin,
- secondaryjoin,
- secondary,
- target_adapter,
- dest_selectable,
- ) = self._join_condition.join_targets(
- source_selectable,
- dest_selectable,
- aliased,
- single_crit,
- extra_criteria,
- )
- if source_selectable is None:
- source_selectable = self.parent.local_table
- if dest_selectable is None:
- dest_selectable = self.entity.local_table
- return (
- primaryjoin,
- secondaryjoin,
- source_selectable,
- dest_selectable,
- secondary,
- target_adapter,
- )
-
-
-def _annotate_columns(element: _CE, annotations: _AnnotationDict) -> _CE:
- def clone(elem: _CE) -> _CE:
- if isinstance(elem, expression.ColumnClause):
- elem = elem._annotate(annotations.copy()) # type: ignore
- elem._copy_internals(clone=clone)
- return elem
-
- if element is not None:
- element = clone(element)
- clone = None # type: ignore # remove gc cycles
- return element
-
-
-class JoinCondition:
- primaryjoin_initial: Optional[ColumnElement[bool]]
- primaryjoin: ColumnElement[bool]
- secondaryjoin: Optional[ColumnElement[bool]]
- secondary: Optional[FromClause]
- prop: RelationshipProperty[Any]
-
- synchronize_pairs: _ColumnPairs
- secondary_synchronize_pairs: _ColumnPairs
- direction: RelationshipDirection
-
- parent_persist_selectable: FromClause
- child_persist_selectable: FromClause
- parent_local_selectable: FromClause
- child_local_selectable: FromClause
-
- _local_remote_pairs: Optional[_ColumnPairs]
-
- def __init__(
- self,
- parent_persist_selectable: FromClause,
- child_persist_selectable: FromClause,
- parent_local_selectable: FromClause,
- child_local_selectable: FromClause,
- *,
- primaryjoin: Optional[ColumnElement[bool]] = None,
- secondary: Optional[FromClause] = None,
- secondaryjoin: Optional[ColumnElement[bool]] = None,
- parent_equivalents: Optional[_EquivalentColumnMap] = None,
- child_equivalents: Optional[_EquivalentColumnMap] = None,
- consider_as_foreign_keys: Any = None,
- local_remote_pairs: Optional[_ColumnPairs] = None,
- remote_side: Any = None,
- self_referential: Any = False,
- prop: RelationshipProperty[Any],
- support_sync: bool = True,
- can_be_synced_fn: Callable[..., bool] = lambda *c: True,
- ):
- self.parent_persist_selectable = parent_persist_selectable
- self.parent_local_selectable = parent_local_selectable
- self.child_persist_selectable = child_persist_selectable
- self.child_local_selectable = child_local_selectable
- self.parent_equivalents = parent_equivalents
- self.child_equivalents = child_equivalents
- self.primaryjoin_initial = primaryjoin
- self.secondaryjoin = secondaryjoin
- self.secondary = secondary
- self.consider_as_foreign_keys = consider_as_foreign_keys
- self._local_remote_pairs = local_remote_pairs
- self._remote_side = remote_side
- self.prop = prop
- self.self_referential = self_referential
- self.support_sync = support_sync
- self.can_be_synced_fn = can_be_synced_fn
-
- self._determine_joins()
- assert self.primaryjoin is not None
-
- self._sanitize_joins()
- self._annotate_fks()
- self._annotate_remote()
- self._annotate_local()
- self._annotate_parentmapper()
- self._setup_pairs()
- self._check_foreign_cols(self.primaryjoin, True)
- if self.secondaryjoin is not None:
- self._check_foreign_cols(self.secondaryjoin, False)
- self._determine_direction()
- self._check_remote_side()
- self._log_joins()
-
- def _log_joins(self) -> None:
- log = self.prop.logger
- log.info("%s setup primary join %s", self.prop, self.primaryjoin)
- log.info("%s setup secondary join %s", self.prop, self.secondaryjoin)
- log.info(
- "%s synchronize pairs [%s]",
- self.prop,
- ",".join(
- "(%s => %s)" % (l, r) for (l, r) in self.synchronize_pairs
- ),
- )
- log.info(
- "%s secondary synchronize pairs [%s]",
- self.prop,
- ",".join(
- "(%s => %s)" % (l, r)
- for (l, r) in self.secondary_synchronize_pairs or []
- ),
- )
- log.info(
- "%s local/remote pairs [%s]",
- self.prop,
- ",".join(
- "(%s / %s)" % (l, r) for (l, r) in self.local_remote_pairs
- ),
- )
- log.info(
- "%s remote columns [%s]",
- self.prop,
- ",".join("%s" % col for col in self.remote_columns),
- )
- log.info(
- "%s local columns [%s]",
- self.prop,
- ",".join("%s" % col for col in self.local_columns),
- )
- log.info("%s relationship direction %s", self.prop, self.direction)
-
- def _sanitize_joins(self) -> None:
- """remove the parententity annotation from our join conditions which
- can leak in here based on some declarative patterns and maybe others.
-
- "parentmapper" is relied upon both by the ORM evaluator as well as
- the use case in _join_fixture_inh_selfref_w_entity
- that relies upon it being present, see :ticket:`3364`.
-
- """
-
- self.primaryjoin = _deep_deannotate(
- self.primaryjoin, values=("parententity", "proxy_key")
- )
- if self.secondaryjoin is not None:
- self.secondaryjoin = _deep_deannotate(
- self.secondaryjoin, values=("parententity", "proxy_key")
- )
-
- def _determine_joins(self) -> None:
- """Determine the 'primaryjoin' and 'secondaryjoin' attributes,
- if not passed to the constructor already.
-
- This is based on analysis of the foreign key relationships
- between the parent and target mapped selectables.
-
- """
- if self.secondaryjoin is not None and self.secondary is None:
- raise sa_exc.ArgumentError(
- "Property %s specified with secondary "
- "join condition but "
- "no secondary argument" % self.prop
- )
-
- # find a join between the given mapper's mapped table and
- # the given table. will try the mapper's local table first
- # for more specificity, then if not found will try the more
- # general mapped table, which in the case of inheritance is
- # a join.
- try:
- consider_as_foreign_keys = self.consider_as_foreign_keys or None
- if self.secondary is not None:
- if self.secondaryjoin is None:
- self.secondaryjoin = join_condition(
- self.child_persist_selectable,
- self.secondary,
- a_subset=self.child_local_selectable,
- consider_as_foreign_keys=consider_as_foreign_keys,
- )
- if self.primaryjoin_initial is None:
- self.primaryjoin = join_condition(
- self.parent_persist_selectable,
- self.secondary,
- a_subset=self.parent_local_selectable,
- consider_as_foreign_keys=consider_as_foreign_keys,
- )
- else:
- self.primaryjoin = self.primaryjoin_initial
- else:
- if self.primaryjoin_initial is None:
- self.primaryjoin = join_condition(
- self.parent_persist_selectable,
- self.child_persist_selectable,
- a_subset=self.parent_local_selectable,
- consider_as_foreign_keys=consider_as_foreign_keys,
- )
- else:
- self.primaryjoin = self.primaryjoin_initial
- except sa_exc.NoForeignKeysError as nfe:
- if self.secondary is not None:
- raise sa_exc.NoForeignKeysError(
- "Could not determine join "
- "condition between parent/child tables on "
- "relationship %s - there are no foreign keys "
- "linking these tables via secondary table '%s'. "
- "Ensure that referencing columns are associated "
- "with a ForeignKey or ForeignKeyConstraint, or "
- "specify 'primaryjoin' and 'secondaryjoin' "
- "expressions." % (self.prop, self.secondary)
- ) from nfe
- else:
- raise sa_exc.NoForeignKeysError(
- "Could not determine join "
- "condition between parent/child tables on "
- "relationship %s - there are no foreign keys "
- "linking these tables. "
- "Ensure that referencing columns are associated "
- "with a ForeignKey or ForeignKeyConstraint, or "
- "specify a 'primaryjoin' expression." % self.prop
- ) from nfe
- except sa_exc.AmbiguousForeignKeysError as afe:
- if self.secondary is not None:
- raise sa_exc.AmbiguousForeignKeysError(
- "Could not determine join "
- "condition between parent/child tables on "
- "relationship %s - there are multiple foreign key "
- "paths linking the tables via secondary table '%s'. "
- "Specify the 'foreign_keys' "
- "argument, providing a list of those columns which "
- "should be counted as containing a foreign key "
- "reference from the secondary table to each of the "
- "parent and child tables." % (self.prop, self.secondary)
- ) from afe
- else:
- raise sa_exc.AmbiguousForeignKeysError(
- "Could not determine join "
- "condition between parent/child tables on "
- "relationship %s - there are multiple foreign key "
- "paths linking the tables. Specify the "
- "'foreign_keys' argument, providing a list of those "
- "columns which should be counted as containing a "
- "foreign key reference to the parent table." % self.prop
- ) from afe
-
- @property
- def primaryjoin_minus_local(self) -> ColumnElement[bool]:
- return _deep_deannotate(self.primaryjoin, values=("local", "remote"))
-
- @property
- def secondaryjoin_minus_local(self) -> ColumnElement[bool]:
- assert self.secondaryjoin is not None
- return _deep_deannotate(self.secondaryjoin, values=("local", "remote"))
-
- @util.memoized_property
- def primaryjoin_reverse_remote(self) -> ColumnElement[bool]:
- """Return the primaryjoin condition suitable for the
- "reverse" direction.
-
- If the primaryjoin was delivered here with pre-existing
- "remote" annotations, the local/remote annotations
- are reversed. Otherwise, the local/remote annotations
- are removed.
-
- """
- if self._has_remote_annotations:
-
- def replace(element: _CE, **kw: Any) -> Optional[_CE]:
- if "remote" in element._annotations:
- v = dict(element._annotations)
- del v["remote"]
- v["local"] = True
- return element._with_annotations(v)
- elif "local" in element._annotations:
- v = dict(element._annotations)
- del v["local"]
- v["remote"] = True
- return element._with_annotations(v)
-
- return None
-
- return visitors.replacement_traverse(self.primaryjoin, {}, replace)
- else:
- if self._has_foreign_annotations:
- # TODO: coverage
- return _deep_deannotate(
- self.primaryjoin, values=("local", "remote")
- )
- else:
- return _deep_deannotate(self.primaryjoin)
-
- def _has_annotation(self, clause: ClauseElement, annotation: str) -> bool:
- for col in visitors.iterate(clause, {}):
- if annotation in col._annotations:
- return True
- else:
- return False
-
- @util.memoized_property
- def _has_foreign_annotations(self) -> bool:
- return self._has_annotation(self.primaryjoin, "foreign")
-
- @util.memoized_property
- def _has_remote_annotations(self) -> bool:
- return self._has_annotation(self.primaryjoin, "remote")
-
- def _annotate_fks(self) -> None:
- """Annotate the primaryjoin and secondaryjoin
- structures with 'foreign' annotations marking columns
- considered as foreign.
-
- """
- if self._has_foreign_annotations:
- return
-
- if self.consider_as_foreign_keys:
- self._annotate_from_fk_list()
- else:
- self._annotate_present_fks()
-
- def _annotate_from_fk_list(self) -> None:
- def check_fk(element: _CE, **kw: Any) -> Optional[_CE]:
- if element in self.consider_as_foreign_keys:
- return element._annotate({"foreign": True})
- return None
-
- self.primaryjoin = visitors.replacement_traverse(
- self.primaryjoin, {}, check_fk
- )
- if self.secondaryjoin is not None:
- self.secondaryjoin = visitors.replacement_traverse(
- self.secondaryjoin, {}, check_fk
- )
-
- def _annotate_present_fks(self) -> None:
- if self.secondary is not None:
- secondarycols = util.column_set(self.secondary.c)
- else:
- secondarycols = set()
-
- def is_foreign(
- a: ColumnElement[Any], b: ColumnElement[Any]
- ) -> Optional[ColumnElement[Any]]:
- if isinstance(a, schema.Column) and isinstance(b, schema.Column):
- if a.references(b):
- return a
- elif b.references(a):
- return b
-
- if secondarycols:
- if a in secondarycols and b not in secondarycols:
- return a
- elif b in secondarycols and a not in secondarycols:
- return b
-
- return None
-
- def visit_binary(binary: BinaryExpression[Any]) -> None:
- if not isinstance(
- binary.left, sql.ColumnElement
- ) or not isinstance(binary.right, sql.ColumnElement):
- return
-
- if (
- "foreign" not in binary.left._annotations
- and "foreign" not in binary.right._annotations
- ):
- col = is_foreign(binary.left, binary.right)
- if col is not None:
- if col.compare(binary.left):
- binary.left = binary.left._annotate({"foreign": True})
- elif col.compare(binary.right):
- binary.right = binary.right._annotate(
- {"foreign": True}
- )
-
- self.primaryjoin = visitors.cloned_traverse(
- self.primaryjoin, {}, {"binary": visit_binary}
- )
- if self.secondaryjoin is not None:
- self.secondaryjoin = visitors.cloned_traverse(
- self.secondaryjoin, {}, {"binary": visit_binary}
- )
-
- def _refers_to_parent_table(self) -> bool:
- """Return True if the join condition contains column
- comparisons where both columns are in both tables.
-
- """
- pt = self.parent_persist_selectable
- mt = self.child_persist_selectable
- result = False
-
- def visit_binary(binary: BinaryExpression[Any]) -> None:
- nonlocal result
- c, f = binary.left, binary.right
- if (
- isinstance(c, expression.ColumnClause)
- and isinstance(f, expression.ColumnClause)
- and pt.is_derived_from(c.table)
- and pt.is_derived_from(f.table)
- and mt.is_derived_from(c.table)
- and mt.is_derived_from(f.table)
- ):
- result = True
-
- visitors.traverse(self.primaryjoin, {}, {"binary": visit_binary})
- return result
-
- def _tables_overlap(self) -> bool:
- """Return True if parent/child tables have some overlap."""
-
- return selectables_overlap(
- self.parent_persist_selectable, self.child_persist_selectable
- )
-
- def _annotate_remote(self) -> None:
- """Annotate the primaryjoin and secondaryjoin
- structures with 'remote' annotations marking columns
- considered as part of the 'remote' side.
-
- """
- if self._has_remote_annotations:
- return
-
- if self.secondary is not None:
- self._annotate_remote_secondary()
- elif self._local_remote_pairs or self._remote_side:
- self._annotate_remote_from_args()
- elif self._refers_to_parent_table():
- self._annotate_selfref(
- lambda col: "foreign" in col._annotations, False
- )
- elif self._tables_overlap():
- self._annotate_remote_with_overlap()
- else:
- self._annotate_remote_distinct_selectables()
-
- def _annotate_remote_secondary(self) -> None:
- """annotate 'remote' in primaryjoin, secondaryjoin
- when 'secondary' is present.
-
- """
-
- assert self.secondary is not None
- fixed_secondary = self.secondary
-
- def repl(element: _CE, **kw: Any) -> Optional[_CE]:
- if fixed_secondary.c.contains_column(element):
- return element._annotate({"remote": True})
- return None
-
- self.primaryjoin = visitors.replacement_traverse(
- self.primaryjoin, {}, repl
- )
-
- assert self.secondaryjoin is not None
- self.secondaryjoin = visitors.replacement_traverse(
- self.secondaryjoin, {}, repl
- )
-
- def _annotate_selfref(
- self, fn: Callable[[ColumnElement[Any]], bool], remote_side_given: bool
- ) -> None:
- """annotate 'remote' in primaryjoin, secondaryjoin
- when the relationship is detected as self-referential.
-
- """
-
- def visit_binary(binary: BinaryExpression[Any]) -> None:
- equated = binary.left.compare(binary.right)
- if isinstance(binary.left, expression.ColumnClause) and isinstance(
- binary.right, expression.ColumnClause
- ):
- # assume one to many - FKs are "remote"
- if fn(binary.left):
- binary.left = binary.left._annotate({"remote": True})
- if fn(binary.right) and not equated:
- binary.right = binary.right._annotate({"remote": True})
- elif not remote_side_given:
- self._warn_non_column_elements()
-
- self.primaryjoin = visitors.cloned_traverse(
- self.primaryjoin, {}, {"binary": visit_binary}
- )
-
- def _annotate_remote_from_args(self) -> None:
- """annotate 'remote' in primaryjoin, secondaryjoin
- when the 'remote_side' or '_local_remote_pairs'
- arguments are used.
-
- """
- if self._local_remote_pairs:
- if self._remote_side:
- raise sa_exc.ArgumentError(
- "remote_side argument is redundant "
- "against more detailed _local_remote_side "
- "argument."
- )
-
- remote_side = [r for (l, r) in self._local_remote_pairs]
- else:
- remote_side = self._remote_side
-
- if self._refers_to_parent_table():
- self._annotate_selfref(lambda col: col in remote_side, True)
- else:
-
- def repl(element: _CE, **kw: Any) -> Optional[_CE]:
- # use set() to avoid generating ``__eq__()`` expressions
- # against each element
- if element in set(remote_side):
- return element._annotate({"remote": True})
- return None
-
- self.primaryjoin = visitors.replacement_traverse(
- self.primaryjoin, {}, repl
- )
-
- def _annotate_remote_with_overlap(self) -> None:
- """annotate 'remote' in primaryjoin, secondaryjoin
- when the parent/child tables have some set of
- tables in common, though is not a fully self-referential
- relationship.
-
- """
-
- def visit_binary(binary: BinaryExpression[Any]) -> None:
- binary.left, binary.right = proc_left_right(
- binary.left, binary.right
- )
- binary.right, binary.left = proc_left_right(
- binary.right, binary.left
- )
-
- check_entities = (
- self.prop is not None and self.prop.mapper is not self.prop.parent
- )
-
- def proc_left_right(
- left: ColumnElement[Any], right: ColumnElement[Any]
- ) -> Tuple[ColumnElement[Any], ColumnElement[Any]]:
- if isinstance(left, expression.ColumnClause) and isinstance(
- right, expression.ColumnClause
- ):
- if self.child_persist_selectable.c.contains_column(
- right
- ) and self.parent_persist_selectable.c.contains_column(left):
- right = right._annotate({"remote": True})
- elif (
- check_entities
- and right._annotations.get("parentmapper") is self.prop.mapper
- ):
- right = right._annotate({"remote": True})
- elif (
- check_entities
- and left._annotations.get("parentmapper") is self.prop.mapper
- ):
- left = left._annotate({"remote": True})
- else:
- self._warn_non_column_elements()
-
- return left, right
-
- self.primaryjoin = visitors.cloned_traverse(
- self.primaryjoin, {}, {"binary": visit_binary}
- )
-
- def _annotate_remote_distinct_selectables(self) -> None:
- """annotate 'remote' in primaryjoin, secondaryjoin
- when the parent/child tables are entirely
- separate.
-
- """
-
- def repl(element: _CE, **kw: Any) -> Optional[_CE]:
- if self.child_persist_selectable.c.contains_column(element) and (
- not self.parent_local_selectable.c.contains_column(element)
- or self.child_local_selectable.c.contains_column(element)
- ):
- return element._annotate({"remote": True})
- return None
-
- self.primaryjoin = visitors.replacement_traverse(
- self.primaryjoin, {}, repl
- )
-
- def _warn_non_column_elements(self) -> None:
- util.warn(
- "Non-simple column elements in primary "
- "join condition for property %s - consider using "
- "remote() annotations to mark the remote side." % self.prop
- )
-
- def _annotate_local(self) -> None:
- """Annotate the primaryjoin and secondaryjoin
- structures with 'local' annotations.
-
- This annotates all column elements found
- simultaneously in the parent table
- and the join condition that don't have a
- 'remote' annotation set up from
- _annotate_remote() or user-defined.
-
- """
- if self._has_annotation(self.primaryjoin, "local"):
- return
-
- if self._local_remote_pairs:
- local_side = util.column_set(
- [l for (l, r) in self._local_remote_pairs]
- )
- else:
- local_side = util.column_set(self.parent_persist_selectable.c)
-
- def locals_(element: _CE, **kw: Any) -> Optional[_CE]:
- if "remote" not in element._annotations and element in local_side:
- return element._annotate({"local": True})
- return None
-
- self.primaryjoin = visitors.replacement_traverse(
- self.primaryjoin, {}, locals_
- )
-
- def _annotate_parentmapper(self) -> None:
- def parentmappers_(element: _CE, **kw: Any) -> Optional[_CE]:
- if "remote" in element._annotations:
- return element._annotate({"parentmapper": self.prop.mapper})
- elif "local" in element._annotations:
- return element._annotate({"parentmapper": self.prop.parent})
- return None
-
- self.primaryjoin = visitors.replacement_traverse(
- self.primaryjoin, {}, parentmappers_
- )
-
- def _check_remote_side(self) -> None:
- if not self.local_remote_pairs:
- raise sa_exc.ArgumentError(
- "Relationship %s could "
- "not determine any unambiguous local/remote column "
- "pairs based on join condition and remote_side "
- "arguments. "
- "Consider using the remote() annotation to "
- "accurately mark those elements of the join "
- "condition that are on the remote side of "
- "the relationship." % (self.prop,)
- )
- else:
- not_target = util.column_set(
- self.parent_persist_selectable.c
- ).difference(self.child_persist_selectable.c)
-
- for _, rmt in self.local_remote_pairs:
- if rmt in not_target:
- util.warn(
- "Expression %s is marked as 'remote', but these "
- "column(s) are local to the local side. The "
- "remote() annotation is needed only for a "
- "self-referential relationship where both sides "
- "of the relationship refer to the same tables."
- % (rmt,)
- )
-
- def _check_foreign_cols(
- self, join_condition: ColumnElement[bool], primary: bool
- ) -> None:
- """Check the foreign key columns collected and emit error
- messages."""
-
- can_sync = False
-
- foreign_cols = self._gather_columns_with_annotation(
- join_condition, "foreign"
- )
-
- has_foreign = bool(foreign_cols)
-
- if primary:
- can_sync = bool(self.synchronize_pairs)
- else:
- can_sync = bool(self.secondary_synchronize_pairs)
-
- if (
- self.support_sync
- and can_sync
- or (not self.support_sync and has_foreign)
- ):
- return
-
- # from here below is just determining the best error message
- # to report. Check for a join condition using any operator
- # (not just ==), perhaps they need to turn on "viewonly=True".
- if self.support_sync and has_foreign and not can_sync:
- err = (
- "Could not locate any simple equality expressions "
- "involving locally mapped foreign key columns for "
- "%s join condition "
- "'%s' on relationship %s."
- % (
- primary and "primary" or "secondary",
- join_condition,
- self.prop,
- )
- )
- err += (
- " Ensure that referencing columns are associated "
- "with a ForeignKey or ForeignKeyConstraint, or are "
- "annotated in the join condition with the foreign() "
- "annotation. To allow comparison operators other than "
- "'==', the relationship can be marked as viewonly=True."
- )
-
- raise sa_exc.ArgumentError(err)
- else:
- err = (
- "Could not locate any relevant foreign key columns "
- "for %s join condition '%s' on relationship %s."
- % (
- primary and "primary" or "secondary",
- join_condition,
- self.prop,
- )
- )
- err += (
- " Ensure that referencing columns are associated "
- "with a ForeignKey or ForeignKeyConstraint, or are "
- "annotated in the join condition with the foreign() "
- "annotation."
- )
- raise sa_exc.ArgumentError(err)
-
- def _determine_direction(self) -> None:
- """Determine if this relationship is one to many, many to one,
- many to many.
-
- """
- if self.secondaryjoin is not None:
- self.direction = MANYTOMANY
- else:
- parentcols = util.column_set(self.parent_persist_selectable.c)
- targetcols = util.column_set(self.child_persist_selectable.c)
-
- # fk collection which suggests ONETOMANY.
- onetomany_fk = targetcols.intersection(self.foreign_key_columns)
-
- # fk collection which suggests MANYTOONE.
-
- manytoone_fk = parentcols.intersection(self.foreign_key_columns)
-
- if onetomany_fk and manytoone_fk:
- # fks on both sides. test for overlap of local/remote
- # with foreign key.
- # we will gather columns directly from their annotations
- # without deannotating, so that we can distinguish on a column
- # that refers to itself.
-
- # 1. columns that are both remote and FK suggest
- # onetomany.
- onetomany_local = self._gather_columns_with_annotation(
- self.primaryjoin, "remote", "foreign"
- )
-
- # 2. columns that are FK but are not remote (e.g. local)
- # suggest manytoone.
- manytoone_local = {
- c
- for c in self._gather_columns_with_annotation(
- self.primaryjoin, "foreign"
- )
- if "remote" not in c._annotations
- }
-
- # 3. if both collections are present, remove columns that
- # refer to themselves. This is for the case of
- # and_(Me.id == Me.remote_id, Me.version == Me.version)
- if onetomany_local and manytoone_local:
- self_equated = self.remote_columns.intersection(
- self.local_columns
- )
- onetomany_local = onetomany_local.difference(self_equated)
- manytoone_local = manytoone_local.difference(self_equated)
-
- # at this point, if only one or the other collection is
- # present, we know the direction, otherwise it's still
- # ambiguous.
-
- if onetomany_local and not manytoone_local:
- self.direction = ONETOMANY
- elif manytoone_local and not onetomany_local:
- self.direction = MANYTOONE
- else:
- raise sa_exc.ArgumentError(
- "Can't determine relationship"
- " direction for relationship '%s' - foreign "
- "key columns within the join condition are present "
- "in both the parent and the child's mapped tables. "
- "Ensure that only those columns referring "
- "to a parent column are marked as foreign, "
- "either via the foreign() annotation or "
- "via the foreign_keys argument." % self.prop
- )
- elif onetomany_fk:
- self.direction = ONETOMANY
- elif manytoone_fk:
- self.direction = MANYTOONE
- else:
- raise sa_exc.ArgumentError(
- "Can't determine relationship "
- "direction for relationship '%s' - foreign "
- "key columns are present in neither the parent "
- "nor the child's mapped tables" % self.prop
- )
-
- def _deannotate_pairs(
- self, collection: _ColumnPairIterable
- ) -> _MutableColumnPairs:
- """provide deannotation for the various lists of
- pairs, so that using them in hashes doesn't incur
- high-overhead __eq__() comparisons against
- original columns mapped.
-
- """
- return [(x._deannotate(), y._deannotate()) for x, y in collection]
-
- def _setup_pairs(self) -> None:
- sync_pairs: _MutableColumnPairs = []
- lrp: util.OrderedSet[Tuple[ColumnElement[Any], ColumnElement[Any]]] = (
- util.OrderedSet([])
- )
- secondary_sync_pairs: _MutableColumnPairs = []
-
- def go(
- joincond: ColumnElement[bool],
- collection: _MutableColumnPairs,
- ) -> None:
- def visit_binary(
- binary: BinaryExpression[Any],
- left: ColumnElement[Any],
- right: ColumnElement[Any],
- ) -> None:
- if (
- "remote" in right._annotations
- and "remote" not in left._annotations
- and self.can_be_synced_fn(left)
- ):
- lrp.add((left, right))
- elif (
- "remote" in left._annotations
- and "remote" not in right._annotations
- and self.can_be_synced_fn(right)
- ):
- lrp.add((right, left))
- if binary.operator is operators.eq and self.can_be_synced_fn(
- left, right
- ):
- if "foreign" in right._annotations:
- collection.append((left, right))
- elif "foreign" in left._annotations:
- collection.append((right, left))
-
- visit_binary_product(visit_binary, joincond)
-
- for joincond, collection in [
- (self.primaryjoin, sync_pairs),
- (self.secondaryjoin, secondary_sync_pairs),
- ]:
- if joincond is None:
- continue
- go(joincond, collection)
-
- self.local_remote_pairs = self._deannotate_pairs(lrp)
- self.synchronize_pairs = self._deannotate_pairs(sync_pairs)
- self.secondary_synchronize_pairs = self._deannotate_pairs(
- secondary_sync_pairs
- )
-
- _track_overlapping_sync_targets: weakref.WeakKeyDictionary[
- ColumnElement[Any],
- weakref.WeakKeyDictionary[
- RelationshipProperty[Any], ColumnElement[Any]
- ],
- ] = weakref.WeakKeyDictionary()
-
- def _warn_for_conflicting_sync_targets(self) -> None:
- if not self.support_sync:
- return
-
- # we would like to detect if we are synchronizing any column
- # pairs in conflict with another relationship that wishes to sync
- # an entirely different column to the same target. This is a
- # very rare edge case so we will try to minimize the memory/overhead
- # impact of this check
- for from_, to_ in [
- (from_, to_) for (from_, to_) in self.synchronize_pairs
- ] + [
- (from_, to_) for (from_, to_) in self.secondary_synchronize_pairs
- ]:
- # save ourselves a ton of memory and overhead by only
- # considering columns that are subject to a overlapping
- # FK constraints at the core level. This condition can arise
- # if multiple relationships overlap foreign() directly, but
- # we're going to assume it's typically a ForeignKeyConstraint-
- # level configuration that benefits from this warning.
-
- if to_ not in self._track_overlapping_sync_targets:
- self._track_overlapping_sync_targets[to_] = (
- weakref.WeakKeyDictionary({self.prop: from_})
- )
- else:
- other_props = []
- prop_to_from = self._track_overlapping_sync_targets[to_]
-
- for pr, fr_ in prop_to_from.items():
- if (
- not pr.mapper._dispose_called
- and pr not in self.prop._reverse_property
- and pr.key not in self.prop._overlaps
- and self.prop.key not in pr._overlaps
- # note: the "__*" symbol is used internally by
- # SQLAlchemy as a general means of suppressing the
- # overlaps warning for some extension cases, however
- # this is not currently
- # a publicly supported symbol and may change at
- # any time.
- and "__*" not in self.prop._overlaps
- and "__*" not in pr._overlaps
- and not self.prop.parent.is_sibling(pr.parent)
- and not self.prop.mapper.is_sibling(pr.mapper)
- and not self.prop.parent.is_sibling(pr.mapper)
- and not self.prop.mapper.is_sibling(pr.parent)
- and (
- self.prop.key != pr.key
- or not self.prop.parent.common_parent(pr.parent)
- )
- ):
- other_props.append((pr, fr_))
-
- if other_props:
- util.warn(
- "relationship '%s' will copy column %s to column %s, "
- "which conflicts with relationship(s): %s. "
- "If this is not the intention, consider if these "
- "relationships should be linked with "
- "back_populates, or if viewonly=True should be "
- "applied to one or more if they are read-only. "
- "For the less common case that foreign key "
- "constraints are partially overlapping, the "
- "orm.foreign() "
- "annotation can be used to isolate the columns that "
- "should be written towards. To silence this "
- "warning, add the parameter 'overlaps=\"%s\"' to the "
- "'%s' relationship."
- % (
- self.prop,
- from_,
- to_,
- ", ".join(
- sorted(
- "'%s' (copies %s to %s)" % (pr, fr_, to_)
- for (pr, fr_) in other_props
- )
- ),
- ",".join(sorted(pr.key for pr, fr in other_props)),
- self.prop,
- ),
- code="qzyx",
- )
- self._track_overlapping_sync_targets[to_][self.prop] = from_
-
- @util.memoized_property
- def remote_columns(self) -> Set[ColumnElement[Any]]:
- return self._gather_join_annotations("remote")
-
- @util.memoized_property
- def local_columns(self) -> Set[ColumnElement[Any]]:
- return self._gather_join_annotations("local")
-
- @util.memoized_property
- def foreign_key_columns(self) -> Set[ColumnElement[Any]]:
- return self._gather_join_annotations("foreign")
-
- def _gather_join_annotations(
- self, annotation: str
- ) -> Set[ColumnElement[Any]]:
- s = set(
- self._gather_columns_with_annotation(self.primaryjoin, annotation)
- )
- if self.secondaryjoin is not None:
- s.update(
- self._gather_columns_with_annotation(
- self.secondaryjoin, annotation
- )
- )
- return {x._deannotate() for x in s}
-
- def _gather_columns_with_annotation(
- self, clause: ColumnElement[Any], *annotation: Iterable[str]
- ) -> Set[ColumnElement[Any]]:
- annotation_set = set(annotation)
- return {
- cast(ColumnElement[Any], col)
- for col in visitors.iterate(clause, {})
- if annotation_set.issubset(col._annotations)
- }
-
- @util.memoized_property
- def _secondary_lineage_set(self) -> FrozenSet[ColumnElement[Any]]:
- if self.secondary is not None:
- return frozenset(
- itertools.chain(*[c.proxy_set for c in self.secondary.c])
- )
- else:
- return util.EMPTY_SET
-
- def join_targets(
- self,
- source_selectable: Optional[FromClause],
- dest_selectable: FromClause,
- aliased: bool,
- single_crit: Optional[ColumnElement[bool]] = None,
- extra_criteria: Tuple[ColumnElement[bool], ...] = (),
- ) -> Tuple[
- ColumnElement[bool],
- Optional[ColumnElement[bool]],
- Optional[FromClause],
- Optional[ClauseAdapter],
- FromClause,
- ]:
- """Given a source and destination selectable, create a
- join between them.
-
- This takes into account aliasing the join clause
- to reference the appropriate corresponding columns
- in the target objects, as well as the extra child
- criterion, equivalent column sets, etc.
-
- """
- # place a barrier on the destination such that
- # replacement traversals won't ever dig into it.
- # its internal structure remains fixed
- # regardless of context.
- dest_selectable = _shallow_annotate(
- dest_selectable, {"no_replacement_traverse": True}
- )
-
- primaryjoin, secondaryjoin, secondary = (
- self.primaryjoin,
- self.secondaryjoin,
- self.secondary,
- )
-
- # adjust the join condition for single table inheritance,
- # in the case that the join is to a subclass
- # this is analogous to the
- # "_adjust_for_single_table_inheritance()" method in Query.
-
- if single_crit is not None:
- if secondaryjoin is not None:
- secondaryjoin = secondaryjoin & single_crit
- else:
- primaryjoin = primaryjoin & single_crit
-
- if extra_criteria:
-
- def mark_exclude_cols(
- elem: SupportsAnnotations, annotations: _AnnotationDict
- ) -> SupportsAnnotations:
- """note unrelated columns in the "extra criteria" as either
- should be adapted or not adapted, even though they are not
- part of our "local" or "remote" side.
-
- see #9779 for this case, as well as #11010 for a follow up
-
- """
-
- parentmapper_for_element = elem._annotations.get(
- "parentmapper", None
- )
-
- if (
- parentmapper_for_element is not self.prop.parent
- and parentmapper_for_element is not self.prop.mapper
- and elem not in self._secondary_lineage_set
- ):
- return _safe_annotate(elem, annotations)
- else:
- return elem
-
- extra_criteria = tuple(
- _deep_annotate(
- elem,
- {"should_not_adapt": True},
- annotate_callable=mark_exclude_cols,
- )
- for elem in extra_criteria
- )
-
- if secondaryjoin is not None:
- secondaryjoin = secondaryjoin & sql.and_(*extra_criteria)
- else:
- primaryjoin = primaryjoin & sql.and_(*extra_criteria)
-
- if aliased:
- if secondary is not None:
- secondary = secondary._anonymous_fromclause(flat=True)
- primary_aliasizer = ClauseAdapter(
- secondary,
- exclude_fn=_local_col_exclude,
- )
- secondary_aliasizer = ClauseAdapter(
- dest_selectable, equivalents=self.child_equivalents
- ).chain(primary_aliasizer)
- if source_selectable is not None:
- primary_aliasizer = ClauseAdapter(
- secondary,
- exclude_fn=_local_col_exclude,
- ).chain(
- ClauseAdapter(
- source_selectable,
- equivalents=self.parent_equivalents,
- )
- )
-
- secondaryjoin = secondary_aliasizer.traverse(secondaryjoin)
- else:
- primary_aliasizer = ClauseAdapter(
- dest_selectable,
- exclude_fn=_local_col_exclude,
- equivalents=self.child_equivalents,
- )
- if source_selectable is not None:
- primary_aliasizer.chain(
- ClauseAdapter(
- source_selectable,
- exclude_fn=_remote_col_exclude,
- equivalents=self.parent_equivalents,
- )
- )
- secondary_aliasizer = None
-
- primaryjoin = primary_aliasizer.traverse(primaryjoin)
- target_adapter = secondary_aliasizer or primary_aliasizer
- target_adapter.exclude_fn = None
- else:
- target_adapter = None
- return (
- primaryjoin,
- secondaryjoin,
- secondary,
- target_adapter,
- dest_selectable,
- )
-
- def create_lazy_clause(self, reverse_direction: bool = False) -> Tuple[
- ColumnElement[bool],
- Dict[str, ColumnElement[Any]],
- Dict[ColumnElement[Any], ColumnElement[Any]],
- ]:
- binds: Dict[ColumnElement[Any], BindParameter[Any]] = {}
- equated_columns: Dict[ColumnElement[Any], ColumnElement[Any]] = {}
-
- has_secondary = self.secondaryjoin is not None
-
- if has_secondary:
- lookup = collections.defaultdict(list)
- for l, r in self.local_remote_pairs:
- lookup[l].append((l, r))
- equated_columns[r] = l
- elif not reverse_direction:
- for l, r in self.local_remote_pairs:
- equated_columns[r] = l
- else:
- for l, r in self.local_remote_pairs:
- equated_columns[l] = r
-
- def col_to_bind(
- element: ColumnElement[Any], **kw: Any
- ) -> Optional[BindParameter[Any]]:
- if (
- (not reverse_direction and "local" in element._annotations)
- or reverse_direction
- and (
- (has_secondary and element in lookup)
- or (not has_secondary and "remote" in element._annotations)
- )
- ):
- if element not in binds:
- binds[element] = sql.bindparam(
- None, None, type_=element.type, unique=True
- )
- return binds[element]
- return None
-
- lazywhere = self.primaryjoin
- if self.secondaryjoin is None or not reverse_direction:
- lazywhere = visitors.replacement_traverse(
- lazywhere, {}, col_to_bind
- )
-
- if self.secondaryjoin is not None:
- secondaryjoin = self.secondaryjoin
- if reverse_direction:
- secondaryjoin = visitors.replacement_traverse(
- secondaryjoin, {}, col_to_bind
- )
- lazywhere = sql.and_(lazywhere, secondaryjoin)
-
- bind_to_col = {binds[col].key: col for col in binds}
-
- return lazywhere, bind_to_col, equated_columns
-
-
-class _ColInAnnotations:
- """Serializable object that tests for names in c._annotations.
-
- TODO: does this need to be serializable anymore? can we find what the
- use case was for that?
-
- """
-
- __slots__ = ("names",)
-
- def __init__(self, *names: str):
- self.names = frozenset(names)
-
- def __call__(self, c: ClauseElement) -> bool:
- return bool(self.names.intersection(c._annotations))
-
-
-_local_col_exclude = _ColInAnnotations("local", "should_not_adapt")
-_remote_col_exclude = _ColInAnnotations("remote", "should_not_adapt")
-
-
-class Relationship(
- RelationshipProperty[_T],
- _DeclarativeMapped[_T],
-):
- """Describes an object property that holds a single item or list
- of items that correspond to a related database table.
-
- Public constructor is the :func:`_orm.relationship` function.
-
- .. seealso::
-
- :ref:`relationship_config_toplevel`
-
- .. versionchanged:: 2.0 Added :class:`_orm.Relationship` as a Declarative
- compatible subclass for :class:`_orm.RelationshipProperty`.
-
- """
-
- inherit_cache = True
- """:meta private:"""
-
-
-class _RelationshipDeclared( # type: ignore[misc]
- Relationship[_T],
- WriteOnlyMapped[_T], # not compatible with Mapped[_T]
- DynamicMapped[_T], # not compatible with Mapped[_T]
-):
- """Relationship subclass used implicitly for declarative mapping."""
-
- inherit_cache = True
- """:meta private:"""
-
- @classmethod
- def _mapper_property_name(cls) -> str:
- return "Relationship"
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/scoping.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/scoping.py
deleted file mode 100644
index 819616a..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/scoping.py
+++ /dev/null
@@ -1,2165 +0,0 @@
-# orm/scoping.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
-
-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 Iterator
-from typing import Optional
-from typing import overload
-from typing import Sequence
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from .session import _S
-from .session import Session
-from .. import exc as sa_exc
-from .. import util
-from ..util import create_proxy_methods
-from ..util import ScopedRegistry
-from ..util import ThreadLocalRegistry
-from ..util import warn
-from ..util import warn_deprecated
-from ..util.typing import Protocol
-
-if TYPE_CHECKING:
- from ._typing import _EntityType
- from ._typing import _IdentityKeyType
- from ._typing import OrmExecuteOptionsParameter
- from .identity import IdentityMap
- from .interfaces import ORMOption
- from .mapper import Mapper
- from .query import Query
- from .query import RowReturningQuery
- from .session import _BindArguments
- from .session import _EntityBindKey
- from .session import _PKIdentityArgument
- from .session import _SessionBind
- from .session import sessionmaker
- from .session import SessionTransaction
- from ..engine import Connection
- from ..engine import CursorResult
- from ..engine import Engine
- from ..engine import Result
- from ..engine import Row
- from ..engine import RowMapping
- from ..engine.interfaces import _CoreAnyExecuteParams
- from ..engine.interfaces import _CoreSingleExecuteParams
- from ..engine.interfaces import CoreExecuteOptionsParameter
- from ..engine.result import ScalarResult
- from ..sql._typing import _ColumnsClauseArgument
- from ..sql._typing import _T0
- from ..sql._typing import _T1
- from ..sql._typing import _T2
- from ..sql._typing import _T3
- from ..sql._typing import _T4
- from ..sql._typing import _T5
- from ..sql._typing import _T6
- from ..sql._typing import _T7
- from ..sql._typing import _TypedColumnClauseArgument as _TCCA
- from ..sql.base import Executable
- from ..sql.dml import UpdateBase
- from ..sql.elements import ClauseElement
- from ..sql.roles import TypedColumnsClauseRole
- from ..sql.selectable import ForUpdateParameter
- from ..sql.selectable import TypedReturnsRows
-
-_T = TypeVar("_T", bound=Any)
-
-
-class QueryPropertyDescriptor(Protocol):
- """Describes the type applied to a class-level
- :meth:`_orm.scoped_session.query_property` attribute.
-
- .. versionadded:: 2.0.5
-
- """
-
- def __get__(self, instance: Any, owner: Type[_T]) -> Query[_T]: ...
-
-
-_O = TypeVar("_O", bound=object)
-
-__all__ = ["scoped_session"]
-
-
-@create_proxy_methods(
- Session,
- ":class:`_orm.Session`",
- ":class:`_orm.scoping.scoped_session`",
- classmethods=["close_all", "object_session", "identity_key"],
- methods=[
- "__contains__",
- "__iter__",
- "add",
- "add_all",
- "begin",
- "begin_nested",
- "close",
- "reset",
- "commit",
- "connection",
- "delete",
- "execute",
- "expire",
- "expire_all",
- "expunge",
- "expunge_all",
- "flush",
- "get",
- "get_one",
- "get_bind",
- "is_modified",
- "bulk_save_objects",
- "bulk_insert_mappings",
- "bulk_update_mappings",
- "merge",
- "query",
- "refresh",
- "rollback",
- "scalar",
- "scalars",
- ],
- attributes=[
- "bind",
- "dirty",
- "deleted",
- "new",
- "identity_map",
- "is_active",
- "autoflush",
- "no_autoflush",
- "info",
- ],
-)
-class scoped_session(Generic[_S]):
- """Provides scoped management of :class:`.Session` objects.
-
- See :ref:`unitofwork_contextual` for a tutorial.
-
- .. note::
-
- When using :ref:`asyncio_toplevel`, the async-compatible
- :class:`_asyncio.async_scoped_session` class should be
- used in place of :class:`.scoped_session`.
-
- """
-
- _support_async: bool = False
-
- session_factory: sessionmaker[_S]
- """The `session_factory` provided to `__init__` is stored in this
- attribute and may be accessed at a later time. This can be useful when
- a new non-scoped :class:`.Session` is needed."""
-
- registry: ScopedRegistry[_S]
-
- def __init__(
- self,
- session_factory: sessionmaker[_S],
- scopefunc: Optional[Callable[[], Any]] = None,
- ):
- """Construct a new :class:`.scoped_session`.
-
- :param session_factory: a factory to create new :class:`.Session`
- instances. This is usually, but not necessarily, an instance
- of :class:`.sessionmaker`.
- :param scopefunc: optional function which defines
- the current scope. If not passed, the :class:`.scoped_session`
- object assumes "thread-local" scope, and will use
- a Python ``threading.local()`` in order to maintain the current
- :class:`.Session`. If passed, the function should return
- a hashable token; this token will be used as the key in a
- dictionary in order to store and retrieve the current
- :class:`.Session`.
-
- """
- self.session_factory = session_factory
-
- if scopefunc:
- self.registry = ScopedRegistry(session_factory, scopefunc)
- else:
- self.registry = ThreadLocalRegistry(session_factory)
-
- @property
- def _proxied(self) -> _S:
- return self.registry()
-
- def __call__(self, **kw: Any) -> _S:
- r"""Return the current :class:`.Session`, creating it
- using the :attr:`.scoped_session.session_factory` if not present.
-
- :param \**kw: Keyword arguments will be passed to the
- :attr:`.scoped_session.session_factory` callable, if an existing
- :class:`.Session` is not present. If the :class:`.Session` is present
- and keyword arguments have been passed,
- :exc:`~sqlalchemy.exc.InvalidRequestError` is raised.
-
- """
- if kw:
- if self.registry.has():
- raise sa_exc.InvalidRequestError(
- "Scoped session is already present; "
- "no new arguments may be specified."
- )
- else:
- sess = self.session_factory(**kw)
- self.registry.set(sess)
- else:
- sess = self.registry()
- if not self._support_async and sess._is_asyncio:
- warn_deprecated(
- "Using `scoped_session` with asyncio is deprecated and "
- "will raise an error in a future version. "
- "Please use `async_scoped_session` instead.",
- "1.4.23",
- )
- return sess
-
- def configure(self, **kwargs: Any) -> None:
- """reconfigure the :class:`.sessionmaker` used by this
- :class:`.scoped_session`.
-
- See :meth:`.sessionmaker.configure`.
-
- """
-
- if self.registry.has():
- warn(
- "At least one scoped session is already present. "
- " configure() can not affect sessions that have "
- "already been created."
- )
-
- self.session_factory.configure(**kwargs)
-
- def remove(self) -> None:
- """Dispose of the current :class:`.Session`, if present.
-
- This will first call :meth:`.Session.close` method
- on the current :class:`.Session`, which releases any existing
- transactional/connection resources still being held; transactions
- specifically are rolled back. The :class:`.Session` is then
- discarded. Upon next usage within the same scope,
- the :class:`.scoped_session` will produce a new
- :class:`.Session` object.
-
- """
-
- if self.registry.has():
- self.registry().close()
- self.registry.clear()
-
- def query_property(
- self, query_cls: Optional[Type[Query[_T]]] = None
- ) -> QueryPropertyDescriptor:
- """return a class property which produces a legacy
- :class:`_query.Query` object against the class and the current
- :class:`.Session` when called.
-
- .. legacy:: The :meth:`_orm.scoped_session.query_property` accessor
- is specific to the legacy :class:`.Query` object and is not
- considered to be part of :term:`2.0-style` ORM use.
-
- e.g.::
-
- from sqlalchemy.orm import QueryPropertyDescriptor
- from sqlalchemy.orm import scoped_session
- from sqlalchemy.orm import sessionmaker
-
- Session = scoped_session(sessionmaker())
-
- class MyClass:
- query: QueryPropertyDescriptor = Session.query_property()
-
- # after mappers are defined
- result = MyClass.query.filter(MyClass.name=='foo').all()
-
- Produces instances of the session's configured query class by
- default. To override and use a custom implementation, provide
- a ``query_cls`` callable. The callable will be invoked with
- the class's mapper as a positional argument and a session
- keyword argument.
-
- There is no limit to the number of query properties placed on
- a class.
-
- """
-
- class query:
- def __get__(s, instance: Any, owner: Type[_O]) -> Query[_O]:
- if query_cls:
- # custom query class
- return query_cls(owner, session=self.registry()) # type: ignore # noqa: E501
- else:
- # session's configured query class
- return self.registry().query(owner)
-
- return query()
-
- # START PROXY METHODS scoped_session
-
- # code within this block is **programmatically,
- # statically generated** by tools/generate_proxy_methods.py
-
- def __contains__(self, instance: object) -> bool:
- r"""Return True if the instance is associated with this session.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- The instance may be pending or persistent within the Session for a
- result of True.
-
-
- """ # noqa: E501
-
- return self._proxied.__contains__(instance)
-
- def __iter__(self) -> Iterator[object]:
- r"""Iterate over all pending or persistent instances within this
- Session.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
-
- """ # noqa: E501
-
- return self._proxied.__iter__()
-
- def add(self, instance: object, _warn: bool = True) -> None:
- r"""Place an object into this :class:`_orm.Session`.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- Objects that are in the :term:`transient` state when passed to the
- :meth:`_orm.Session.add` method will move to the
- :term:`pending` state, until the next flush, at which point they
- will move to the :term:`persistent` state.
-
- Objects that are in the :term:`detached` state when passed to the
- :meth:`_orm.Session.add` method will move to the :term:`persistent`
- state directly.
-
- If the transaction used by the :class:`_orm.Session` is rolled back,
- objects which were transient when they were passed to
- :meth:`_orm.Session.add` will be moved back to the
- :term:`transient` state, and will no longer be present within this
- :class:`_orm.Session`.
-
- .. seealso::
-
- :meth:`_orm.Session.add_all`
-
- :ref:`session_adding` - at :ref:`session_basics`
-
-
- """ # noqa: E501
-
- return self._proxied.add(instance, _warn=_warn)
-
- def add_all(self, instances: Iterable[object]) -> None:
- r"""Add the given collection of instances to this :class:`_orm.Session`.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- See the documentation for :meth:`_orm.Session.add` for a general
- behavioral description.
-
- .. seealso::
-
- :meth:`_orm.Session.add`
-
- :ref:`session_adding` - at :ref:`session_basics`
-
-
- """ # noqa: E501
-
- return self._proxied.add_all(instances)
-
- def begin(self, nested: bool = False) -> SessionTransaction:
- r"""Begin a transaction, or nested transaction,
- on this :class:`.Session`, if one is not already begun.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- The :class:`_orm.Session` object features **autobegin** behavior,
- so that normally it is not necessary to call the
- :meth:`_orm.Session.begin`
- method explicitly. However, it may be used in order to control
- the scope of when the transactional state is begun.
-
- When used to begin the outermost transaction, an error is raised
- if this :class:`.Session` is already inside of a transaction.
-
- :param nested: if True, begins a SAVEPOINT transaction and is
- equivalent to calling :meth:`~.Session.begin_nested`. For
- documentation on SAVEPOINT transactions, please see
- :ref:`session_begin_nested`.
-
- :return: the :class:`.SessionTransaction` object. Note that
- :class:`.SessionTransaction`
- acts as a Python context manager, allowing :meth:`.Session.begin`
- to be used in a "with" block. See :ref:`session_explicit_begin` for
- an example.
-
- .. seealso::
-
- :ref:`session_autobegin`
-
- :ref:`unitofwork_transaction`
-
- :meth:`.Session.begin_nested`
-
-
-
- """ # noqa: E501
-
- return self._proxied.begin(nested=nested)
-
- def begin_nested(self) -> SessionTransaction:
- r"""Begin a "nested" transaction on this Session, e.g. SAVEPOINT.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- The target database(s) and associated drivers must support SQL
- SAVEPOINT for this method to function correctly.
-
- For documentation on SAVEPOINT
- transactions, please see :ref:`session_begin_nested`.
-
- :return: the :class:`.SessionTransaction` object. Note that
- :class:`.SessionTransaction` acts as a context manager, allowing
- :meth:`.Session.begin_nested` to be used in a "with" block.
- See :ref:`session_begin_nested` for a usage example.
-
- .. seealso::
-
- :ref:`session_begin_nested`
-
- :ref:`pysqlite_serializable` - special workarounds required
- with the SQLite driver in order for SAVEPOINT to work
- correctly. For asyncio use cases, see the section
- :ref:`aiosqlite_serializable`.
-
-
- """ # noqa: E501
-
- return self._proxied.begin_nested()
-
- def close(self) -> None:
- r"""Close out the transactional resources and ORM objects used by this
- :class:`_orm.Session`.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- This expunges all ORM objects associated with this
- :class:`_orm.Session`, ends any transaction in progress and
- :term:`releases` any :class:`_engine.Connection` objects which this
- :class:`_orm.Session` itself has checked out from associated
- :class:`_engine.Engine` objects. The operation then leaves the
- :class:`_orm.Session` in a state which it may be used again.
-
- .. tip::
-
- In the default running mode the :meth:`_orm.Session.close`
- method **does not prevent the Session from being used again**.
- The :class:`_orm.Session` itself does not actually have a
- distinct "closed" state; it merely means
- the :class:`_orm.Session` will release all database connections
- and ORM objects.
-
- Setting the parameter :paramref:`_orm.Session.close_resets_only`
- to ``False`` will instead make the ``close`` final, meaning that
- any further action on the session will be forbidden.
-
- .. versionchanged:: 1.4 The :meth:`.Session.close` method does not
- immediately create a new :class:`.SessionTransaction` object;
- instead, the new :class:`.SessionTransaction` is created only if
- the :class:`.Session` is used again for a database operation.
-
- .. seealso::
-
- :ref:`session_closing` - detail on the semantics of
- :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
-
- :meth:`_orm.Session.reset` - a similar method that behaves like
- ``close()`` with the parameter
- :paramref:`_orm.Session.close_resets_only` set to ``True``.
-
-
- """ # noqa: E501
-
- return self._proxied.close()
-
- def reset(self) -> None:
- r"""Close out the transactional resources and ORM objects used by this
- :class:`_orm.Session`, resetting the session to its initial state.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- This method provides for same "reset-only" behavior that the
- :meth:`_orm.Session.close` method has provided historically, where the
- state of the :class:`_orm.Session` is reset as though the object were
- brand new, and ready to be used again.
- This method may then be useful for :class:`_orm.Session` objects
- which set :paramref:`_orm.Session.close_resets_only` to ``False``,
- so that "reset only" behavior is still available.
-
- .. versionadded:: 2.0.22
-
- .. seealso::
-
- :ref:`session_closing` - detail on the semantics of
- :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
-
- :meth:`_orm.Session.close` - a similar method will additionally
- prevent re-use of the Session when the parameter
- :paramref:`_orm.Session.close_resets_only` is set to ``False``.
-
- """ # noqa: E501
-
- return self._proxied.reset()
-
- def commit(self) -> None:
- r"""Flush pending changes and commit the current transaction.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- When the COMMIT operation is complete, all objects are fully
- :term:`expired`, erasing their internal contents, which will be
- automatically re-loaded when the objects are next accessed. In the
- interim, these objects are in an expired state and will not function if
- they are :term:`detached` from the :class:`.Session`. Additionally,
- this re-load operation is not supported when using asyncio-oriented
- APIs. The :paramref:`.Session.expire_on_commit` parameter may be used
- to disable this behavior.
-
- When there is no transaction in place for the :class:`.Session`,
- indicating that no operations were invoked on this :class:`.Session`
- since the previous call to :meth:`.Session.commit`, the method will
- begin and commit an internal-only "logical" transaction, that does not
- normally affect the database unless pending flush changes were
- detected, but will still invoke event handlers and object expiration
- rules.
-
- The outermost database transaction is committed unconditionally,
- automatically releasing any SAVEPOINTs in effect.
-
- .. seealso::
-
- :ref:`session_committing`
-
- :ref:`unitofwork_transaction`
-
- :ref:`asyncio_orm_avoid_lazyloads`
-
-
- """ # noqa: E501
-
- return self._proxied.commit()
-
- def connection(
- self,
- bind_arguments: Optional[_BindArguments] = None,
- execution_options: Optional[CoreExecuteOptionsParameter] = None,
- ) -> Connection:
- r"""Return a :class:`_engine.Connection` object corresponding to this
- :class:`.Session` object's transactional state.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- Either the :class:`_engine.Connection` corresponding to the current
- transaction is returned, or if no transaction is in progress, a new
- one is begun and the :class:`_engine.Connection`
- returned (note that no
- transactional state is established with the DBAPI until the first
- SQL statement is emitted).
-
- Ambiguity in multi-bind or unbound :class:`.Session` objects can be
- resolved through any of the optional keyword arguments. This
- ultimately makes usage of the :meth:`.get_bind` method for resolution.
-
- :param bind_arguments: dictionary of bind arguments. May include
- "mapper", "bind", "clause", other custom arguments that are passed
- to :meth:`.Session.get_bind`.
-
- :param execution_options: a dictionary of execution options that will
- be passed to :meth:`_engine.Connection.execution_options`, **when the
- connection is first procured only**. If the connection is already
- present within the :class:`.Session`, a warning is emitted and
- the arguments are ignored.
-
- .. seealso::
-
- :ref:`session_transaction_isolation`
-
-
- """ # noqa: E501
-
- return self._proxied.connection(
- bind_arguments=bind_arguments, execution_options=execution_options
- )
-
- def delete(self, instance: object) -> None:
- r"""Mark an instance as deleted.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- The object is assumed to be either :term:`persistent` or
- :term:`detached` when passed; after the method is called, the
- object will remain in the :term:`persistent` state until the next
- flush proceeds. During this time, the object will also be a member
- of the :attr:`_orm.Session.deleted` collection.
-
- When the next flush proceeds, the object will move to the
- :term:`deleted` state, indicating a ``DELETE`` statement was emitted
- for its row within the current transaction. When the transaction
- is successfully committed,
- the deleted object is moved to the :term:`detached` state and is
- no longer present within this :class:`_orm.Session`.
-
- .. seealso::
-
- :ref:`session_deleting` - at :ref:`session_basics`
-
-
- """ # noqa: E501
-
- return self._proxied.delete(instance)
-
- @overload
- def execute(
- self,
- statement: TypedReturnsRows[_T],
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- _parent_execute_state: Optional[Any] = None,
- _add_event: Optional[Any] = None,
- ) -> Result[_T]: ...
-
- @overload
- def execute(
- self,
- statement: UpdateBase,
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- _parent_execute_state: Optional[Any] = None,
- _add_event: Optional[Any] = None,
- ) -> CursorResult[Any]: ...
-
- @overload
- def execute(
- self,
- statement: Executable,
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- _parent_execute_state: Optional[Any] = None,
- _add_event: Optional[Any] = None,
- ) -> Result[Any]: ...
-
- def execute(
- self,
- statement: Executable,
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- _parent_execute_state: Optional[Any] = None,
- _add_event: Optional[Any] = None,
- ) -> Result[Any]:
- r"""Execute a SQL expression construct.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- Returns a :class:`_engine.Result` object representing
- results of the statement execution.
-
- E.g.::
-
- from sqlalchemy import select
- result = session.execute(
- select(User).where(User.id == 5)
- )
-
- The API contract of :meth:`_orm.Session.execute` is similar to that
- of :meth:`_engine.Connection.execute`, the :term:`2.0 style` version
- of :class:`_engine.Connection`.
-
- .. versionchanged:: 1.4 the :meth:`_orm.Session.execute` method is
- now the primary point of ORM statement execution when using
- :term:`2.0 style` ORM usage.
-
- :param statement:
- An executable statement (i.e. an :class:`.Executable` expression
- such as :func:`_expression.select`).
-
- :param params:
- Optional dictionary, or list of dictionaries, containing
- bound parameter values. If a single dictionary, single-row
- execution occurs; if a list of dictionaries, an
- "executemany" will be invoked. The keys in each dictionary
- must correspond to parameter names present in the statement.
-
- :param execution_options: optional dictionary of execution options,
- which will be associated with the statement execution. This
- dictionary can provide a subset of the options that are accepted
- by :meth:`_engine.Connection.execution_options`, and may also
- provide additional options understood only in an ORM context.
-
- .. seealso::
-
- :ref:`orm_queryguide_execution_options` - ORM-specific execution
- options
-
- :param bind_arguments: dictionary of additional arguments to determine
- the bind. May include "mapper", "bind", or other custom arguments.
- Contents of this dictionary are passed to the
- :meth:`.Session.get_bind` method.
-
- :return: a :class:`_engine.Result` object.
-
-
-
- """ # noqa: E501
-
- return self._proxied.execute(
- statement,
- params=params,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- _parent_execute_state=_parent_execute_state,
- _add_event=_add_event,
- )
-
- def expire(
- self, instance: object, attribute_names: Optional[Iterable[str]] = None
- ) -> None:
- r"""Expire the attributes on an instance.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- Marks the attributes of an instance as out of date. When an expired
- attribute is next accessed, a query will be issued to the
- :class:`.Session` object's current transactional context in order to
- load all expired attributes for the given instance. Note that
- a highly isolated transaction will return the same values as were
- previously read in that same transaction, regardless of changes
- in database state outside of that transaction.
-
- To expire all objects in the :class:`.Session` simultaneously,
- use :meth:`Session.expire_all`.
-
- The :class:`.Session` object's default behavior is to
- expire all state whenever the :meth:`Session.rollback`
- or :meth:`Session.commit` methods are called, so that new
- state can be loaded for the new transaction. For this reason,
- calling :meth:`Session.expire` only makes sense for the specific
- case that a non-ORM SQL statement was emitted in the current
- transaction.
-
- :param instance: The instance to be refreshed.
- :param attribute_names: optional list of string attribute names
- indicating a subset of attributes to be expired.
-
- .. seealso::
-
- :ref:`session_expire` - introductory material
-
- :meth:`.Session.expire`
-
- :meth:`.Session.refresh`
-
- :meth:`_orm.Query.populate_existing`
-
-
- """ # noqa: E501
-
- return self._proxied.expire(instance, attribute_names=attribute_names)
-
- def expire_all(self) -> None:
- r"""Expires all persistent instances within this Session.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- When any attributes on a persistent instance is next accessed,
- a query will be issued using the
- :class:`.Session` object's current transactional context in order to
- load all expired attributes for the given instance. Note that
- a highly isolated transaction will return the same values as were
- previously read in that same transaction, regardless of changes
- in database state outside of that transaction.
-
- To expire individual objects and individual attributes
- on those objects, use :meth:`Session.expire`.
-
- The :class:`.Session` object's default behavior is to
- expire all state whenever the :meth:`Session.rollback`
- or :meth:`Session.commit` methods are called, so that new
- state can be loaded for the new transaction. For this reason,
- calling :meth:`Session.expire_all` is not usually needed,
- assuming the transaction is isolated.
-
- .. seealso::
-
- :ref:`session_expire` - introductory material
-
- :meth:`.Session.expire`
-
- :meth:`.Session.refresh`
-
- :meth:`_orm.Query.populate_existing`
-
-
- """ # noqa: E501
-
- return self._proxied.expire_all()
-
- def expunge(self, instance: object) -> None:
- r"""Remove the `instance` from this ``Session``.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- This will free all internal references to the instance. Cascading
- will be applied according to the *expunge* cascade rule.
-
-
- """ # noqa: E501
-
- return self._proxied.expunge(instance)
-
- def expunge_all(self) -> None:
- r"""Remove all object instances from this ``Session``.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- This is equivalent to calling ``expunge(obj)`` on all objects in this
- ``Session``.
-
-
- """ # noqa: E501
-
- return self._proxied.expunge_all()
-
- def flush(self, objects: Optional[Sequence[Any]] = None) -> None:
- r"""Flush all the object changes to the database.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- Writes out all pending object creations, deletions and modifications
- to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are
- automatically ordered by the Session's unit of work dependency
- solver.
-
- Database operations will be issued in the current transactional
- context and do not affect the state of the transaction, unless an
- error occurs, in which case the entire transaction is rolled back.
- You may flush() as often as you like within a transaction to move
- changes from Python to the database's transaction buffer.
-
- :param objects: Optional; restricts the flush operation to operate
- only on elements that are in the given collection.
-
- This feature is for an extremely narrow set of use cases where
- particular objects may need to be operated upon before the
- full flush() occurs. It is not intended for general use.
-
-
- """ # noqa: E501
-
- return self._proxied.flush(objects=objects)
-
- def get(
- self,
- entity: _EntityBindKey[_O],
- ident: _PKIdentityArgument,
- *,
- options: Optional[Sequence[ORMOption]] = None,
- populate_existing: bool = False,
- with_for_update: ForUpdateParameter = None,
- identity_token: Optional[Any] = None,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- ) -> Optional[_O]:
- r"""Return an instance based on the given primary key identifier,
- or ``None`` if not found.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- E.g.::
-
- my_user = session.get(User, 5)
-
- some_object = session.get(VersionedFoo, (5, 10))
-
- some_object = session.get(
- VersionedFoo,
- {"id": 5, "version_id": 10}
- )
-
- .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved
- from the now legacy :meth:`_orm.Query.get` method.
-
- :meth:`_orm.Session.get` is special in that it provides direct
- access to the identity map of the :class:`.Session`.
- If the given primary key identifier is present
- in the local identity map, the object is returned
- directly from this collection and no SQL is emitted,
- unless the object has been marked fully expired.
- If not present,
- a SELECT is performed in order to locate the object.
-
- :meth:`_orm.Session.get` also will perform a check if
- the object is present in the identity map and
- marked as expired - a SELECT
- is emitted to refresh the object as well as to
- ensure that the row is still present.
- If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
-
- :param entity: a mapped class or :class:`.Mapper` indicating the
- type of entity to be loaded.
-
- :param ident: A scalar, tuple, or dictionary representing the
- primary key. For a composite (e.g. multiple column) primary key,
- a tuple or dictionary should be passed.
-
- For a single-column primary key, the scalar calling form is typically
- the most expedient. If the primary key of a row is the value "5",
- the call looks like::
-
- my_object = session.get(SomeClass, 5)
-
- The tuple form contains primary key values typically in
- the order in which they correspond to the mapped
- :class:`_schema.Table`
- object's primary key columns, or if the
- :paramref:`_orm.Mapper.primary_key` configuration parameter were
- used, in
- the order used for that parameter. For example, if the primary key
- of a row is represented by the integer
- digits "5, 10" the call would look like::
-
- my_object = session.get(SomeClass, (5, 10))
-
- The dictionary form should include as keys the mapped attribute names
- corresponding to each element of the primary key. If the mapped class
- has the attributes ``id``, ``version_id`` as the attributes which
- store the object's primary key value, the call would look like::
-
- my_object = session.get(SomeClass, {"id": 5, "version_id": 10})
-
- :param options: optional sequence of loader options which will be
- applied to the query, if one is emitted.
-
- :param populate_existing: causes the method to unconditionally emit
- a SQL query and refresh the object with the newly loaded data,
- regardless of whether or not the object is already present.
-
- :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
- should be used, or may be a dictionary containing flags to
- indicate a more specific set of FOR UPDATE flags for the SELECT;
- flags should match the parameters of
- :meth:`_query.Query.with_for_update`.
- Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
-
- :param execution_options: optional dictionary of execution options,
- which will be associated with the query execution if one is emitted.
- This dictionary can provide a subset of the options that are
- accepted by :meth:`_engine.Connection.execution_options`, and may
- also provide additional options understood only in an ORM context.
-
- .. versionadded:: 1.4.29
-
- .. seealso::
-
- :ref:`orm_queryguide_execution_options` - ORM-specific execution
- options
-
- :param bind_arguments: dictionary of additional arguments to determine
- the bind. May include "mapper", "bind", or other custom arguments.
- Contents of this dictionary are passed to the
- :meth:`.Session.get_bind` method.
-
- .. versionadded: 2.0.0rc1
-
- :return: The object instance, or ``None``.
-
-
- """ # noqa: E501
-
- return self._proxied.get(
- entity,
- ident,
- options=options,
- populate_existing=populate_existing,
- with_for_update=with_for_update,
- identity_token=identity_token,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- )
-
- def get_one(
- self,
- entity: _EntityBindKey[_O],
- ident: _PKIdentityArgument,
- *,
- options: Optional[Sequence[ORMOption]] = None,
- populate_existing: bool = False,
- with_for_update: ForUpdateParameter = None,
- identity_token: Optional[Any] = None,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- ) -> _O:
- r"""Return exactly one instance based on the given primary key
- identifier, or raise an exception if not found.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- Raises ``sqlalchemy.orm.exc.NoResultFound`` if the query
- selects no rows.
-
- For a detailed documentation of the arguments see the
- method :meth:`.Session.get`.
-
- .. versionadded:: 2.0.22
-
- :return: The object instance.
-
- .. seealso::
-
- :meth:`.Session.get` - equivalent method that instead
- returns ``None`` if no row was found with the provided primary
- key
-
-
- """ # noqa: E501
-
- return self._proxied.get_one(
- entity,
- ident,
- options=options,
- populate_existing=populate_existing,
- with_for_update=with_for_update,
- identity_token=identity_token,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- )
-
- def get_bind(
- self,
- mapper: Optional[_EntityBindKey[_O]] = None,
- *,
- clause: Optional[ClauseElement] = None,
- bind: Optional[_SessionBind] = None,
- _sa_skip_events: Optional[bool] = None,
- _sa_skip_for_implicit_returning: bool = False,
- **kw: Any,
- ) -> Union[Engine, Connection]:
- r"""Return a "bind" to which this :class:`.Session` is bound.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- The "bind" is usually an instance of :class:`_engine.Engine`,
- except in the case where the :class:`.Session` has been
- explicitly bound directly to a :class:`_engine.Connection`.
-
- For a multiply-bound or unbound :class:`.Session`, the
- ``mapper`` or ``clause`` arguments are used to determine the
- appropriate bind to return.
-
- Note that the "mapper" argument is usually present
- when :meth:`.Session.get_bind` is called via an ORM
- operation such as a :meth:`.Session.query`, each
- individual INSERT/UPDATE/DELETE operation within a
- :meth:`.Session.flush`, call, etc.
-
- The order of resolution is:
-
- 1. if mapper given and :paramref:`.Session.binds` is present,
- locate a bind based first on the mapper in use, then
- on the mapped class in use, then on any base classes that are
- present in the ``__mro__`` of the mapped class, from more specific
- superclasses to more general.
- 2. if clause given and ``Session.binds`` is present,
- locate a bind based on :class:`_schema.Table` objects
- found in the given clause present in ``Session.binds``.
- 3. if ``Session.binds`` is present, return that.
- 4. if clause given, attempt to return a bind
- linked to the :class:`_schema.MetaData` ultimately
- associated with the clause.
- 5. if mapper given, attempt to return a bind
- linked to the :class:`_schema.MetaData` ultimately
- associated with the :class:`_schema.Table` or other
- selectable to which the mapper is mapped.
- 6. No bind can be found, :exc:`~sqlalchemy.exc.UnboundExecutionError`
- is raised.
-
- Note that the :meth:`.Session.get_bind` method can be overridden on
- a user-defined subclass of :class:`.Session` to provide any kind
- of bind resolution scheme. See the example at
- :ref:`session_custom_partitioning`.
-
- :param mapper:
- Optional mapped class or corresponding :class:`_orm.Mapper` instance.
- The bind can be derived from a :class:`_orm.Mapper` first by
- consulting the "binds" map associated with this :class:`.Session`,
- and secondly by consulting the :class:`_schema.MetaData` associated
- with the :class:`_schema.Table` to which the :class:`_orm.Mapper` is
- mapped for a bind.
-
- :param clause:
- A :class:`_expression.ClauseElement` (i.e.
- :func:`_expression.select`,
- :func:`_expression.text`,
- etc.). If the ``mapper`` argument is not present or could not
- produce a bind, the given expression construct will be searched
- for a bound element, typically a :class:`_schema.Table`
- associated with
- bound :class:`_schema.MetaData`.
-
- .. seealso::
-
- :ref:`session_partitioning`
-
- :paramref:`.Session.binds`
-
- :meth:`.Session.bind_mapper`
-
- :meth:`.Session.bind_table`
-
-
- """ # noqa: E501
-
- return self._proxied.get_bind(
- mapper=mapper,
- clause=clause,
- bind=bind,
- _sa_skip_events=_sa_skip_events,
- _sa_skip_for_implicit_returning=_sa_skip_for_implicit_returning,
- **kw,
- )
-
- def is_modified(
- self, instance: object, include_collections: bool = True
- ) -> bool:
- r"""Return ``True`` if the given instance has locally
- modified attributes.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- This method retrieves the history for each instrumented
- attribute on the instance and performs a comparison of the current
- value to its previously committed value, if any.
-
- It is in effect a more expensive and accurate
- version of checking for the given instance in the
- :attr:`.Session.dirty` collection; a full test for
- each attribute's net "dirty" status is performed.
-
- E.g.::
-
- return session.is_modified(someobject)
-
- A few caveats to this method apply:
-
- * Instances present in the :attr:`.Session.dirty` collection may
- report ``False`` when tested with this method. This is because
- the object may have received change events via attribute mutation,
- thus placing it in :attr:`.Session.dirty`, but ultimately the state
- is the same as that loaded from the database, resulting in no net
- change here.
- * Scalar attributes may not have recorded the previously set
- value when a new value was applied, if the attribute was not loaded,
- or was expired, at the time the new value was received - in these
- cases, the attribute is assumed to have a change, even if there is
- ultimately no net change against its database value. SQLAlchemy in
- most cases does not need the "old" value when a set event occurs, so
- it skips the expense of a SQL call if the old value isn't present,
- based on the assumption that an UPDATE of the scalar value is
- usually needed, and in those few cases where it isn't, is less
- expensive on average than issuing a defensive SELECT.
-
- The "old" value is fetched unconditionally upon set only if the
- attribute container has the ``active_history`` flag set to ``True``.
- This flag is set typically for primary key attributes and scalar
- object references that are not a simple many-to-one. To set this
- flag for any arbitrary mapped column, use the ``active_history``
- argument with :func:`.column_property`.
-
- :param instance: mapped instance to be tested for pending changes.
- :param include_collections: Indicates if multivalued collections
- should be included in the operation. Setting this to ``False`` is a
- way to detect only local-column based properties (i.e. scalar columns
- or many-to-one foreign keys) that would result in an UPDATE for this
- instance upon flush.
-
-
- """ # noqa: E501
-
- return self._proxied.is_modified(
- instance, include_collections=include_collections
- )
-
- def bulk_save_objects(
- self,
- objects: Iterable[object],
- return_defaults: bool = False,
- update_changed_only: bool = True,
- preserve_order: bool = True,
- ) -> None:
- r"""Perform a bulk save of the given list of objects.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- .. legacy::
-
- This method is a legacy feature as of the 2.0 series of
- SQLAlchemy. For modern bulk INSERT and UPDATE, see
- the sections :ref:`orm_queryguide_bulk_insert` and
- :ref:`orm_queryguide_bulk_update`.
-
- For general INSERT and UPDATE of existing ORM mapped objects,
- prefer standard :term:`unit of work` data management patterns,
- introduced in the :ref:`unified_tutorial` at
- :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0
- now uses :ref:`engine_insertmanyvalues` with modern dialects
- which solves previous issues of bulk INSERT slowness.
-
- :param objects: a sequence of mapped object instances. The mapped
- objects are persisted as is, and are **not** associated with the
- :class:`.Session` afterwards.
-
- For each object, whether the object is sent as an INSERT or an
- UPDATE is dependent on the same rules used by the :class:`.Session`
- in traditional operation; if the object has the
- :attr:`.InstanceState.key`
- attribute set, then the object is assumed to be "detached" and
- will result in an UPDATE. Otherwise, an INSERT is used.
-
- In the case of an UPDATE, statements are grouped based on which
- attributes have changed, and are thus to be the subject of each
- SET clause. If ``update_changed_only`` is False, then all
- attributes present within each object are applied to the UPDATE
- statement, which may help in allowing the statements to be grouped
- together into a larger executemany(), and will also reduce the
- overhead of checking history on attributes.
-
- :param return_defaults: when True, rows that are missing values which
- generate defaults, namely integer primary key defaults and sequences,
- will be inserted **one at a time**, so that the primary key value
- is available. In particular this will allow joined-inheritance
- and other multi-table mappings to insert correctly without the need
- to provide primary key values ahead of time; however,
- :paramref:`.Session.bulk_save_objects.return_defaults` **greatly
- reduces the performance gains** of the method overall. It is strongly
- advised to please use the standard :meth:`_orm.Session.add_all`
- approach.
-
- :param update_changed_only: when True, UPDATE statements are rendered
- based on those attributes in each state that have logged changes.
- When False, all attributes present are rendered into the SET clause
- with the exception of primary key attributes.
-
- :param preserve_order: when True, the order of inserts and updates
- matches exactly the order in which the objects are given. When
- False, common types of objects are grouped into inserts
- and updates, to allow for more batching opportunities.
-
- .. seealso::
-
- :doc:`queryguide/dml`
-
- :meth:`.Session.bulk_insert_mappings`
-
- :meth:`.Session.bulk_update_mappings`
-
-
- """ # noqa: E501
-
- return self._proxied.bulk_save_objects(
- objects,
- return_defaults=return_defaults,
- update_changed_only=update_changed_only,
- preserve_order=preserve_order,
- )
-
- def bulk_insert_mappings(
- self,
- mapper: Mapper[Any],
- mappings: Iterable[Dict[str, Any]],
- return_defaults: bool = False,
- render_nulls: bool = False,
- ) -> None:
- r"""Perform a bulk insert of the given list of mapping dictionaries.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- .. legacy::
-
- This method is a legacy feature as of the 2.0 series of
- SQLAlchemy. For modern bulk INSERT and UPDATE, see
- the sections :ref:`orm_queryguide_bulk_insert` and
- :ref:`orm_queryguide_bulk_update`. The 2.0 API shares
- implementation details with this method and adds new features
- as well.
-
- :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
- object,
- representing the single kind of object represented within the mapping
- list.
-
- :param mappings: a sequence of dictionaries, each one containing the
- state of the mapped row to be inserted, in terms of the attribute
- names on the mapped class. If the mapping refers to multiple tables,
- such as a joined-inheritance mapping, each dictionary must contain all
- keys to be populated into all tables.
-
- :param return_defaults: when True, the INSERT process will be altered
- to ensure that newly generated primary key values will be fetched.
- The rationale for this parameter is typically to enable
- :ref:`Joined Table Inheritance <joined_inheritance>` mappings to
- be bulk inserted.
-
- .. note:: for backends that don't support RETURNING, the
- :paramref:`_orm.Session.bulk_insert_mappings.return_defaults`
- parameter can significantly decrease performance as INSERT
- statements can no longer be batched. See
- :ref:`engine_insertmanyvalues`
- for background on which backends are affected.
-
- :param render_nulls: When True, a value of ``None`` will result
- in a NULL value being included in the INSERT statement, rather
- than the column being omitted from the INSERT. This allows all
- the rows being INSERTed to have the identical set of columns which
- allows the full set of rows to be batched to the DBAPI. Normally,
- each column-set that contains a different combination of NULL values
- than the previous row must omit a different series of columns from
- the rendered INSERT statement, which means it must be emitted as a
- separate statement. By passing this flag, the full set of rows
- are guaranteed to be batchable into one batch; the cost however is
- that server-side defaults which are invoked by an omitted column will
- be skipped, so care must be taken to ensure that these are not
- necessary.
-
- .. warning::
-
- When this flag is set, **server side default SQL values will
- not be invoked** for those columns that are inserted as NULL;
- the NULL value will be sent explicitly. Care must be taken
- to ensure that no server-side default functions need to be
- invoked for the operation as a whole.
-
- .. seealso::
-
- :doc:`queryguide/dml`
-
- :meth:`.Session.bulk_save_objects`
-
- :meth:`.Session.bulk_update_mappings`
-
-
- """ # noqa: E501
-
- return self._proxied.bulk_insert_mappings(
- mapper,
- mappings,
- return_defaults=return_defaults,
- render_nulls=render_nulls,
- )
-
- def bulk_update_mappings(
- self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]]
- ) -> None:
- r"""Perform a bulk update of the given list of mapping dictionaries.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- .. legacy::
-
- This method is a legacy feature as of the 2.0 series of
- SQLAlchemy. For modern bulk INSERT and UPDATE, see
- the sections :ref:`orm_queryguide_bulk_insert` and
- :ref:`orm_queryguide_bulk_update`. The 2.0 API shares
- implementation details with this method and adds new features
- as well.
-
- :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
- object,
- representing the single kind of object represented within the mapping
- list.
-
- :param mappings: a sequence of dictionaries, each one containing the
- state of the mapped row to be updated, in terms of the attribute names
- on the mapped class. If the mapping refers to multiple tables, such
- as a joined-inheritance mapping, each dictionary may contain keys
- corresponding to all tables. All those keys which are present and
- are not part of the primary key are applied to the SET clause of the
- UPDATE statement; the primary key values, which are required, are
- applied to the WHERE clause.
-
-
- .. seealso::
-
- :doc:`queryguide/dml`
-
- :meth:`.Session.bulk_insert_mappings`
-
- :meth:`.Session.bulk_save_objects`
-
-
- """ # noqa: E501
-
- return self._proxied.bulk_update_mappings(mapper, mappings)
-
- def merge(
- self,
- instance: _O,
- *,
- load: bool = True,
- options: Optional[Sequence[ORMOption]] = None,
- ) -> _O:
- r"""Copy the state of a given instance into a corresponding instance
- within this :class:`.Session`.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- :meth:`.Session.merge` examines the primary key attributes of the
- source instance, and attempts to reconcile it with an instance of the
- same primary key in the session. If not found locally, it attempts
- to load the object from the database based on primary key, and if
- none can be located, creates a new instance. The state of each
- attribute on the source instance is then copied to the target
- instance. The resulting target instance is then returned by the
- method; the original source instance is left unmodified, and
- un-associated with the :class:`.Session` if not already.
-
- This operation cascades to associated instances if the association is
- mapped with ``cascade="merge"``.
-
- See :ref:`unitofwork_merging` for a detailed discussion of merging.
-
- :param instance: Instance to be merged.
- :param load: Boolean, when False, :meth:`.merge` switches into
- a "high performance" mode which causes it to forego emitting history
- events as well as all database access. This flag is used for
- cases such as transferring graphs of objects into a :class:`.Session`
- from a second level cache, or to transfer just-loaded objects
- into the :class:`.Session` owned by a worker thread or process
- without re-querying the database.
-
- The ``load=False`` use case adds the caveat that the given
- object has to be in a "clean" state, that is, has no pending changes
- to be flushed - even if the incoming object is detached from any
- :class:`.Session`. This is so that when
- the merge operation populates local attributes and
- cascades to related objects and
- collections, the values can be "stamped" onto the
- target object as is, without generating any history or attribute
- events, and without the need to reconcile the incoming data with
- any existing related objects or collections that might not
- be loaded. The resulting objects from ``load=False`` are always
- produced as "clean", so it is only appropriate that the given objects
- should be "clean" as well, else this suggests a mis-use of the
- method.
- :param options: optional sequence of loader options which will be
- applied to the :meth:`_orm.Session.get` method when the merge
- operation loads the existing version of the object from the database.
-
- .. versionadded:: 1.4.24
-
-
- .. seealso::
-
- :func:`.make_transient_to_detached` - provides for an alternative
- means of "merging" a single object into the :class:`.Session`
-
-
- """ # noqa: E501
-
- return self._proxied.merge(instance, load=load, options=options)
-
- @overload
- def query(self, _entity: _EntityType[_O]) -> Query[_O]: ...
-
- @overload
- def query(
- self, _colexpr: TypedColumnsClauseRole[_T]
- ) -> RowReturningQuery[Tuple[_T]]: ...
-
- # START OVERLOADED FUNCTIONS self.query RowReturningQuery 2-8
-
- # code within this block is **programmatically,
- # statically generated** by tools/generate_tuple_map_overloads.py
-
- @overload
- def query(
- self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1]
- ) -> RowReturningQuery[Tuple[_T0, _T1]]: ...
-
- @overload
- def query(
- self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2]
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2]]: ...
-
- @overload
- def query(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3]]: ...
-
- @overload
- def query(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- __ent4: _TCCA[_T4],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4]]: ...
-
- @overload
- def query(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- __ent4: _TCCA[_T4],
- __ent5: _TCCA[_T5],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]: ...
-
- @overload
- def query(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- __ent4: _TCCA[_T4],
- __ent5: _TCCA[_T5],
- __ent6: _TCCA[_T6],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]: ...
-
- @overload
- def query(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- __ent4: _TCCA[_T4],
- __ent5: _TCCA[_T5],
- __ent6: _TCCA[_T6],
- __ent7: _TCCA[_T7],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]: ...
-
- # END OVERLOADED FUNCTIONS self.query
-
- @overload
- def query(
- self, *entities: _ColumnsClauseArgument[Any], **kwargs: Any
- ) -> Query[Any]: ...
-
- def query(
- self, *entities: _ColumnsClauseArgument[Any], **kwargs: Any
- ) -> Query[Any]:
- r"""Return a new :class:`_query.Query` object corresponding to this
- :class:`_orm.Session`.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- Note that the :class:`_query.Query` object is legacy as of
- SQLAlchemy 2.0; the :func:`_sql.select` construct is now used
- to construct ORM queries.
-
- .. seealso::
-
- :ref:`unified_tutorial`
-
- :ref:`queryguide_toplevel`
-
- :ref:`query_api_toplevel` - legacy API doc
-
-
- """ # noqa: E501
-
- return self._proxied.query(*entities, **kwargs)
-
- def refresh(
- self,
- instance: object,
- attribute_names: Optional[Iterable[str]] = None,
- with_for_update: ForUpdateParameter = None,
- ) -> None:
- r"""Expire and refresh attributes on the given instance.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- The selected attributes will first be expired as they would when using
- :meth:`_orm.Session.expire`; then a SELECT statement will be issued to
- the database to refresh column-oriented attributes with the current
- value available in the current transaction.
-
- :func:`_orm.relationship` oriented attributes will also be immediately
- loaded if they were already eagerly loaded on the object, using the
- same eager loading strategy that they were loaded with originally.
-
- .. versionadded:: 1.4 - the :meth:`_orm.Session.refresh` method
- can also refresh eagerly loaded attributes.
-
- :func:`_orm.relationship` oriented attributes that would normally
- load using the ``select`` (or "lazy") loader strategy will also
- load **if they are named explicitly in the attribute_names
- collection**, emitting a SELECT statement for the attribute using the
- ``immediate`` loader strategy. If lazy-loaded relationships are not
- named in :paramref:`_orm.Session.refresh.attribute_names`, then
- they remain as "lazy loaded" attributes and are not implicitly
- refreshed.
-
- .. versionchanged:: 2.0.4 The :meth:`_orm.Session.refresh` method
- will now refresh lazy-loaded :func:`_orm.relationship` oriented
- attributes for those which are named explicitly in the
- :paramref:`_orm.Session.refresh.attribute_names` collection.
-
- .. tip::
-
- While the :meth:`_orm.Session.refresh` method is capable of
- refreshing both column and relationship oriented attributes, its
- primary focus is on refreshing of local column-oriented attributes
- on a single instance. For more open ended "refresh" functionality,
- including the ability to refresh the attributes on many objects at
- once while having explicit control over relationship loader
- strategies, use the
- :ref:`populate existing <orm_queryguide_populate_existing>` feature
- instead.
-
- Note that a highly isolated transaction will return the same values as
- were previously read in that same transaction, regardless of changes
- in database state outside of that transaction. Refreshing
- attributes usually only makes sense at the start of a transaction
- where database rows have not yet been accessed.
-
- :param attribute_names: optional. An iterable collection of
- string attribute names indicating a subset of attributes to
- be refreshed.
-
- :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
- should be used, or may be a dictionary containing flags to
- indicate a more specific set of FOR UPDATE flags for the SELECT;
- flags should match the parameters of
- :meth:`_query.Query.with_for_update`.
- Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
-
- .. seealso::
-
- :ref:`session_expire` - introductory material
-
- :meth:`.Session.expire`
-
- :meth:`.Session.expire_all`
-
- :ref:`orm_queryguide_populate_existing` - allows any ORM query
- to refresh objects as they would be loaded normally.
-
-
- """ # noqa: E501
-
- return self._proxied.refresh(
- instance,
- attribute_names=attribute_names,
- with_for_update=with_for_update,
- )
-
- def rollback(self) -> None:
- r"""Rollback the current transaction in progress.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- If no transaction is in progress, this method is a pass-through.
-
- The method always rolls back
- the topmost database transaction, discarding any nested
- transactions that may be in progress.
-
- .. seealso::
-
- :ref:`session_rollback`
-
- :ref:`unitofwork_transaction`
-
-
- """ # noqa: E501
-
- return self._proxied.rollback()
-
- @overload
- def scalar(
- self,
- statement: TypedReturnsRows[Tuple[_T]],
- params: Optional[_CoreSingleExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- **kw: Any,
- ) -> Optional[_T]: ...
-
- @overload
- def scalar(
- self,
- statement: Executable,
- params: Optional[_CoreSingleExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- **kw: Any,
- ) -> Any: ...
-
- def scalar(
- self,
- statement: Executable,
- params: Optional[_CoreSingleExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- **kw: Any,
- ) -> Any:
- r"""Execute a statement and return a scalar result.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- Usage and parameters are the same as that of
- :meth:`_orm.Session.execute`; the return result is a scalar Python
- value.
-
-
- """ # noqa: E501
-
- return self._proxied.scalar(
- statement,
- params=params,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- **kw,
- )
-
- @overload
- def scalars(
- self,
- statement: TypedReturnsRows[Tuple[_T]],
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- **kw: Any,
- ) -> ScalarResult[_T]: ...
-
- @overload
- def scalars(
- self,
- statement: Executable,
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- **kw: Any,
- ) -> ScalarResult[Any]: ...
-
- def scalars(
- self,
- statement: Executable,
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- **kw: Any,
- ) -> ScalarResult[Any]:
- r"""Execute a statement and return the results as scalars.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- Usage and parameters are the same as that of
- :meth:`_orm.Session.execute`; the return result is a
- :class:`_result.ScalarResult` filtering object which
- will return single elements rather than :class:`_row.Row` objects.
-
- :return: a :class:`_result.ScalarResult` object
-
- .. versionadded:: 1.4.24 Added :meth:`_orm.Session.scalars`
-
- .. versionadded:: 1.4.26 Added :meth:`_orm.scoped_session.scalars`
-
- .. seealso::
-
- :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior
- of :meth:`_orm.Session.execute` to :meth:`_orm.Session.scalars`
-
-
- """ # noqa: E501
-
- return self._proxied.scalars(
- statement,
- params=params,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- **kw,
- )
-
- @property
- def bind(self) -> Optional[Union[Engine, Connection]]:
- r"""Proxy for the :attr:`_orm.Session.bind` attribute
- on behalf of the :class:`_orm.scoping.scoped_session` class.
-
- """ # noqa: E501
-
- return self._proxied.bind
-
- @bind.setter
- def bind(self, attr: Optional[Union[Engine, Connection]]) -> None:
- self._proxied.bind = attr
-
- @property
- def dirty(self) -> Any:
- r"""The set of all persistent instances considered dirty.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class
- on behalf of the :class:`_orm.scoping.scoped_session` class.
-
- E.g.::
-
- some_mapped_object in session.dirty
-
- Instances are considered dirty when they were modified but not
- deleted.
-
- Note that this 'dirty' calculation is 'optimistic'; most
- attribute-setting or collection modification operations will
- mark an instance as 'dirty' and place it in this set, even if
- there is no net change to the attribute's value. At flush
- time, the value of each attribute is compared to its
- previously saved value, and if there's no net change, no SQL
- operation will occur (this is a more expensive operation so
- it's only done at flush time).
-
- To check if an instance has actionable net changes to its
- attributes, use the :meth:`.Session.is_modified` method.
-
-
- """ # noqa: E501
-
- return self._proxied.dirty
-
- @property
- def deleted(self) -> Any:
- r"""The set of all instances marked as 'deleted' within this ``Session``
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class
- on behalf of the :class:`_orm.scoping.scoped_session` class.
-
- """ # noqa: E501
-
- return self._proxied.deleted
-
- @property
- def new(self) -> Any:
- r"""The set of all instances marked as 'new' within this ``Session``.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class
- on behalf of the :class:`_orm.scoping.scoped_session` class.
-
- """ # noqa: E501
-
- return self._proxied.new
-
- @property
- def identity_map(self) -> IdentityMap:
- r"""Proxy for the :attr:`_orm.Session.identity_map` attribute
- on behalf of the :class:`_orm.scoping.scoped_session` class.
-
- """ # noqa: E501
-
- return self._proxied.identity_map
-
- @identity_map.setter
- def identity_map(self, attr: IdentityMap) -> None:
- self._proxied.identity_map = attr
-
- @property
- def is_active(self) -> Any:
- r"""True if this :class:`.Session` not in "partial rollback" state.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class
- on behalf of the :class:`_orm.scoping.scoped_session` class.
-
- .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins
- a new transaction immediately, so this attribute will be False
- when the :class:`_orm.Session` is first instantiated.
-
- "partial rollback" state typically indicates that the flush process
- of the :class:`_orm.Session` has failed, and that the
- :meth:`_orm.Session.rollback` method must be emitted in order to
- fully roll back the transaction.
-
- If this :class:`_orm.Session` is not in a transaction at all, the
- :class:`_orm.Session` will autobegin when it is first used, so in this
- case :attr:`_orm.Session.is_active` will return True.
-
- Otherwise, if this :class:`_orm.Session` is within a transaction,
- and that transaction has not been rolled back internally, the
- :attr:`_orm.Session.is_active` will also return True.
-
- .. seealso::
-
- :ref:`faq_session_rollback`
-
- :meth:`_orm.Session.in_transaction`
-
-
- """ # noqa: E501
-
- return self._proxied.is_active
-
- @property
- def autoflush(self) -> bool:
- r"""Proxy for the :attr:`_orm.Session.autoflush` attribute
- on behalf of the :class:`_orm.scoping.scoped_session` class.
-
- """ # noqa: E501
-
- return self._proxied.autoflush
-
- @autoflush.setter
- def autoflush(self, attr: bool) -> None:
- self._proxied.autoflush = attr
-
- @property
- def no_autoflush(self) -> Any:
- r"""Return a context manager that disables autoflush.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class
- on behalf of the :class:`_orm.scoping.scoped_session` class.
-
- e.g.::
-
- with session.no_autoflush:
-
- some_object = SomeClass()
- session.add(some_object)
- # won't autoflush
- some_object.related_thing = session.query(SomeRelated).first()
-
- Operations that proceed within the ``with:`` block
- will not be subject to flushes occurring upon query
- access. This is useful when initializing a series
- of objects which involve existing database queries,
- where the uncompleted object should not yet be flushed.
-
-
- """ # noqa: E501
-
- return self._proxied.no_autoflush
-
- @property
- def info(self) -> Any:
- r"""A user-modifiable dictionary.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class
- on behalf of the :class:`_orm.scoping.scoped_session` class.
-
- The initial value of this dictionary can be populated using the
- ``info`` argument to the :class:`.Session` constructor or
- :class:`.sessionmaker` constructor or factory methods. The dictionary
- here is always local to this :class:`.Session` and can be modified
- independently of all other :class:`.Session` objects.
-
-
- """ # noqa: E501
-
- return self._proxied.info
-
- @classmethod
- def close_all(cls) -> None:
- r"""Close *all* sessions in memory.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- .. deprecated:: 1.3 The :meth:`.Session.close_all` method is deprecated and will be removed in a future release. Please refer to :func:`.session.close_all_sessions`.
-
- """ # noqa: E501
-
- return Session.close_all()
-
- @classmethod
- def object_session(cls, instance: object) -> Optional[Session]:
- r"""Return the :class:`.Session` to which an object belongs.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- This is an alias of :func:`.object_session`.
-
-
- """ # noqa: E501
-
- return Session.object_session(instance)
-
- @classmethod
- def identity_key(
- cls,
- class_: Optional[Type[Any]] = None,
- ident: Union[Any, Tuple[Any, ...]] = None,
- *,
- instance: Optional[Any] = None,
- row: Optional[Union[Row[Any], RowMapping]] = None,
- identity_token: Optional[Any] = None,
- ) -> _IdentityKeyType[Any]:
- r"""Return an identity key.
-
- .. container:: class_bases
-
- Proxied for the :class:`_orm.Session` class on
- behalf of the :class:`_orm.scoping.scoped_session` class.
-
- This is an alias of :func:`.util.identity_key`.
-
-
- """ # noqa: E501
-
- return Session.identity_key(
- class_=class_,
- ident=ident,
- instance=instance,
- row=row,
- identity_token=identity_token,
- )
-
- # END PROXY METHODS scoped_session
-
-
-ScopedSession = scoped_session
-"""Old name for backwards compatibility."""
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/session.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/session.py
deleted file mode 100644
index 3eba5aa..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/session.py
+++ /dev/null
@@ -1,5238 +0,0 @@
-# orm/session.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
-
-"""Provides the Session class and related utilities."""
-
-from __future__ import annotations
-
-import contextlib
-from enum import Enum
-import itertools
-import sys
-import typing
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Dict
-from typing import Generic
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import NoReturn
-from typing import Optional
-from typing import overload
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-import weakref
-
-from . import attributes
-from . import bulk_persistence
-from . import context
-from . import descriptor_props
-from . import exc
-from . import identity
-from . import loading
-from . import query
-from . import state as statelib
-from ._typing import _O
-from ._typing import insp_is_mapper
-from ._typing import is_composite_class
-from ._typing import is_orm_option
-from ._typing import is_user_defined_option
-from .base import _class_to_mapper
-from .base import _none_set
-from .base import _state_mapper
-from .base import instance_str
-from .base import LoaderCallableStatus
-from .base import object_mapper
-from .base import object_state
-from .base import PassiveFlag
-from .base import state_str
-from .context import FromStatement
-from .context import ORMCompileState
-from .identity import IdentityMap
-from .query import Query
-from .state import InstanceState
-from .state_changes import _StateChange
-from .state_changes import _StateChangeState
-from .state_changes import _StateChangeStates
-from .unitofwork import UOWTransaction
-from .. import engine
-from .. import exc as sa_exc
-from .. import sql
-from .. import util
-from ..engine import Connection
-from ..engine import Engine
-from ..engine.util import TransactionalContext
-from ..event import dispatcher
-from ..event import EventTarget
-from ..inspection import inspect
-from ..inspection import Inspectable
-from ..sql import coercions
-from ..sql import dml
-from ..sql import roles
-from ..sql import Select
-from ..sql import TableClause
-from ..sql import visitors
-from ..sql.base import _NoArg
-from ..sql.base import CompileState
-from ..sql.schema import Table
-from ..sql.selectable import ForUpdateArg
-from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
-from ..util import IdentitySet
-from ..util.typing import Literal
-from ..util.typing import Protocol
-
-if typing.TYPE_CHECKING:
- from ._typing import _EntityType
- from ._typing import _IdentityKeyType
- from ._typing import _InstanceDict
- from ._typing import OrmExecuteOptionsParameter
- from .interfaces import ORMOption
- from .interfaces import UserDefinedOption
- from .mapper import Mapper
- from .path_registry import PathRegistry
- from .query import RowReturningQuery
- from ..engine import CursorResult
- from ..engine import Result
- from ..engine import Row
- from ..engine import RowMapping
- from ..engine.base import Transaction
- from ..engine.base import TwoPhaseTransaction
- from ..engine.interfaces import _CoreAnyExecuteParams
- from ..engine.interfaces import _CoreSingleExecuteParams
- from ..engine.interfaces import _ExecuteOptions
- from ..engine.interfaces import CoreExecuteOptionsParameter
- from ..engine.result import ScalarResult
- from ..event import _InstanceLevelDispatch
- from ..sql._typing import _ColumnsClauseArgument
- from ..sql._typing import _InfoType
- from ..sql._typing import _T0
- from ..sql._typing import _T1
- from ..sql._typing import _T2
- from ..sql._typing import _T3
- from ..sql._typing import _T4
- from ..sql._typing import _T5
- from ..sql._typing import _T6
- from ..sql._typing import _T7
- from ..sql._typing import _TypedColumnClauseArgument as _TCCA
- from ..sql.base import Executable
- from ..sql.base import ExecutableOption
- from ..sql.dml import UpdateBase
- from ..sql.elements import ClauseElement
- from ..sql.roles import TypedColumnsClauseRole
- from ..sql.selectable import ForUpdateParameter
- from ..sql.selectable import TypedReturnsRows
-
-_T = TypeVar("_T", bound=Any)
-
-__all__ = [
- "Session",
- "SessionTransaction",
- "sessionmaker",
- "ORMExecuteState",
- "close_all_sessions",
- "make_transient",
- "make_transient_to_detached",
- "object_session",
-]
-
-_sessions: weakref.WeakValueDictionary[int, Session] = (
- weakref.WeakValueDictionary()
-)
-"""Weak-referencing dictionary of :class:`.Session` objects.
-"""
-
-statelib._sessions = _sessions
-
-_PKIdentityArgument = Union[Any, Tuple[Any, ...]]
-
-_BindArguments = Dict[str, Any]
-
-_EntityBindKey = Union[Type[_O], "Mapper[_O]"]
-_SessionBindKey = Union[Type[Any], "Mapper[Any]", "TableClause", str]
-_SessionBind = Union["Engine", "Connection"]
-
-JoinTransactionMode = Literal[
- "conditional_savepoint",
- "rollback_only",
- "control_fully",
- "create_savepoint",
-]
-
-
-class _ConnectionCallableProto(Protocol):
- """a callable that returns a :class:`.Connection` given an instance.
-
- This callable, when present on a :class:`.Session`, is called only from the
- ORM's persistence mechanism (i.e. the unit of work flush process) to allow
- for connection-per-instance schemes (i.e. horizontal sharding) to be used
- as persistence time.
-
- This callable is not present on a plain :class:`.Session`, however
- is established when using the horizontal sharding extension.
-
- """
-
- def __call__(
- self,
- mapper: Optional[Mapper[Any]] = None,
- instance: Optional[object] = None,
- **kw: Any,
- ) -> Connection: ...
-
-
-def _state_session(state: InstanceState[Any]) -> Optional[Session]:
- """Given an :class:`.InstanceState`, return the :class:`.Session`
- associated, if any.
- """
- return state.session
-
-
-class _SessionClassMethods:
- """Class-level methods for :class:`.Session`, :class:`.sessionmaker`."""
-
- @classmethod
- @util.deprecated(
- "1.3",
- "The :meth:`.Session.close_all` method is deprecated and will be "
- "removed in a future release. Please refer to "
- ":func:`.session.close_all_sessions`.",
- )
- def close_all(cls) -> None:
- """Close *all* sessions in memory."""
-
- close_all_sessions()
-
- @classmethod
- @util.preload_module("sqlalchemy.orm.util")
- def identity_key(
- cls,
- class_: Optional[Type[Any]] = None,
- ident: Union[Any, Tuple[Any, ...]] = None,
- *,
- instance: Optional[Any] = None,
- row: Optional[Union[Row[Any], RowMapping]] = None,
- identity_token: Optional[Any] = None,
- ) -> _IdentityKeyType[Any]:
- """Return an identity key.
-
- This is an alias of :func:`.util.identity_key`.
-
- """
- return util.preloaded.orm_util.identity_key(
- class_,
- ident,
- instance=instance,
- row=row,
- identity_token=identity_token,
- )
-
- @classmethod
- def object_session(cls, instance: object) -> Optional[Session]:
- """Return the :class:`.Session` to which an object belongs.
-
- This is an alias of :func:`.object_session`.
-
- """
-
- return object_session(instance)
-
-
-class SessionTransactionState(_StateChangeState):
- ACTIVE = 1
- PREPARED = 2
- COMMITTED = 3
- DEACTIVE = 4
- CLOSED = 5
- PROVISIONING_CONNECTION = 6
-
-
-# backwards compatibility
-ACTIVE, PREPARED, COMMITTED, DEACTIVE, CLOSED, PROVISIONING_CONNECTION = tuple(
- SessionTransactionState
-)
-
-
-class ORMExecuteState(util.MemoizedSlots):
- """Represents a call to the :meth:`_orm.Session.execute` method, as passed
- to the :meth:`.SessionEvents.do_orm_execute` event hook.
-
- .. versionadded:: 1.4
-
- .. seealso::
-
- :ref:`session_execute_events` - top level documentation on how
- to use :meth:`_orm.SessionEvents.do_orm_execute`
-
- """
-
- __slots__ = (
- "session",
- "statement",
- "parameters",
- "execution_options",
- "local_execution_options",
- "bind_arguments",
- "identity_token",
- "_compile_state_cls",
- "_starting_event_idx",
- "_events_todo",
- "_update_execution_options",
- )
-
- session: Session
- """The :class:`_orm.Session` in use."""
-
- statement: Executable
- """The SQL statement being invoked.
-
- For an ORM selection as would
- be retrieved from :class:`_orm.Query`, this is an instance of
- :class:`_sql.select` that was generated from the ORM query.
- """
-
- parameters: Optional[_CoreAnyExecuteParams]
- """Dictionary of parameters that was passed to
- :meth:`_orm.Session.execute`."""
-
- execution_options: _ExecuteOptions
- """The complete dictionary of current execution options.
-
- This is a merge of the statement level options with the
- locally passed execution options.
-
- .. seealso::
-
- :attr:`_orm.ORMExecuteState.local_execution_options`
-
- :meth:`_sql.Executable.execution_options`
-
- :ref:`orm_queryguide_execution_options`
-
- """
-
- local_execution_options: _ExecuteOptions
- """Dictionary view of the execution options passed to the
- :meth:`.Session.execute` method.
-
- This does not include options that may be associated with the statement
- being invoked.
-
- .. seealso::
-
- :attr:`_orm.ORMExecuteState.execution_options`
-
- """
-
- bind_arguments: _BindArguments
- """The dictionary passed as the
- :paramref:`_orm.Session.execute.bind_arguments` dictionary.
-
- This dictionary may be used by extensions to :class:`_orm.Session` to pass
- arguments that will assist in determining amongst a set of database
- connections which one should be used to invoke this statement.
-
- """
-
- _compile_state_cls: Optional[Type[ORMCompileState]]
- _starting_event_idx: int
- _events_todo: List[Any]
- _update_execution_options: Optional[_ExecuteOptions]
-
- def __init__(
- self,
- session: Session,
- statement: Executable,
- parameters: Optional[_CoreAnyExecuteParams],
- execution_options: _ExecuteOptions,
- bind_arguments: _BindArguments,
- compile_state_cls: Optional[Type[ORMCompileState]],
- events_todo: List[_InstanceLevelDispatch[Session]],
- ):
- """Construct a new :class:`_orm.ORMExecuteState`.
-
- this object is constructed internally.
-
- """
- self.session = session
- self.statement = statement
- self.parameters = parameters
- self.local_execution_options = execution_options
- self.execution_options = statement._execution_options.union(
- execution_options
- )
- self.bind_arguments = bind_arguments
- self._compile_state_cls = compile_state_cls
- self._events_todo = list(events_todo)
-
- def _remaining_events(self) -> List[_InstanceLevelDispatch[Session]]:
- return self._events_todo[self._starting_event_idx + 1 :]
-
- def invoke_statement(
- self,
- statement: Optional[Executable] = None,
- params: Optional[_CoreAnyExecuteParams] = None,
- execution_options: Optional[OrmExecuteOptionsParameter] = None,
- bind_arguments: Optional[_BindArguments] = None,
- ) -> Result[Any]:
- """Execute the statement represented by this
- :class:`.ORMExecuteState`, without re-invoking events that have
- already proceeded.
-
- This method essentially performs a re-entrant execution of the current
- statement for which the :meth:`.SessionEvents.do_orm_execute` event is
- being currently invoked. The use case for this is for event handlers
- that want to override how the ultimate
- :class:`_engine.Result` object is returned, such as for schemes that
- retrieve results from an offline cache or which concatenate results
- from multiple executions.
-
- When the :class:`_engine.Result` object is returned by the actual
- handler function within :meth:`_orm.SessionEvents.do_orm_execute` and
- is propagated to the calling
- :meth:`_orm.Session.execute` method, the remainder of the
- :meth:`_orm.Session.execute` method is preempted and the
- :class:`_engine.Result` object is returned to the caller of
- :meth:`_orm.Session.execute` immediately.
-
- :param statement: optional statement to be invoked, in place of the
- statement currently represented by :attr:`.ORMExecuteState.statement`.
-
- :param params: optional dictionary of parameters or list of parameters
- which will be merged into the existing
- :attr:`.ORMExecuteState.parameters` of this :class:`.ORMExecuteState`.
-
- .. versionchanged:: 2.0 a list of parameter dictionaries is accepted
- for executemany executions.
-
- :param execution_options: optional dictionary of execution options
- will be merged into the existing
- :attr:`.ORMExecuteState.execution_options` of this
- :class:`.ORMExecuteState`.
-
- :param bind_arguments: optional dictionary of bind_arguments
- which will be merged amongst the current
- :attr:`.ORMExecuteState.bind_arguments`
- of this :class:`.ORMExecuteState`.
-
- :return: a :class:`_engine.Result` object with ORM-level results.
-
- .. seealso::
-
- :ref:`do_orm_execute_re_executing` - background and examples on the
- appropriate usage of :meth:`_orm.ORMExecuteState.invoke_statement`.
-
-
- """
-
- if statement is None:
- statement = self.statement
-
- _bind_arguments = dict(self.bind_arguments)
- if bind_arguments:
- _bind_arguments.update(bind_arguments)
- _bind_arguments["_sa_skip_events"] = True
-
- _params: Optional[_CoreAnyExecuteParams]
- if params:
- if self.is_executemany:
- _params = []
- exec_many_parameters = cast(
- "List[Dict[str, Any]]", self.parameters
- )
- for _existing_params, _new_params in itertools.zip_longest(
- exec_many_parameters,
- cast("List[Dict[str, Any]]", params),
- ):
- if _existing_params is None or _new_params is None:
- raise sa_exc.InvalidRequestError(
- f"Can't apply executemany parameters to "
- f"statement; number of parameter sets passed to "
- f"Session.execute() ({len(exec_many_parameters)}) "
- f"does not match number of parameter sets given "
- f"to ORMExecuteState.invoke_statement() "
- f"({len(params)})"
- )
- _existing_params = dict(_existing_params)
- _existing_params.update(_new_params)
- _params.append(_existing_params)
- else:
- _params = dict(cast("Dict[str, Any]", self.parameters))
- _params.update(cast("Dict[str, Any]", params))
- else:
- _params = self.parameters
-
- _execution_options = self.local_execution_options
- if execution_options:
- _execution_options = _execution_options.union(execution_options)
-
- return self.session._execute_internal(
- statement,
- _params,
- execution_options=_execution_options,
- bind_arguments=_bind_arguments,
- _parent_execute_state=self,
- )
-
- @property
- def bind_mapper(self) -> Optional[Mapper[Any]]:
- """Return the :class:`_orm.Mapper` that is the primary "bind" mapper.
-
- For an :class:`_orm.ORMExecuteState` object invoking an ORM
- statement, that is, the :attr:`_orm.ORMExecuteState.is_orm_statement`
- attribute is ``True``, this attribute will return the
- :class:`_orm.Mapper` that is considered to be the "primary" mapper
- of the statement. The term "bind mapper" refers to the fact that
- a :class:`_orm.Session` object may be "bound" to multiple
- :class:`_engine.Engine` objects keyed to mapped classes, and the
- "bind mapper" determines which of those :class:`_engine.Engine` objects
- would be selected.
-
- For a statement that is invoked against a single mapped class,
- :attr:`_orm.ORMExecuteState.bind_mapper` is intended to be a reliable
- way of getting this mapper.
-
- .. versionadded:: 1.4.0b2
-
- .. seealso::
-
- :attr:`_orm.ORMExecuteState.all_mappers`
-
-
- """
- mp: Optional[Mapper[Any]] = self.bind_arguments.get("mapper", None)
- return mp
-
- @property
- def all_mappers(self) -> Sequence[Mapper[Any]]:
- """Return a sequence of all :class:`_orm.Mapper` objects that are
- involved at the top level of this statement.
-
- By "top level" we mean those :class:`_orm.Mapper` objects that would
- be represented in the result set rows for a :func:`_sql.select`
- query, or for a :func:`_dml.update` or :func:`_dml.delete` query,
- the mapper that is the main subject of the UPDATE or DELETE.
-
- .. versionadded:: 1.4.0b2
-
- .. seealso::
-
- :attr:`_orm.ORMExecuteState.bind_mapper`
-
-
-
- """
- if not self.is_orm_statement:
- return []
- elif isinstance(self.statement, (Select, FromStatement)):
- result = []
- seen = set()
- for d in self.statement.column_descriptions:
- ent = d["entity"]
- if ent:
- insp = inspect(ent, raiseerr=False)
- if insp and insp.mapper and insp.mapper not in seen:
- seen.add(insp.mapper)
- result.append(insp.mapper)
- return result
- elif self.statement.is_dml and self.bind_mapper:
- return [self.bind_mapper]
- else:
- return []
-
- @property
- def is_orm_statement(self) -> bool:
- """return True if the operation is an ORM statement.
-
- This indicates that the select(), insert(), update(), or delete()
- being invoked contains ORM entities as subjects. For a statement
- that does not have ORM entities and instead refers only to
- :class:`.Table` metadata, it is invoked as a Core SQL statement
- and no ORM-level automation takes place.
-
- """
- return self._compile_state_cls is not None
-
- @property
- def is_executemany(self) -> bool:
- """return True if the parameters are a multi-element list of
- dictionaries with more than one dictionary.
-
- .. versionadded:: 2.0
-
- """
- return isinstance(self.parameters, list)
-
- @property
- def is_select(self) -> bool:
- """return True if this is a SELECT operation."""
- return self.statement.is_select
-
- @property
- def is_insert(self) -> bool:
- """return True if this is an INSERT operation."""
- return self.statement.is_dml and self.statement.is_insert
-
- @property
- def is_update(self) -> bool:
- """return True if this is an UPDATE operation."""
- return self.statement.is_dml and self.statement.is_update
-
- @property
- def is_delete(self) -> bool:
- """return True if this is a DELETE operation."""
- return self.statement.is_dml and self.statement.is_delete
-
- @property
- def _is_crud(self) -> bool:
- return isinstance(self.statement, (dml.Update, dml.Delete))
-
- def update_execution_options(self, **opts: Any) -> None:
- """Update the local execution options with new values."""
- self.local_execution_options = self.local_execution_options.union(opts)
-
- def _orm_compile_options(
- self,
- ) -> Optional[
- Union[
- context.ORMCompileState.default_compile_options,
- Type[context.ORMCompileState.default_compile_options],
- ]
- ]:
- if not self.is_select:
- return None
- try:
- opts = self.statement._compile_options
- except AttributeError:
- return None
-
- if opts is not None and opts.isinstance(
- context.ORMCompileState.default_compile_options
- ):
- return opts # type: ignore
- else:
- return None
-
- @property
- def lazy_loaded_from(self) -> Optional[InstanceState[Any]]:
- """An :class:`.InstanceState` that is using this statement execution
- for a lazy load operation.
-
- The primary rationale for this attribute is to support the horizontal
- sharding extension, where it is available within specific query
- execution time hooks created by this extension. To that end, the
- attribute is only intended to be meaningful at **query execution
- time**, and importantly not any time prior to that, including query
- compilation time.
-
- """
- return self.load_options._lazy_loaded_from
-
- @property
- def loader_strategy_path(self) -> Optional[PathRegistry]:
- """Return the :class:`.PathRegistry` for the current load path.
-
- This object represents the "path" in a query along relationships
- when a particular object or collection is being loaded.
-
- """
- opts = self._orm_compile_options()
- if opts is not None:
- return opts._current_path
- else:
- return None
-
- @property
- def is_column_load(self) -> bool:
- """Return True if the operation is refreshing column-oriented
- attributes on an existing ORM object.
-
- This occurs during operations such as :meth:`_orm.Session.refresh`,
- as well as when an attribute deferred by :func:`_orm.defer` is
- being loaded, or an attribute that was expired either directly
- by :meth:`_orm.Session.expire` or via a commit operation is being
- loaded.
-
- Handlers will very likely not want to add any options to queries
- when such an operation is occurring as the query should be a straight
- primary key fetch which should not have any additional WHERE criteria,
- and loader options travelling with the instance
- will have already been added to the query.
-
- .. versionadded:: 1.4.0b2
-
- .. seealso::
-
- :attr:`_orm.ORMExecuteState.is_relationship_load`
-
- """
- opts = self._orm_compile_options()
- return opts is not None and opts._for_refresh_state
-
- @property
- def is_relationship_load(self) -> bool:
- """Return True if this load is loading objects on behalf of a
- relationship.
-
- This means, the loader in effect is either a LazyLoader,
- SelectInLoader, SubqueryLoader, or similar, and the entire
- SELECT statement being emitted is on behalf of a relationship
- load.
-
- Handlers will very likely not want to add any options to queries
- when such an operation is occurring, as loader options are already
- capable of being propagated to relationship loaders and should
- be already present.
-
- .. seealso::
-
- :attr:`_orm.ORMExecuteState.is_column_load`
-
- """
- opts = self._orm_compile_options()
- if opts is None:
- return False
- path = self.loader_strategy_path
- return path is not None and not path.is_root
-
- @property
- def load_options(
- self,
- ) -> Union[
- context.QueryContext.default_load_options,
- Type[context.QueryContext.default_load_options],
- ]:
- """Return the load_options that will be used for this execution."""
-
- if not self.is_select:
- raise sa_exc.InvalidRequestError(
- "This ORM execution is not against a SELECT statement "
- "so there are no load options."
- )
-
- lo: Union[
- context.QueryContext.default_load_options,
- Type[context.QueryContext.default_load_options],
- ] = self.execution_options.get(
- "_sa_orm_load_options", context.QueryContext.default_load_options
- )
- return lo
-
- @property
- def update_delete_options(
- self,
- ) -> Union[
- bulk_persistence.BulkUDCompileState.default_update_options,
- Type[bulk_persistence.BulkUDCompileState.default_update_options],
- ]:
- """Return the update_delete_options that will be used for this
- execution."""
-
- if not self._is_crud:
- raise sa_exc.InvalidRequestError(
- "This ORM execution is not against an UPDATE or DELETE "
- "statement so there are no update options."
- )
- uo: Union[
- bulk_persistence.BulkUDCompileState.default_update_options,
- Type[bulk_persistence.BulkUDCompileState.default_update_options],
- ] = self.execution_options.get(
- "_sa_orm_update_options",
- bulk_persistence.BulkUDCompileState.default_update_options,
- )
- return uo
-
- @property
- def _non_compile_orm_options(self) -> Sequence[ORMOption]:
- return [
- opt
- for opt in self.statement._with_options
- if is_orm_option(opt) and not opt._is_compile_state
- ]
-
- @property
- def user_defined_options(self) -> Sequence[UserDefinedOption]:
- """The sequence of :class:`.UserDefinedOptions` that have been
- associated with the statement being invoked.
-
- """
- return [
- opt
- for opt in self.statement._with_options
- if is_user_defined_option(opt)
- ]
-
-
-class SessionTransactionOrigin(Enum):
- """indicates the origin of a :class:`.SessionTransaction`.
-
- This enumeration is present on the
- :attr:`.SessionTransaction.origin` attribute of any
- :class:`.SessionTransaction` object.
-
- .. versionadded:: 2.0
-
- """
-
- AUTOBEGIN = 0
- """transaction were started by autobegin"""
-
- BEGIN = 1
- """transaction were started by calling :meth:`_orm.Session.begin`"""
-
- BEGIN_NESTED = 2
- """tranaction were started by :meth:`_orm.Session.begin_nested`"""
-
- SUBTRANSACTION = 3
- """transaction is an internal "subtransaction" """
-
-
-class SessionTransaction(_StateChange, TransactionalContext):
- """A :class:`.Session`-level transaction.
-
- :class:`.SessionTransaction` is produced from the
- :meth:`_orm.Session.begin`
- and :meth:`_orm.Session.begin_nested` methods. It's largely an internal
- object that in modern use provides a context manager for session
- transactions.
-
- Documentation on interacting with :class:`_orm.SessionTransaction` is
- at: :ref:`unitofwork_transaction`.
-
-
- .. versionchanged:: 1.4 The scoping and API methods to work with the
- :class:`_orm.SessionTransaction` object directly have been simplified.
-
- .. seealso::
-
- :ref:`unitofwork_transaction`
-
- :meth:`.Session.begin`
-
- :meth:`.Session.begin_nested`
-
- :meth:`.Session.rollback`
-
- :meth:`.Session.commit`
-
- :meth:`.Session.in_transaction`
-
- :meth:`.Session.in_nested_transaction`
-
- :meth:`.Session.get_transaction`
-
- :meth:`.Session.get_nested_transaction`
-
-
- """
-
- _rollback_exception: Optional[BaseException] = None
-
- _connections: Dict[
- Union[Engine, Connection], Tuple[Connection, Transaction, bool, bool]
- ]
- session: Session
- _parent: Optional[SessionTransaction]
-
- _state: SessionTransactionState
-
- _new: weakref.WeakKeyDictionary[InstanceState[Any], object]
- _deleted: weakref.WeakKeyDictionary[InstanceState[Any], object]
- _dirty: weakref.WeakKeyDictionary[InstanceState[Any], object]
- _key_switches: weakref.WeakKeyDictionary[
- InstanceState[Any], Tuple[Any, Any]
- ]
-
- origin: SessionTransactionOrigin
- """Origin of this :class:`_orm.SessionTransaction`.
-
- Refers to a :class:`.SessionTransactionOrigin` instance which is an
- enumeration indicating the source event that led to constructing
- this :class:`_orm.SessionTransaction`.
-
- .. versionadded:: 2.0
-
- """
-
- nested: bool = False
- """Indicates if this is a nested, or SAVEPOINT, transaction.
-
- When :attr:`.SessionTransaction.nested` is True, it is expected
- that :attr:`.SessionTransaction.parent` will be present as well,
- linking to the enclosing :class:`.SessionTransaction`.
-
- .. seealso::
-
- :attr:`.SessionTransaction.origin`
-
- """
-
- def __init__(
- self,
- session: Session,
- origin: SessionTransactionOrigin,
- parent: Optional[SessionTransaction] = None,
- ):
- TransactionalContext._trans_ctx_check(session)
-
- self.session = session
- self._connections = {}
- self._parent = parent
- self.nested = nested = origin is SessionTransactionOrigin.BEGIN_NESTED
- self.origin = origin
-
- if session._close_state is _SessionCloseState.CLOSED:
- raise sa_exc.InvalidRequestError(
- "This Session has been permanently closed and is unable "
- "to handle any more transaction requests."
- )
-
- if nested:
- if not parent:
- raise sa_exc.InvalidRequestError(
- "Can't start a SAVEPOINT transaction when no existing "
- "transaction is in progress"
- )
-
- self._previous_nested_transaction = session._nested_transaction
- elif origin is SessionTransactionOrigin.SUBTRANSACTION:
- assert parent is not None
- else:
- assert parent is None
-
- self._state = SessionTransactionState.ACTIVE
-
- self._take_snapshot()
-
- # make sure transaction is assigned before we call the
- # dispatch
- self.session._transaction = self
-
- self.session.dispatch.after_transaction_create(self.session, self)
-
- def _raise_for_prerequisite_state(
- self, operation_name: str, state: _StateChangeState
- ) -> NoReturn:
- if state is SessionTransactionState.DEACTIVE:
- if self._rollback_exception:
- raise sa_exc.PendingRollbackError(
- "This Session's transaction has been rolled back "
- "due to a previous exception during flush."
- " To begin a new transaction with this Session, "
- "first issue Session.rollback()."
- f" Original exception was: {self._rollback_exception}",
- code="7s2a",
- )
- else:
- raise sa_exc.InvalidRequestError(
- "This session is in 'inactive' state, due to the "
- "SQL transaction being rolled back; no further SQL "
- "can be emitted within this transaction."
- )
- elif state is SessionTransactionState.CLOSED:
- raise sa_exc.ResourceClosedError("This transaction is closed")
- elif state is SessionTransactionState.PROVISIONING_CONNECTION:
- raise sa_exc.InvalidRequestError(
- "This session is provisioning a new connection; concurrent "
- "operations are not permitted",
- code="isce",
- )
- else:
- raise sa_exc.InvalidRequestError(
- f"This session is in '{state.name.lower()}' state; no "
- "further SQL can be emitted within this transaction."
- )
-
- @property
- def parent(self) -> Optional[SessionTransaction]:
- """The parent :class:`.SessionTransaction` of this
- :class:`.SessionTransaction`.
-
- If this attribute is ``None``, indicates this
- :class:`.SessionTransaction` is at the top of the stack, and
- corresponds to a real "COMMIT"/"ROLLBACK"
- block. If non-``None``, then this is either a "subtransaction"
- (an internal marker object used by the flush process) or a
- "nested" / SAVEPOINT transaction. If the
- :attr:`.SessionTransaction.nested` attribute is ``True``, then
- this is a SAVEPOINT, and if ``False``, indicates this a subtransaction.
-
- """
- return self._parent
-
- @property
- def is_active(self) -> bool:
- return (
- self.session is not None
- and self._state is SessionTransactionState.ACTIVE
- )
-
- @property
- def _is_transaction_boundary(self) -> bool:
- return self.nested or not self._parent
-
- @_StateChange.declare_states(
- (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE
- )
- def connection(
- self,
- bindkey: Optional[Mapper[Any]],
- execution_options: Optional[_ExecuteOptions] = None,
- **kwargs: Any,
- ) -> Connection:
- bind = self.session.get_bind(bindkey, **kwargs)
- return self._connection_for_bind(bind, execution_options)
-
- @_StateChange.declare_states(
- (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE
- )
- def _begin(self, nested: bool = False) -> SessionTransaction:
- return SessionTransaction(
- self.session,
- (
- SessionTransactionOrigin.BEGIN_NESTED
- if nested
- else SessionTransactionOrigin.SUBTRANSACTION
- ),
- self,
- )
-
- def _iterate_self_and_parents(
- self, upto: Optional[SessionTransaction] = None
- ) -> Iterable[SessionTransaction]:
- current = self
- result: Tuple[SessionTransaction, ...] = ()
- while current:
- result += (current,)
- if current._parent is upto:
- break
- elif current._parent is None:
- raise sa_exc.InvalidRequestError(
- "Transaction %s is not on the active transaction list"
- % (upto)
- )
- else:
- current = current._parent
-
- return result
-
- def _take_snapshot(self) -> None:
- if not self._is_transaction_boundary:
- parent = self._parent
- assert parent is not None
- self._new = parent._new
- self._deleted = parent._deleted
- self._dirty = parent._dirty
- self._key_switches = parent._key_switches
- return
-
- is_begin = self.origin in (
- SessionTransactionOrigin.BEGIN,
- SessionTransactionOrigin.AUTOBEGIN,
- )
- if not is_begin and not self.session._flushing:
- self.session.flush()
-
- self._new = weakref.WeakKeyDictionary()
- self._deleted = weakref.WeakKeyDictionary()
- self._dirty = weakref.WeakKeyDictionary()
- self._key_switches = weakref.WeakKeyDictionary()
-
- def _restore_snapshot(self, dirty_only: bool = False) -> None:
- """Restore the restoration state taken before a transaction began.
-
- Corresponds to a rollback.
-
- """
- assert self._is_transaction_boundary
-
- to_expunge = set(self._new).union(self.session._new)
- self.session._expunge_states(to_expunge, to_transient=True)
-
- for s, (oldkey, newkey) in self._key_switches.items():
- # we probably can do this conditionally based on
- # if we expunged or not, but safe_discard does that anyway
- self.session.identity_map.safe_discard(s)
-
- # restore the old key
- s.key = oldkey
-
- # now restore the object, but only if we didn't expunge
- if s not in to_expunge:
- self.session.identity_map.replace(s)
-
- for s in set(self._deleted).union(self.session._deleted):
- self.session._update_impl(s, revert_deletion=True)
-
- assert not self.session._deleted
-
- for s in self.session.identity_map.all_states():
- if not dirty_only or s.modified or s in self._dirty:
- s._expire(s.dict, self.session.identity_map._modified)
-
- def _remove_snapshot(self) -> None:
- """Remove the restoration state taken before a transaction began.
-
- Corresponds to a commit.
-
- """
- assert self._is_transaction_boundary
-
- if not self.nested and self.session.expire_on_commit:
- for s in self.session.identity_map.all_states():
- s._expire(s.dict, self.session.identity_map._modified)
-
- statelib.InstanceState._detach_states(
- list(self._deleted), self.session
- )
- self._deleted.clear()
- elif self.nested:
- parent = self._parent
- assert parent is not None
- parent._new.update(self._new)
- parent._dirty.update(self._dirty)
- parent._deleted.update(self._deleted)
- parent._key_switches.update(self._key_switches)
-
- @_StateChange.declare_states(
- (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE
- )
- def _connection_for_bind(
- self,
- bind: _SessionBind,
- execution_options: Optional[CoreExecuteOptionsParameter],
- ) -> Connection:
- if bind in self._connections:
- if execution_options:
- util.warn(
- "Connection is already established for the "
- "given bind; execution_options ignored"
- )
- return self._connections[bind][0]
-
- self._state = SessionTransactionState.PROVISIONING_CONNECTION
-
- local_connect = False
- should_commit = True
-
- try:
- if self._parent:
- conn = self._parent._connection_for_bind(
- bind, execution_options
- )
- if not self.nested:
- return conn
- else:
- if isinstance(bind, engine.Connection):
- conn = bind
- if conn.engine in self._connections:
- raise sa_exc.InvalidRequestError(
- "Session already has a Connection associated "
- "for the given Connection's Engine"
- )
- else:
- conn = bind.connect()
- local_connect = True
-
- try:
- if execution_options:
- conn = conn.execution_options(**execution_options)
-
- transaction: Transaction
- if self.session.twophase and self._parent is None:
- # TODO: shouldn't we only be here if not
- # conn.in_transaction() ?
- # if twophase is set and conn.in_transaction(), validate
- # that it is in fact twophase.
- transaction = conn.begin_twophase()
- elif self.nested:
- transaction = conn.begin_nested()
- elif conn.in_transaction():
- join_transaction_mode = self.session.join_transaction_mode
-
- if join_transaction_mode == "conditional_savepoint":
- if conn.in_nested_transaction():
- join_transaction_mode = "create_savepoint"
- else:
- join_transaction_mode = "rollback_only"
-
- if join_transaction_mode in (
- "control_fully",
- "rollback_only",
- ):
- if conn.in_nested_transaction():
- transaction = (
- conn._get_required_nested_transaction()
- )
- else:
- transaction = conn._get_required_transaction()
- if join_transaction_mode == "rollback_only":
- should_commit = False
- elif join_transaction_mode == "create_savepoint":
- transaction = conn.begin_nested()
- else:
- assert False, join_transaction_mode
- else:
- transaction = conn.begin()
- except:
- # connection will not not be associated with this Session;
- # close it immediately so that it isn't closed under GC
- if local_connect:
- conn.close()
- raise
- else:
- bind_is_connection = isinstance(bind, engine.Connection)
-
- self._connections[conn] = self._connections[conn.engine] = (
- conn,
- transaction,
- should_commit,
- not bind_is_connection,
- )
- self.session.dispatch.after_begin(self.session, self, conn)
- return conn
- finally:
- self._state = SessionTransactionState.ACTIVE
-
- def prepare(self) -> None:
- if self._parent is not None or not self.session.twophase:
- raise sa_exc.InvalidRequestError(
- "'twophase' mode not enabled, or not root transaction; "
- "can't prepare."
- )
- self._prepare_impl()
-
- @_StateChange.declare_states(
- (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED
- )
- def _prepare_impl(self) -> None:
- if self._parent is None or self.nested:
- self.session.dispatch.before_commit(self.session)
-
- stx = self.session._transaction
- assert stx is not None
- if stx is not self:
- for subtransaction in stx._iterate_self_and_parents(upto=self):
- subtransaction.commit()
-
- if not self.session._flushing:
- for _flush_guard in range(100):
- if self.session._is_clean():
- break
- self.session.flush()
- else:
- raise exc.FlushError(
- "Over 100 subsequent flushes have occurred within "
- "session.commit() - is an after_flush() hook "
- "creating new objects?"
- )
-
- if self._parent is None and self.session.twophase:
- try:
- for t in set(self._connections.values()):
- cast("TwoPhaseTransaction", t[1]).prepare()
- except:
- with util.safe_reraise():
- self.rollback()
-
- self._state = SessionTransactionState.PREPARED
-
- @_StateChange.declare_states(
- (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED),
- SessionTransactionState.CLOSED,
- )
- def commit(self, _to_root: bool = False) -> None:
- if self._state is not SessionTransactionState.PREPARED:
- with self._expect_state(SessionTransactionState.PREPARED):
- self._prepare_impl()
-
- if self._parent is None or self.nested:
- for conn, trans, should_commit, autoclose in set(
- self._connections.values()
- ):
- if should_commit:
- trans.commit()
-
- self._state = SessionTransactionState.COMMITTED
- self.session.dispatch.after_commit(self.session)
-
- self._remove_snapshot()
-
- with self._expect_state(SessionTransactionState.CLOSED):
- self.close()
-
- if _to_root and self._parent:
- self._parent.commit(_to_root=True)
-
- @_StateChange.declare_states(
- (
- SessionTransactionState.ACTIVE,
- SessionTransactionState.DEACTIVE,
- SessionTransactionState.PREPARED,
- ),
- SessionTransactionState.CLOSED,
- )
- def rollback(
- self, _capture_exception: bool = False, _to_root: bool = False
- ) -> None:
- stx = self.session._transaction
- assert stx is not None
- if stx is not self:
- for subtransaction in stx._iterate_self_and_parents(upto=self):
- subtransaction.close()
-
- boundary = self
- rollback_err = None
- if self._state in (
- SessionTransactionState.ACTIVE,
- SessionTransactionState.PREPARED,
- ):
- for transaction in self._iterate_self_and_parents():
- if transaction._parent is None or transaction.nested:
- try:
- for t in set(transaction._connections.values()):
- t[1].rollback()
-
- transaction._state = SessionTransactionState.DEACTIVE
- self.session.dispatch.after_rollback(self.session)
- except:
- rollback_err = sys.exc_info()
- finally:
- transaction._state = SessionTransactionState.DEACTIVE
- transaction._restore_snapshot(
- dirty_only=transaction.nested
- )
- boundary = transaction
- break
- else:
- transaction._state = SessionTransactionState.DEACTIVE
-
- sess = self.session
-
- if not rollback_err and not sess._is_clean():
- # if items were added, deleted, or mutated
- # here, we need to re-restore the snapshot
- util.warn(
- "Session's state has been changed on "
- "a non-active transaction - this state "
- "will be discarded."
- )
- boundary._restore_snapshot(dirty_only=boundary.nested)
-
- with self._expect_state(SessionTransactionState.CLOSED):
- self.close()
-
- if self._parent and _capture_exception:
- self._parent._rollback_exception = sys.exc_info()[1]
-
- if rollback_err and rollback_err[1]:
- raise rollback_err[1].with_traceback(rollback_err[2])
-
- sess.dispatch.after_soft_rollback(sess, self)
-
- if _to_root and self._parent:
- self._parent.rollback(_to_root=True)
-
- @_StateChange.declare_states(
- _StateChangeStates.ANY, SessionTransactionState.CLOSED
- )
- def close(self, invalidate: bool = False) -> None:
- if self.nested:
- self.session._nested_transaction = (
- self._previous_nested_transaction
- )
-
- self.session._transaction = self._parent
-
- for connection, transaction, should_commit, autoclose in set(
- self._connections.values()
- ):
- if invalidate and self._parent is None:
- connection.invalidate()
- if should_commit and transaction.is_active:
- transaction.close()
- if autoclose and self._parent is None:
- connection.close()
-
- self._state = SessionTransactionState.CLOSED
- sess = self.session
-
- # TODO: these two None sets were historically after the
- # event hook below, and in 2.0 I changed it this way for some reason,
- # and I remember there being a reason, but not what it was.
- # Why do we need to get rid of them at all? test_memusage::CycleTest
- # passes with these commented out.
- # self.session = None # type: ignore
- # self._connections = None # type: ignore
-
- sess.dispatch.after_transaction_end(sess, self)
-
- def _get_subject(self) -> Session:
- return self.session
-
- def _transaction_is_active(self) -> bool:
- return self._state is SessionTransactionState.ACTIVE
-
- def _transaction_is_closed(self) -> bool:
- return self._state is SessionTransactionState.CLOSED
-
- def _rollback_can_be_called(self) -> bool:
- return self._state not in (COMMITTED, CLOSED)
-
-
-class _SessionCloseState(Enum):
- ACTIVE = 1
- CLOSED = 2
- CLOSE_IS_RESET = 3
-
-
-class Session(_SessionClassMethods, EventTarget):
- """Manages persistence operations for ORM-mapped objects.
-
- The :class:`_orm.Session` is **not safe for use in concurrent threads.**.
- See :ref:`session_faq_threadsafe` for background.
-
- The Session's usage paradigm is described at :doc:`/orm/session`.
-
-
- """
-
- _is_asyncio = False
-
- dispatch: dispatcher[Session]
-
- identity_map: IdentityMap
- """A mapping of object identities to objects themselves.
-
- Iterating through ``Session.identity_map.values()`` provides
- access to the full set of persistent objects (i.e., those
- that have row identity) currently in the session.
-
- .. seealso::
-
- :func:`.identity_key` - helper function to produce the keys used
- in this dictionary.
-
- """
-
- _new: Dict[InstanceState[Any], Any]
- _deleted: Dict[InstanceState[Any], Any]
- bind: Optional[Union[Engine, Connection]]
- __binds: Dict[_SessionBindKey, _SessionBind]
- _flushing: bool
- _warn_on_events: bool
- _transaction: Optional[SessionTransaction]
- _nested_transaction: Optional[SessionTransaction]
- hash_key: int
- autoflush: bool
- expire_on_commit: bool
- enable_baked_queries: bool
- twophase: bool
- join_transaction_mode: JoinTransactionMode
- _query_cls: Type[Query[Any]]
- _close_state: _SessionCloseState
-
- def __init__(
- self,
- bind: Optional[_SessionBind] = None,
- *,
- autoflush: bool = True,
- future: Literal[True] = True,
- expire_on_commit: bool = True,
- autobegin: bool = True,
- twophase: bool = False,
- binds: Optional[Dict[_SessionBindKey, _SessionBind]] = None,
- enable_baked_queries: bool = True,
- info: Optional[_InfoType] = None,
- query_cls: Optional[Type[Query[Any]]] = None,
- autocommit: Literal[False] = False,
- join_transaction_mode: JoinTransactionMode = "conditional_savepoint",
- close_resets_only: Union[bool, _NoArg] = _NoArg.NO_ARG,
- ):
- r"""Construct a new :class:`_orm.Session`.
-
- See also the :class:`.sessionmaker` function which is used to
- generate a :class:`.Session`-producing callable with a given
- set of arguments.
-
- :param autoflush: When ``True``, all query operations will issue a
- :meth:`~.Session.flush` call to this ``Session`` before proceeding.
- This is a convenience feature so that :meth:`~.Session.flush` need
- not be called repeatedly in order for database queries to retrieve
- results.
-
- .. seealso::
-
- :ref:`session_flushing` - additional background on autoflush
-
- :param autobegin: Automatically start transactions (i.e. equivalent to
- invoking :meth:`_orm.Session.begin`) when database access is
- requested by an operation. Defaults to ``True``. Set to
- ``False`` to prevent a :class:`_orm.Session` from implicitly
- beginning transactions after construction, as well as after any of
- the :meth:`_orm.Session.rollback`, :meth:`_orm.Session.commit`,
- or :meth:`_orm.Session.close` methods are called.
-
- .. versionadded:: 2.0
-
- .. seealso::
-
- :ref:`session_autobegin_disable`
-
- :param bind: An optional :class:`_engine.Engine` or
- :class:`_engine.Connection` to
- which this ``Session`` should be bound. When specified, all SQL
- operations performed by this session will execute via this
- connectable.
-
- :param binds: A dictionary which may specify any number of
- :class:`_engine.Engine` or :class:`_engine.Connection`
- objects as the source of
- connectivity for SQL operations on a per-entity basis. The keys
- of the dictionary consist of any series of mapped classes,
- arbitrary Python classes that are bases for mapped classes,
- :class:`_schema.Table` objects and :class:`_orm.Mapper` objects.
- The
- values of the dictionary are then instances of
- :class:`_engine.Engine`
- or less commonly :class:`_engine.Connection` objects.
- Operations which
- proceed relative to a particular mapped class will consult this
- dictionary for the closest matching entity in order to determine
- which :class:`_engine.Engine` should be used for a particular SQL
- operation. The complete heuristics for resolution are
- described at :meth:`.Session.get_bind`. Usage looks like::
-
- Session = sessionmaker(binds={
- SomeMappedClass: create_engine('postgresql+psycopg2://engine1'),
- SomeDeclarativeBase: create_engine('postgresql+psycopg2://engine2'),
- some_mapper: create_engine('postgresql+psycopg2://engine3'),
- some_table: create_engine('postgresql+psycopg2://engine4'),
- })
-
- .. seealso::
-
- :ref:`session_partitioning`
-
- :meth:`.Session.bind_mapper`
-
- :meth:`.Session.bind_table`
-
- :meth:`.Session.get_bind`
-
-
- :param \class_: Specify an alternate class other than
- ``sqlalchemy.orm.session.Session`` which should be used by the
- returned class. This is the only argument that is local to the
- :class:`.sessionmaker` function, and is not sent directly to the
- constructor for ``Session``.
-
- :param enable_baked_queries: legacy; defaults to ``True``.
- A parameter consumed
- by the :mod:`sqlalchemy.ext.baked` extension to determine if
- "baked queries" should be cached, as is the normal operation
- of this extension. When set to ``False``, caching as used by
- this particular extension is disabled.
-
- .. versionchanged:: 1.4 The ``sqlalchemy.ext.baked`` extension is
- legacy and is not used by any of SQLAlchemy's internals. This
- flag therefore only affects applications that are making explicit
- use of this extension within their own code.
-
- :param expire_on_commit: Defaults to ``True``. When ``True``, all
- instances will be fully expired after each :meth:`~.commit`,
- so that all attribute/object access subsequent to a completed
- transaction will load from the most recent database state.
-
- .. seealso::
-
- :ref:`session_committing`
-
- :param future: Deprecated; this flag is always True.
-
- .. seealso::
-
- :ref:`migration_20_toplevel`
-
- :param info: optional dictionary of arbitrary data to be associated
- with this :class:`.Session`. Is available via the
- :attr:`.Session.info` attribute. Note the dictionary is copied at
- construction time so that modifications to the per-
- :class:`.Session` dictionary will be local to that
- :class:`.Session`.
-
- :param query_cls: Class which should be used to create new Query
- objects, as returned by the :meth:`~.Session.query` method.
- Defaults to :class:`_query.Query`.
-
- :param twophase: When ``True``, all transactions will be started as
- a "two phase" transaction, i.e. using the "two phase" semantics
- of the database in use along with an XID. During a
- :meth:`~.commit`, after :meth:`~.flush` has been issued for all
- attached databases, the :meth:`~.TwoPhaseTransaction.prepare`
- method on each database's :class:`.TwoPhaseTransaction` will be
- called. This allows each database to roll back the entire
- transaction, before each transaction is committed.
-
- :param autocommit: the "autocommit" keyword is present for backwards
- compatibility but must remain at its default value of ``False``.
-
- :param join_transaction_mode: Describes the transactional behavior to
- take when a given bind is a :class:`_engine.Connection` that
- has already begun a transaction outside the scope of this
- :class:`_orm.Session`; in other words the
- :meth:`_engine.Connection.in_transaction()` method returns True.
-
- The following behaviors only take effect when the :class:`_orm.Session`
- **actually makes use of the connection given**; that is, a method
- such as :meth:`_orm.Session.execute`, :meth:`_orm.Session.connection`,
- etc. are actually invoked:
-
- * ``"conditional_savepoint"`` - this is the default. if the given
- :class:`_engine.Connection` is begun within a transaction but
- does not have a SAVEPOINT, then ``"rollback_only"`` is used.
- If the :class:`_engine.Connection` is additionally within
- a SAVEPOINT, in other words
- :meth:`_engine.Connection.in_nested_transaction()` method returns
- True, then ``"create_savepoint"`` is used.
-
- ``"conditional_savepoint"`` behavior attempts to make use of
- savepoints in order to keep the state of the existing transaction
- unchanged, but only if there is already a savepoint in progress;
- otherwise, it is not assumed that the backend in use has adequate
- support for SAVEPOINT, as availability of this feature varies.
- ``"conditional_savepoint"`` also seeks to establish approximate
- backwards compatibility with previous :class:`_orm.Session`
- behavior, for applications that are not setting a specific mode. It
- is recommended that one of the explicit settings be used.
-
- * ``"create_savepoint"`` - the :class:`_orm.Session` will use
- :meth:`_engine.Connection.begin_nested()` in all cases to create
- its own transaction. This transaction by its nature rides
- "on top" of any existing transaction that's opened on the given
- :class:`_engine.Connection`; if the underlying database and
- the driver in use has full, non-broken support for SAVEPOINT, the
- external transaction will remain unaffected throughout the
- lifespan of the :class:`_orm.Session`.
-
- The ``"create_savepoint"`` mode is the most useful for integrating
- a :class:`_orm.Session` into a test suite where an externally
- initiated transaction should remain unaffected; however, it relies
- on proper SAVEPOINT support from the underlying driver and
- database.
-
- .. tip:: When using SQLite, the SQLite driver included through
- Python 3.11 does not handle SAVEPOINTs correctly in all cases
- without workarounds. See the sections
- :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable`
- for details on current workarounds.
-
- * ``"control_fully"`` - the :class:`_orm.Session` will take
- control of the given transaction as its own;
- :meth:`_orm.Session.commit` will call ``.commit()`` on the
- transaction, :meth:`_orm.Session.rollback` will call
- ``.rollback()`` on the transaction, :meth:`_orm.Session.close` will
- call ``.rollback`` on the transaction.
-
- .. tip:: This mode of use is equivalent to how SQLAlchemy 1.4 would
- handle a :class:`_engine.Connection` given with an existing
- SAVEPOINT (i.e. :meth:`_engine.Connection.begin_nested`); the
- :class:`_orm.Session` would take full control of the existing
- SAVEPOINT.
-
- * ``"rollback_only"`` - the :class:`_orm.Session` will take control
- of the given transaction for ``.rollback()`` calls only;
- ``.commit()`` calls will not be propagated to the given
- transaction. ``.close()`` calls will have no effect on the
- given transaction.
-
- .. tip:: This mode of use is equivalent to how SQLAlchemy 1.4 would
- handle a :class:`_engine.Connection` given with an existing
- regular database transaction (i.e.
- :meth:`_engine.Connection.begin`); the :class:`_orm.Session`
- would propagate :meth:`_orm.Session.rollback` calls to the
- underlying transaction, but not :meth:`_orm.Session.commit` or
- :meth:`_orm.Session.close` calls.
-
- .. versionadded:: 2.0.0rc1
-
- :param close_resets_only: Defaults to ``True``. Determines if
- the session should reset itself after calling ``.close()``
- or should pass in a no longer usable state, disabling re-use.
-
- .. versionadded:: 2.0.22 added flag ``close_resets_only``.
- A future SQLAlchemy version may change the default value of
- this flag to ``False``.
-
- .. seealso::
-
- :ref:`session_closing` - Detail on the semantics of
- :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
-
- """ # noqa
-
- # considering allowing the "autocommit" keyword to still be accepted
- # as long as it's False, so that external test suites, oslo.db etc
- # continue to function as the argument appears to be passed in lots
- # of cases including in our own test suite
- if autocommit:
- raise sa_exc.ArgumentError(
- "autocommit=True is no longer supported"
- )
- self.identity_map = identity.WeakInstanceDict()
-
- if not future:
- raise sa_exc.ArgumentError(
- "The 'future' parameter passed to "
- "Session() may only be set to True."
- )
-
- self._new = {} # InstanceState->object, strong refs object
- self._deleted = {} # same
- self.bind = bind
- self.__binds = {}
- self._flushing = False
- self._warn_on_events = False
- self._transaction = None
- self._nested_transaction = None
- self.hash_key = _new_sessionid()
- self.autobegin = autobegin
- self.autoflush = autoflush
- self.expire_on_commit = expire_on_commit
- self.enable_baked_queries = enable_baked_queries
-
- # the idea is that at some point NO_ARG will warn that in the future
- # the default will switch to close_resets_only=False.
- if close_resets_only or close_resets_only is _NoArg.NO_ARG:
- self._close_state = _SessionCloseState.CLOSE_IS_RESET
- else:
- self._close_state = _SessionCloseState.ACTIVE
- if (
- join_transaction_mode
- and join_transaction_mode
- not in JoinTransactionMode.__args__ # type: ignore
- ):
- raise sa_exc.ArgumentError(
- f"invalid selection for join_transaction_mode: "
- f'"{join_transaction_mode}"'
- )
- self.join_transaction_mode = join_transaction_mode
-
- self.twophase = twophase
- self._query_cls = query_cls if query_cls else query.Query
- if info:
- self.info.update(info)
-
- if binds is not None:
- for key, bind in binds.items():
- self._add_bind(key, bind)
-
- _sessions[self.hash_key] = self
-
- # used by sqlalchemy.engine.util.TransactionalContext
- _trans_context_manager: Optional[TransactionalContext] = None
-
- connection_callable: Optional[_ConnectionCallableProto] = None
-
- def __enter__(self: _S) -> _S:
- return self
-
- def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
- self.close()
-
- @contextlib.contextmanager
- def _maker_context_manager(self: _S) -> Iterator[_S]:
- with self:
- with self.begin():
- yield self
-
- def in_transaction(self) -> bool:
- """Return True if this :class:`_orm.Session` has begun a transaction.
-
- .. versionadded:: 1.4
-
- .. seealso::
-
- :attr:`_orm.Session.is_active`
-
-
- """
- return self._transaction is not None
-
- def in_nested_transaction(self) -> bool:
- """Return True if this :class:`_orm.Session` has begun a nested
- transaction, e.g. SAVEPOINT.
-
- .. versionadded:: 1.4
-
- """
- return self._nested_transaction is not None
-
- def get_transaction(self) -> Optional[SessionTransaction]:
- """Return the current root transaction in progress, if any.
-
- .. versionadded:: 1.4
-
- """
- trans = self._transaction
- while trans is not None and trans._parent is not None:
- trans = trans._parent
- return trans
-
- def get_nested_transaction(self) -> Optional[SessionTransaction]:
- """Return the current nested transaction in progress, if any.
-
- .. versionadded:: 1.4
-
- """
-
- return self._nested_transaction
-
- @util.memoized_property
- def info(self) -> _InfoType:
- """A user-modifiable dictionary.
-
- The initial value of this dictionary can be populated using the
- ``info`` argument to the :class:`.Session` constructor or
- :class:`.sessionmaker` constructor or factory methods. The dictionary
- here is always local to this :class:`.Session` and can be modified
- independently of all other :class:`.Session` objects.
-
- """
- return {}
-
- def _autobegin_t(self, begin: bool = False) -> SessionTransaction:
- if self._transaction is None:
- if not begin and not self.autobegin:
- raise sa_exc.InvalidRequestError(
- "Autobegin is disabled on this Session; please call "
- "session.begin() to start a new transaction"
- )
- trans = SessionTransaction(
- self,
- (
- SessionTransactionOrigin.BEGIN
- if begin
- else SessionTransactionOrigin.AUTOBEGIN
- ),
- )
- assert self._transaction is trans
- return trans
-
- return self._transaction
-
- def begin(self, nested: bool = False) -> SessionTransaction:
- """Begin a transaction, or nested transaction,
- on this :class:`.Session`, if one is not already begun.
-
- The :class:`_orm.Session` object features **autobegin** behavior,
- so that normally it is not necessary to call the
- :meth:`_orm.Session.begin`
- method explicitly. However, it may be used in order to control
- the scope of when the transactional state is begun.
-
- When used to begin the outermost transaction, an error is raised
- if this :class:`.Session` is already inside of a transaction.
-
- :param nested: if True, begins a SAVEPOINT transaction and is
- equivalent to calling :meth:`~.Session.begin_nested`. For
- documentation on SAVEPOINT transactions, please see
- :ref:`session_begin_nested`.
-
- :return: the :class:`.SessionTransaction` object. Note that
- :class:`.SessionTransaction`
- acts as a Python context manager, allowing :meth:`.Session.begin`
- to be used in a "with" block. See :ref:`session_explicit_begin` for
- an example.
-
- .. seealso::
-
- :ref:`session_autobegin`
-
- :ref:`unitofwork_transaction`
-
- :meth:`.Session.begin_nested`
-
-
- """
-
- trans = self._transaction
- if trans is None:
- trans = self._autobegin_t(begin=True)
-
- if not nested:
- return trans
-
- assert trans is not None
-
- if nested:
- trans = trans._begin(nested=nested)
- assert self._transaction is trans
- self._nested_transaction = trans
- else:
- raise sa_exc.InvalidRequestError(
- "A transaction is already begun on this Session."
- )
-
- return trans # needed for __enter__/__exit__ hook
-
- def begin_nested(self) -> SessionTransaction:
- """Begin a "nested" transaction on this Session, e.g. SAVEPOINT.
-
- The target database(s) and associated drivers must support SQL
- SAVEPOINT for this method to function correctly.
-
- For documentation on SAVEPOINT
- transactions, please see :ref:`session_begin_nested`.
-
- :return: the :class:`.SessionTransaction` object. Note that
- :class:`.SessionTransaction` acts as a context manager, allowing
- :meth:`.Session.begin_nested` to be used in a "with" block.
- See :ref:`session_begin_nested` for a usage example.
-
- .. seealso::
-
- :ref:`session_begin_nested`
-
- :ref:`pysqlite_serializable` - special workarounds required
- with the SQLite driver in order for SAVEPOINT to work
- correctly. For asyncio use cases, see the section
- :ref:`aiosqlite_serializable`.
-
- """
- return self.begin(nested=True)
-
- def rollback(self) -> None:
- """Rollback the current transaction in progress.
-
- If no transaction is in progress, this method is a pass-through.
-
- The method always rolls back
- the topmost database transaction, discarding any nested
- transactions that may be in progress.
-
- .. seealso::
-
- :ref:`session_rollback`
-
- :ref:`unitofwork_transaction`
-
- """
- if self._transaction is None:
- pass
- else:
- self._transaction.rollback(_to_root=True)
-
- def commit(self) -> None:
- """Flush pending changes and commit the current transaction.
-
- When the COMMIT operation is complete, all objects are fully
- :term:`expired`, erasing their internal contents, which will be
- automatically re-loaded when the objects are next accessed. In the
- interim, these objects are in an expired state and will not function if
- they are :term:`detached` from the :class:`.Session`. Additionally,
- this re-load operation is not supported when using asyncio-oriented
- APIs. The :paramref:`.Session.expire_on_commit` parameter may be used
- to disable this behavior.
-
- When there is no transaction in place for the :class:`.Session`,
- indicating that no operations were invoked on this :class:`.Session`
- since the previous call to :meth:`.Session.commit`, the method will
- begin and commit an internal-only "logical" transaction, that does not
- normally affect the database unless pending flush changes were
- detected, but will still invoke event handlers and object expiration
- rules.
-
- The outermost database transaction is committed unconditionally,
- automatically releasing any SAVEPOINTs in effect.
-
- .. seealso::
-
- :ref:`session_committing`
-
- :ref:`unitofwork_transaction`
-
- :ref:`asyncio_orm_avoid_lazyloads`
-
- """
- trans = self._transaction
- if trans is None:
- trans = self._autobegin_t()
-
- trans.commit(_to_root=True)
-
- def prepare(self) -> None:
- """Prepare the current transaction in progress for two phase commit.
-
- If no transaction is in progress, this method raises an
- :exc:`~sqlalchemy.exc.InvalidRequestError`.
-
- Only root transactions of two phase sessions can be prepared. If the
- current transaction is not such, an
- :exc:`~sqlalchemy.exc.InvalidRequestError` is raised.
-
- """
- trans = self._transaction
- if trans is None:
- trans = self._autobegin_t()
-
- trans.prepare()
-
- def connection(
- self,
- bind_arguments: Optional[_BindArguments] = None,
- execution_options: Optional[CoreExecuteOptionsParameter] = None,
- ) -> Connection:
- r"""Return a :class:`_engine.Connection` object corresponding to this
- :class:`.Session` object's transactional state.
-
- Either the :class:`_engine.Connection` corresponding to the current
- transaction is returned, or if no transaction is in progress, a new
- one is begun and the :class:`_engine.Connection`
- returned (note that no
- transactional state is established with the DBAPI until the first
- SQL statement is emitted).
-
- Ambiguity in multi-bind or unbound :class:`.Session` objects can be
- resolved through any of the optional keyword arguments. This
- ultimately makes usage of the :meth:`.get_bind` method for resolution.
-
- :param bind_arguments: dictionary of bind arguments. May include
- "mapper", "bind", "clause", other custom arguments that are passed
- to :meth:`.Session.get_bind`.
-
- :param execution_options: a dictionary of execution options that will
- be passed to :meth:`_engine.Connection.execution_options`, **when the
- connection is first procured only**. If the connection is already
- present within the :class:`.Session`, a warning is emitted and
- the arguments are ignored.
-
- .. seealso::
-
- :ref:`session_transaction_isolation`
-
- """
-
- if bind_arguments:
- bind = bind_arguments.pop("bind", None)
-
- if bind is None:
- bind = self.get_bind(**bind_arguments)
- else:
- bind = self.get_bind()
-
- return self._connection_for_bind(
- bind,
- execution_options=execution_options,
- )
-
- def _connection_for_bind(
- self,
- engine: _SessionBind,
- execution_options: Optional[CoreExecuteOptionsParameter] = None,
- **kw: Any,
- ) -> Connection:
- TransactionalContext._trans_ctx_check(self)
-
- trans = self._transaction
- if trans is None:
- trans = self._autobegin_t()
- return trans._connection_for_bind(engine, execution_options)
-
- @overload
- def _execute_internal(
- self,
- statement: Executable,
- params: Optional[_CoreSingleExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- _parent_execute_state: Optional[Any] = None,
- _add_event: Optional[Any] = None,
- _scalar_result: Literal[True] = ...,
- ) -> Any: ...
-
- @overload
- def _execute_internal(
- self,
- statement: Executable,
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- _parent_execute_state: Optional[Any] = None,
- _add_event: Optional[Any] = None,
- _scalar_result: bool = ...,
- ) -> Result[Any]: ...
-
- def _execute_internal(
- self,
- statement: Executable,
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- _parent_execute_state: Optional[Any] = None,
- _add_event: Optional[Any] = None,
- _scalar_result: bool = False,
- ) -> Any:
- statement = coercions.expect(roles.StatementRole, statement)
-
- if not bind_arguments:
- bind_arguments = {}
- else:
- bind_arguments = dict(bind_arguments)
-
- if (
- statement._propagate_attrs.get("compile_state_plugin", None)
- == "orm"
- ):
- compile_state_cls = CompileState._get_plugin_class_for_plugin(
- statement, "orm"
- )
- if TYPE_CHECKING:
- assert isinstance(
- compile_state_cls, context.AbstractORMCompileState
- )
- else:
- compile_state_cls = None
- bind_arguments.setdefault("clause", statement)
-
- execution_options = util.coerce_to_immutabledict(execution_options)
-
- if _parent_execute_state:
- events_todo = _parent_execute_state._remaining_events()
- else:
- events_todo = self.dispatch.do_orm_execute
- if _add_event:
- events_todo = list(events_todo) + [_add_event]
-
- if events_todo:
- if compile_state_cls is not None:
- # for event handlers, do the orm_pre_session_exec
- # pass ahead of the event handlers, so that things like
- # .load_options, .update_delete_options etc. are populated.
- # is_pre_event=True allows the hook to hold off on things
- # it doesn't want to do twice, including autoflush as well
- # as "pre fetch" for DML, etc.
- (
- statement,
- execution_options,
- ) = compile_state_cls.orm_pre_session_exec(
- self,
- statement,
- params,
- execution_options,
- bind_arguments,
- True,
- )
-
- orm_exec_state = ORMExecuteState(
- self,
- statement,
- params,
- execution_options,
- bind_arguments,
- compile_state_cls,
- events_todo,
- )
- for idx, fn in enumerate(events_todo):
- orm_exec_state._starting_event_idx = idx
- fn_result: Optional[Result[Any]] = fn(orm_exec_state)
- if fn_result:
- if _scalar_result:
- return fn_result.scalar()
- else:
- return fn_result
-
- statement = orm_exec_state.statement
- execution_options = orm_exec_state.local_execution_options
-
- if compile_state_cls is not None:
- # now run orm_pre_session_exec() "for real". if there were
- # event hooks, this will re-run the steps that interpret
- # new execution_options into load_options / update_delete_options,
- # which we assume the event hook might have updated.
- # autoflush will also be invoked in this step if enabled.
- (
- statement,
- execution_options,
- ) = compile_state_cls.orm_pre_session_exec(
- self,
- statement,
- params,
- execution_options,
- bind_arguments,
- False,
- )
-
- bind = self.get_bind(**bind_arguments)
-
- conn = self._connection_for_bind(bind)
-
- if _scalar_result and not compile_state_cls:
- if TYPE_CHECKING:
- params = cast(_CoreSingleExecuteParams, params)
- return conn.scalar(
- statement, params or {}, execution_options=execution_options
- )
-
- if compile_state_cls:
- result: Result[Any] = compile_state_cls.orm_execute_statement(
- self,
- statement,
- params or {},
- execution_options,
- bind_arguments,
- conn,
- )
- else:
- result = conn.execute(
- statement, params or {}, execution_options=execution_options
- )
-
- if _scalar_result:
- return result.scalar()
- else:
- return result
-
- @overload
- def execute(
- self,
- statement: TypedReturnsRows[_T],
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- _parent_execute_state: Optional[Any] = None,
- _add_event: Optional[Any] = None,
- ) -> Result[_T]: ...
-
- @overload
- def execute(
- self,
- statement: UpdateBase,
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- _parent_execute_state: Optional[Any] = None,
- _add_event: Optional[Any] = None,
- ) -> CursorResult[Any]: ...
-
- @overload
- def execute(
- self,
- statement: Executable,
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- _parent_execute_state: Optional[Any] = None,
- _add_event: Optional[Any] = None,
- ) -> Result[Any]: ...
-
- def execute(
- self,
- statement: Executable,
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- _parent_execute_state: Optional[Any] = None,
- _add_event: Optional[Any] = None,
- ) -> Result[Any]:
- r"""Execute a SQL expression construct.
-
- Returns a :class:`_engine.Result` object representing
- results of the statement execution.
-
- E.g.::
-
- from sqlalchemy import select
- result = session.execute(
- select(User).where(User.id == 5)
- )
-
- The API contract of :meth:`_orm.Session.execute` is similar to that
- of :meth:`_engine.Connection.execute`, the :term:`2.0 style` version
- of :class:`_engine.Connection`.
-
- .. versionchanged:: 1.4 the :meth:`_orm.Session.execute` method is
- now the primary point of ORM statement execution when using
- :term:`2.0 style` ORM usage.
-
- :param statement:
- An executable statement (i.e. an :class:`.Executable` expression
- such as :func:`_expression.select`).
-
- :param params:
- Optional dictionary, or list of dictionaries, containing
- bound parameter values. If a single dictionary, single-row
- execution occurs; if a list of dictionaries, an
- "executemany" will be invoked. The keys in each dictionary
- must correspond to parameter names present in the statement.
-
- :param execution_options: optional dictionary of execution options,
- which will be associated with the statement execution. This
- dictionary can provide a subset of the options that are accepted
- by :meth:`_engine.Connection.execution_options`, and may also
- provide additional options understood only in an ORM context.
-
- .. seealso::
-
- :ref:`orm_queryguide_execution_options` - ORM-specific execution
- options
-
- :param bind_arguments: dictionary of additional arguments to determine
- the bind. May include "mapper", "bind", or other custom arguments.
- Contents of this dictionary are passed to the
- :meth:`.Session.get_bind` method.
-
- :return: a :class:`_engine.Result` object.
-
-
- """
- return self._execute_internal(
- statement,
- params,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- _parent_execute_state=_parent_execute_state,
- _add_event=_add_event,
- )
-
- @overload
- def scalar(
- self,
- statement: TypedReturnsRows[Tuple[_T]],
- params: Optional[_CoreSingleExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- **kw: Any,
- ) -> Optional[_T]: ...
-
- @overload
- def scalar(
- self,
- statement: Executable,
- params: Optional[_CoreSingleExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- **kw: Any,
- ) -> Any: ...
-
- def scalar(
- self,
- statement: Executable,
- params: Optional[_CoreSingleExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- **kw: Any,
- ) -> Any:
- """Execute a statement and return a scalar result.
-
- Usage and parameters are the same as that of
- :meth:`_orm.Session.execute`; the return result is a scalar Python
- value.
-
- """
-
- return self._execute_internal(
- statement,
- params,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- _scalar_result=True,
- **kw,
- )
-
- @overload
- def scalars(
- self,
- statement: TypedReturnsRows[Tuple[_T]],
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- **kw: Any,
- ) -> ScalarResult[_T]: ...
-
- @overload
- def scalars(
- self,
- statement: Executable,
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- **kw: Any,
- ) -> ScalarResult[Any]: ...
-
- def scalars(
- self,
- statement: Executable,
- params: Optional[_CoreAnyExecuteParams] = None,
- *,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- **kw: Any,
- ) -> ScalarResult[Any]:
- """Execute a statement and return the results as scalars.
-
- Usage and parameters are the same as that of
- :meth:`_orm.Session.execute`; the return result is a
- :class:`_result.ScalarResult` filtering object which
- will return single elements rather than :class:`_row.Row` objects.
-
- :return: a :class:`_result.ScalarResult` object
-
- .. versionadded:: 1.4.24 Added :meth:`_orm.Session.scalars`
-
- .. versionadded:: 1.4.26 Added :meth:`_orm.scoped_session.scalars`
-
- .. seealso::
-
- :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior
- of :meth:`_orm.Session.execute` to :meth:`_orm.Session.scalars`
-
- """
-
- return self._execute_internal(
- statement,
- params=params,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- _scalar_result=False, # mypy appreciates this
- **kw,
- ).scalars()
-
- def close(self) -> None:
- """Close out the transactional resources and ORM objects used by this
- :class:`_orm.Session`.
-
- This expunges all ORM objects associated with this
- :class:`_orm.Session`, ends any transaction in progress and
- :term:`releases` any :class:`_engine.Connection` objects which this
- :class:`_orm.Session` itself has checked out from associated
- :class:`_engine.Engine` objects. The operation then leaves the
- :class:`_orm.Session` in a state which it may be used again.
-
- .. tip::
-
- In the default running mode the :meth:`_orm.Session.close`
- method **does not prevent the Session from being used again**.
- The :class:`_orm.Session` itself does not actually have a
- distinct "closed" state; it merely means
- the :class:`_orm.Session` will release all database connections
- and ORM objects.
-
- Setting the parameter :paramref:`_orm.Session.close_resets_only`
- to ``False`` will instead make the ``close`` final, meaning that
- any further action on the session will be forbidden.
-
- .. versionchanged:: 1.4 The :meth:`.Session.close` method does not
- immediately create a new :class:`.SessionTransaction` object;
- instead, the new :class:`.SessionTransaction` is created only if
- the :class:`.Session` is used again for a database operation.
-
- .. seealso::
-
- :ref:`session_closing` - detail on the semantics of
- :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
-
- :meth:`_orm.Session.reset` - a similar method that behaves like
- ``close()`` with the parameter
- :paramref:`_orm.Session.close_resets_only` set to ``True``.
-
- """
- self._close_impl(invalidate=False)
-
- def reset(self) -> None:
- """Close out the transactional resources and ORM objects used by this
- :class:`_orm.Session`, resetting the session to its initial state.
-
- This method provides for same "reset-only" behavior that the
- :meth:`_orm.Session.close` method has provided historically, where the
- state of the :class:`_orm.Session` is reset as though the object were
- brand new, and ready to be used again.
- This method may then be useful for :class:`_orm.Session` objects
- which set :paramref:`_orm.Session.close_resets_only` to ``False``,
- so that "reset only" behavior is still available.
-
- .. versionadded:: 2.0.22
-
- .. seealso::
-
- :ref:`session_closing` - detail on the semantics of
- :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
-
- :meth:`_orm.Session.close` - a similar method will additionally
- prevent re-use of the Session when the parameter
- :paramref:`_orm.Session.close_resets_only` is set to ``False``.
- """
- self._close_impl(invalidate=False, is_reset=True)
-
- def invalidate(self) -> None:
- """Close this Session, using connection invalidation.
-
- This is a variant of :meth:`.Session.close` that will additionally
- ensure that the :meth:`_engine.Connection.invalidate`
- method will be called on each :class:`_engine.Connection` object
- that is currently in use for a transaction (typically there is only
- one connection unless the :class:`_orm.Session` is used with
- multiple engines).
-
- This can be called when the database is known to be in a state where
- the connections are no longer safe to be used.
-
- Below illustrates a scenario when using `gevent
- <https://www.gevent.org/>`_, which can produce ``Timeout`` exceptions
- that may mean the underlying connection should be discarded::
-
- import gevent
-
- try:
- sess = Session()
- sess.add(User())
- sess.commit()
- except gevent.Timeout:
- sess.invalidate()
- raise
- except:
- sess.rollback()
- raise
-
- The method additionally does everything that :meth:`_orm.Session.close`
- does, including that all ORM objects are expunged.
-
- """
- self._close_impl(invalidate=True)
-
- def _close_impl(self, invalidate: bool, is_reset: bool = False) -> None:
- if not is_reset and self._close_state is _SessionCloseState.ACTIVE:
- self._close_state = _SessionCloseState.CLOSED
- self.expunge_all()
- if self._transaction is not None:
- for transaction in self._transaction._iterate_self_and_parents():
- transaction.close(invalidate)
-
- def expunge_all(self) -> None:
- """Remove all object instances from this ``Session``.
-
- This is equivalent to calling ``expunge(obj)`` on all objects in this
- ``Session``.
-
- """
-
- all_states = self.identity_map.all_states() + list(self._new)
- self.identity_map._kill()
- self.identity_map = identity.WeakInstanceDict()
- self._new = {}
- self._deleted = {}
-
- statelib.InstanceState._detach_states(all_states, self)
-
- def _add_bind(self, key: _SessionBindKey, bind: _SessionBind) -> None:
- try:
- insp = inspect(key)
- except sa_exc.NoInspectionAvailable as err:
- if not isinstance(key, type):
- raise sa_exc.ArgumentError(
- "Not an acceptable bind target: %s" % key
- ) from err
- else:
- self.__binds[key] = bind
- else:
- if TYPE_CHECKING:
- assert isinstance(insp, Inspectable)
-
- if isinstance(insp, TableClause):
- self.__binds[insp] = bind
- elif insp_is_mapper(insp):
- self.__binds[insp.class_] = bind
- for _selectable in insp._all_tables:
- self.__binds[_selectable] = bind
- else:
- raise sa_exc.ArgumentError(
- "Not an acceptable bind target: %s" % key
- )
-
- def bind_mapper(
- self, mapper: _EntityBindKey[_O], bind: _SessionBind
- ) -> None:
- """Associate a :class:`_orm.Mapper` or arbitrary Python class with a
- "bind", e.g. an :class:`_engine.Engine` or
- :class:`_engine.Connection`.
-
- The given entity is added to a lookup used by the
- :meth:`.Session.get_bind` method.
-
- :param mapper: a :class:`_orm.Mapper` object,
- or an instance of a mapped
- class, or any Python class that is the base of a set of mapped
- classes.
-
- :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
- object.
-
- .. seealso::
-
- :ref:`session_partitioning`
-
- :paramref:`.Session.binds`
-
- :meth:`.Session.bind_table`
-
-
- """
- self._add_bind(mapper, bind)
-
- def bind_table(self, table: TableClause, bind: _SessionBind) -> None:
- """Associate a :class:`_schema.Table` with a "bind", e.g. an
- :class:`_engine.Engine`
- or :class:`_engine.Connection`.
-
- The given :class:`_schema.Table` is added to a lookup used by the
- :meth:`.Session.get_bind` method.
-
- :param table: a :class:`_schema.Table` object,
- which is typically the target
- of an ORM mapping, or is present within a selectable that is
- mapped.
-
- :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
- object.
-
- .. seealso::
-
- :ref:`session_partitioning`
-
- :paramref:`.Session.binds`
-
- :meth:`.Session.bind_mapper`
-
-
- """
- self._add_bind(table, bind)
-
- def get_bind(
- self,
- mapper: Optional[_EntityBindKey[_O]] = None,
- *,
- clause: Optional[ClauseElement] = None,
- bind: Optional[_SessionBind] = None,
- _sa_skip_events: Optional[bool] = None,
- _sa_skip_for_implicit_returning: bool = False,
- **kw: Any,
- ) -> Union[Engine, Connection]:
- """Return a "bind" to which this :class:`.Session` is bound.
-
- The "bind" is usually an instance of :class:`_engine.Engine`,
- except in the case where the :class:`.Session` has been
- explicitly bound directly to a :class:`_engine.Connection`.
-
- For a multiply-bound or unbound :class:`.Session`, the
- ``mapper`` or ``clause`` arguments are used to determine the
- appropriate bind to return.
-
- Note that the "mapper" argument is usually present
- when :meth:`.Session.get_bind` is called via an ORM
- operation such as a :meth:`.Session.query`, each
- individual INSERT/UPDATE/DELETE operation within a
- :meth:`.Session.flush`, call, etc.
-
- The order of resolution is:
-
- 1. if mapper given and :paramref:`.Session.binds` is present,
- locate a bind based first on the mapper in use, then
- on the mapped class in use, then on any base classes that are
- present in the ``__mro__`` of the mapped class, from more specific
- superclasses to more general.
- 2. if clause given and ``Session.binds`` is present,
- locate a bind based on :class:`_schema.Table` objects
- found in the given clause present in ``Session.binds``.
- 3. if ``Session.binds`` is present, return that.
- 4. if clause given, attempt to return a bind
- linked to the :class:`_schema.MetaData` ultimately
- associated with the clause.
- 5. if mapper given, attempt to return a bind
- linked to the :class:`_schema.MetaData` ultimately
- associated with the :class:`_schema.Table` or other
- selectable to which the mapper is mapped.
- 6. No bind can be found, :exc:`~sqlalchemy.exc.UnboundExecutionError`
- is raised.
-
- Note that the :meth:`.Session.get_bind` method can be overridden on
- a user-defined subclass of :class:`.Session` to provide any kind
- of bind resolution scheme. See the example at
- :ref:`session_custom_partitioning`.
-
- :param mapper:
- Optional mapped class or corresponding :class:`_orm.Mapper` instance.
- The bind can be derived from a :class:`_orm.Mapper` first by
- consulting the "binds" map associated with this :class:`.Session`,
- and secondly by consulting the :class:`_schema.MetaData` associated
- with the :class:`_schema.Table` to which the :class:`_orm.Mapper` is
- mapped for a bind.
-
- :param clause:
- A :class:`_expression.ClauseElement` (i.e.
- :func:`_expression.select`,
- :func:`_expression.text`,
- etc.). If the ``mapper`` argument is not present or could not
- produce a bind, the given expression construct will be searched
- for a bound element, typically a :class:`_schema.Table`
- associated with
- bound :class:`_schema.MetaData`.
-
- .. seealso::
-
- :ref:`session_partitioning`
-
- :paramref:`.Session.binds`
-
- :meth:`.Session.bind_mapper`
-
- :meth:`.Session.bind_table`
-
- """
-
- # this function is documented as a subclassing hook, so we have
- # to call this method even if the return is simple
- if bind:
- return bind
- elif not self.__binds and self.bind:
- # simplest and most common case, we have a bind and no
- # per-mapper/table binds, we're done
- return self.bind
-
- # we don't have self.bind and either have self.__binds
- # or we don't have self.__binds (which is legacy). Look at the
- # mapper and the clause
- if mapper is None and clause is None:
- if self.bind:
- return self.bind
- else:
- raise sa_exc.UnboundExecutionError(
- "This session is not bound to a single Engine or "
- "Connection, and no context was provided to locate "
- "a binding."
- )
-
- # look more closely at the mapper.
- if mapper is not None:
- try:
- inspected_mapper = inspect(mapper)
- except sa_exc.NoInspectionAvailable as err:
- if isinstance(mapper, type):
- raise exc.UnmappedClassError(mapper) from err
- else:
- raise
- else:
- inspected_mapper = None
-
- # match up the mapper or clause in the __binds
- if self.__binds:
- # matching mappers and selectables to entries in the
- # binds dictionary; supported use case.
- if inspected_mapper:
- for cls in inspected_mapper.class_.__mro__:
- if cls in self.__binds:
- return self.__binds[cls]
- if clause is None:
- clause = inspected_mapper.persist_selectable
-
- if clause is not None:
- plugin_subject = clause._propagate_attrs.get(
- "plugin_subject", None
- )
-
- if plugin_subject is not None:
- for cls in plugin_subject.mapper.class_.__mro__:
- if cls in self.__binds:
- return self.__binds[cls]
-
- for obj in visitors.iterate(clause):
- if obj in self.__binds:
- if TYPE_CHECKING:
- assert isinstance(obj, Table)
- return self.__binds[obj]
-
- # none of the __binds matched, but we have a fallback bind.
- # return that
- if self.bind:
- return self.bind
-
- context = []
- if inspected_mapper is not None:
- context.append(f"mapper {inspected_mapper}")
- if clause is not None:
- context.append("SQL expression")
-
- raise sa_exc.UnboundExecutionError(
- f"Could not locate a bind configured on "
- f'{", ".join(context)} or this Session.'
- )
-
- @overload
- def query(self, _entity: _EntityType[_O]) -> Query[_O]: ...
-
- @overload
- def query(
- self, _colexpr: TypedColumnsClauseRole[_T]
- ) -> RowReturningQuery[Tuple[_T]]: ...
-
- # START OVERLOADED FUNCTIONS self.query RowReturningQuery 2-8
-
- # code within this block is **programmatically,
- # statically generated** by tools/generate_tuple_map_overloads.py
-
- @overload
- def query(
- self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1]
- ) -> RowReturningQuery[Tuple[_T0, _T1]]: ...
-
- @overload
- def query(
- self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2]
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2]]: ...
-
- @overload
- def query(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3]]: ...
-
- @overload
- def query(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- __ent4: _TCCA[_T4],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4]]: ...
-
- @overload
- def query(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- __ent4: _TCCA[_T4],
- __ent5: _TCCA[_T5],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]: ...
-
- @overload
- def query(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- __ent4: _TCCA[_T4],
- __ent5: _TCCA[_T5],
- __ent6: _TCCA[_T6],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]: ...
-
- @overload
- def query(
- self,
- __ent0: _TCCA[_T0],
- __ent1: _TCCA[_T1],
- __ent2: _TCCA[_T2],
- __ent3: _TCCA[_T3],
- __ent4: _TCCA[_T4],
- __ent5: _TCCA[_T5],
- __ent6: _TCCA[_T6],
- __ent7: _TCCA[_T7],
- ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]: ...
-
- # END OVERLOADED FUNCTIONS self.query
-
- @overload
- def query(
- self, *entities: _ColumnsClauseArgument[Any], **kwargs: Any
- ) -> Query[Any]: ...
-
- def query(
- self, *entities: _ColumnsClauseArgument[Any], **kwargs: Any
- ) -> Query[Any]:
- """Return a new :class:`_query.Query` object corresponding to this
- :class:`_orm.Session`.
-
- Note that the :class:`_query.Query` object is legacy as of
- SQLAlchemy 2.0; the :func:`_sql.select` construct is now used
- to construct ORM queries.
-
- .. seealso::
-
- :ref:`unified_tutorial`
-
- :ref:`queryguide_toplevel`
-
- :ref:`query_api_toplevel` - legacy API doc
-
- """
-
- return self._query_cls(entities, self, **kwargs)
-
- def _identity_lookup(
- self,
- mapper: Mapper[_O],
- primary_key_identity: Union[Any, Tuple[Any, ...]],
- identity_token: Any = None,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- lazy_loaded_from: Optional[InstanceState[Any]] = None,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- ) -> Union[Optional[_O], LoaderCallableStatus]:
- """Locate an object in the identity map.
-
- Given a primary key identity, constructs an identity key and then
- looks in the session's identity map. If present, the object may
- be run through unexpiration rules (e.g. load unloaded attributes,
- check if was deleted).
-
- e.g.::
-
- obj = session._identity_lookup(inspect(SomeClass), (1, ))
-
- :param mapper: mapper in use
- :param primary_key_identity: the primary key we are searching for, as
- a tuple.
- :param identity_token: identity token that should be used to create
- the identity key. Used as is, however overriding subclasses can
- repurpose this in order to interpret the value in a special way,
- such as if None then look among multiple target tokens.
- :param passive: passive load flag passed to
- :func:`.loading.get_from_identity`, which impacts the behavior if
- the object is found; the object may be validated and/or unexpired
- if the flag allows for SQL to be emitted.
- :param lazy_loaded_from: an :class:`.InstanceState` that is
- specifically asking for this identity as a related identity. Used
- for sharding schemes where there is a correspondence between an object
- and a related object being lazy-loaded (or otherwise
- relationship-loaded).
-
- :return: None if the object is not found in the identity map, *or*
- if the object was unexpired and found to have been deleted.
- if passive flags disallow SQL and the object is expired, returns
- PASSIVE_NO_RESULT. In all other cases the instance is returned.
-
- .. versionchanged:: 1.4.0 - the :meth:`.Session._identity_lookup`
- method was moved from :class:`_query.Query` to
- :class:`.Session`, to avoid having to instantiate the
- :class:`_query.Query` object.
-
-
- """
-
- key = mapper.identity_key_from_primary_key(
- primary_key_identity, identity_token=identity_token
- )
-
- # work around: https://github.com/python/typing/discussions/1143
- return_value = loading.get_from_identity(self, mapper, key, passive)
- return return_value
-
- @util.non_memoized_property
- @contextlib.contextmanager
- def no_autoflush(self) -> Iterator[Session]:
- """Return a context manager that disables autoflush.
-
- e.g.::
-
- with session.no_autoflush:
-
- some_object = SomeClass()
- session.add(some_object)
- # won't autoflush
- some_object.related_thing = session.query(SomeRelated).first()
-
- Operations that proceed within the ``with:`` block
- will not be subject to flushes occurring upon query
- access. This is useful when initializing a series
- of objects which involve existing database queries,
- where the uncompleted object should not yet be flushed.
-
- """
- autoflush = self.autoflush
- self.autoflush = False
- try:
- yield self
- finally:
- self.autoflush = autoflush
-
- @util.langhelpers.tag_method_for_warnings(
- "This warning originated from the Session 'autoflush' process, "
- "which was invoked automatically in response to a user-initiated "
- "operation.",
- sa_exc.SAWarning,
- )
- def _autoflush(self) -> None:
- if self.autoflush and not self._flushing:
- try:
- self.flush()
- except sa_exc.StatementError as e:
- # note we are reraising StatementError as opposed to
- # raising FlushError with "chaining" to remain compatible
- # with code that catches StatementError, IntegrityError,
- # etc.
- e.add_detail(
- "raised as a result of Query-invoked autoflush; "
- "consider using a session.no_autoflush block if this "
- "flush is occurring prematurely"
- )
- raise e.with_traceback(sys.exc_info()[2])
-
- def refresh(
- self,
- instance: object,
- attribute_names: Optional[Iterable[str]] = None,
- with_for_update: ForUpdateParameter = None,
- ) -> None:
- """Expire and refresh attributes on the given instance.
-
- The selected attributes will first be expired as they would when using
- :meth:`_orm.Session.expire`; then a SELECT statement will be issued to
- the database to refresh column-oriented attributes with the current
- value available in the current transaction.
-
- :func:`_orm.relationship` oriented attributes will also be immediately
- loaded if they were already eagerly loaded on the object, using the
- same eager loading strategy that they were loaded with originally.
-
- .. versionadded:: 1.4 - the :meth:`_orm.Session.refresh` method
- can also refresh eagerly loaded attributes.
-
- :func:`_orm.relationship` oriented attributes that would normally
- load using the ``select`` (or "lazy") loader strategy will also
- load **if they are named explicitly in the attribute_names
- collection**, emitting a SELECT statement for the attribute using the
- ``immediate`` loader strategy. If lazy-loaded relationships are not
- named in :paramref:`_orm.Session.refresh.attribute_names`, then
- they remain as "lazy loaded" attributes and are not implicitly
- refreshed.
-
- .. versionchanged:: 2.0.4 The :meth:`_orm.Session.refresh` method
- will now refresh lazy-loaded :func:`_orm.relationship` oriented
- attributes for those which are named explicitly in the
- :paramref:`_orm.Session.refresh.attribute_names` collection.
-
- .. tip::
-
- While the :meth:`_orm.Session.refresh` method is capable of
- refreshing both column and relationship oriented attributes, its
- primary focus is on refreshing of local column-oriented attributes
- on a single instance. For more open ended "refresh" functionality,
- including the ability to refresh the attributes on many objects at
- once while having explicit control over relationship loader
- strategies, use the
- :ref:`populate existing <orm_queryguide_populate_existing>` feature
- instead.
-
- Note that a highly isolated transaction will return the same values as
- were previously read in that same transaction, regardless of changes
- in database state outside of that transaction. Refreshing
- attributes usually only makes sense at the start of a transaction
- where database rows have not yet been accessed.
-
- :param attribute_names: optional. An iterable collection of
- string attribute names indicating a subset of attributes to
- be refreshed.
-
- :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
- should be used, or may be a dictionary containing flags to
- indicate a more specific set of FOR UPDATE flags for the SELECT;
- flags should match the parameters of
- :meth:`_query.Query.with_for_update`.
- Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
-
- .. seealso::
-
- :ref:`session_expire` - introductory material
-
- :meth:`.Session.expire`
-
- :meth:`.Session.expire_all`
-
- :ref:`orm_queryguide_populate_existing` - allows any ORM query
- to refresh objects as they would be loaded normally.
-
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- raise exc.UnmappedInstanceError(instance) from err
-
- self._expire_state(state, attribute_names)
-
- # this autoflush previously used to occur as a secondary effect
- # of the load_on_ident below. Meaning we'd organize the SELECT
- # based on current DB pks, then flush, then if pks changed in that
- # flush, crash. this was unticketed but discovered as part of
- # #8703. So here, autoflush up front, dont autoflush inside
- # load_on_ident.
- self._autoflush()
-
- if with_for_update == {}:
- raise sa_exc.ArgumentError(
- "with_for_update should be the boolean value "
- "True, or a dictionary with options. "
- "A blank dictionary is ambiguous."
- )
-
- with_for_update = ForUpdateArg._from_argument(with_for_update)
-
- stmt: Select[Any] = sql.select(object_mapper(instance))
- if (
- loading.load_on_ident(
- self,
- stmt,
- state.key,
- refresh_state=state,
- with_for_update=with_for_update,
- only_load_props=attribute_names,
- require_pk_cols=True,
- # technically unnecessary as we just did autoflush
- # above, however removes the additional unnecessary
- # call to _autoflush()
- no_autoflush=True,
- is_user_refresh=True,
- )
- is None
- ):
- raise sa_exc.InvalidRequestError(
- "Could not refresh instance '%s'" % instance_str(instance)
- )
-
- def expire_all(self) -> None:
- """Expires all persistent instances within this Session.
-
- When any attributes on a persistent instance is next accessed,
- a query will be issued using the
- :class:`.Session` object's current transactional context in order to
- load all expired attributes for the given instance. Note that
- a highly isolated transaction will return the same values as were
- previously read in that same transaction, regardless of changes
- in database state outside of that transaction.
-
- To expire individual objects and individual attributes
- on those objects, use :meth:`Session.expire`.
-
- The :class:`.Session` object's default behavior is to
- expire all state whenever the :meth:`Session.rollback`
- or :meth:`Session.commit` methods are called, so that new
- state can be loaded for the new transaction. For this reason,
- calling :meth:`Session.expire_all` is not usually needed,
- assuming the transaction is isolated.
-
- .. seealso::
-
- :ref:`session_expire` - introductory material
-
- :meth:`.Session.expire`
-
- :meth:`.Session.refresh`
-
- :meth:`_orm.Query.populate_existing`
-
- """
- for state in self.identity_map.all_states():
- state._expire(state.dict, self.identity_map._modified)
-
- def expire(
- self, instance: object, attribute_names: Optional[Iterable[str]] = None
- ) -> None:
- """Expire the attributes on an instance.
-
- Marks the attributes of an instance as out of date. When an expired
- attribute is next accessed, a query will be issued to the
- :class:`.Session` object's current transactional context in order to
- load all expired attributes for the given instance. Note that
- a highly isolated transaction will return the same values as were
- previously read in that same transaction, regardless of changes
- in database state outside of that transaction.
-
- To expire all objects in the :class:`.Session` simultaneously,
- use :meth:`Session.expire_all`.
-
- The :class:`.Session` object's default behavior is to
- expire all state whenever the :meth:`Session.rollback`
- or :meth:`Session.commit` methods are called, so that new
- state can be loaded for the new transaction. For this reason,
- calling :meth:`Session.expire` only makes sense for the specific
- case that a non-ORM SQL statement was emitted in the current
- transaction.
-
- :param instance: The instance to be refreshed.
- :param attribute_names: optional list of string attribute names
- indicating a subset of attributes to be expired.
-
- .. seealso::
-
- :ref:`session_expire` - introductory material
-
- :meth:`.Session.expire`
-
- :meth:`.Session.refresh`
-
- :meth:`_orm.Query.populate_existing`
-
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- raise exc.UnmappedInstanceError(instance) from err
- self._expire_state(state, attribute_names)
-
- def _expire_state(
- self,
- state: InstanceState[Any],
- attribute_names: Optional[Iterable[str]],
- ) -> None:
- self._validate_persistent(state)
- if attribute_names:
- state._expire_attributes(state.dict, attribute_names)
- else:
- # pre-fetch the full cascade since the expire is going to
- # remove associations
- cascaded = list(
- state.manager.mapper.cascade_iterator("refresh-expire", state)
- )
- self._conditional_expire(state)
- for o, m, st_, dct_ in cascaded:
- self._conditional_expire(st_)
-
- def _conditional_expire(
- self, state: InstanceState[Any], autoflush: Optional[bool] = None
- ) -> None:
- """Expire a state if persistent, else expunge if pending"""
-
- if state.key:
- state._expire(state.dict, self.identity_map._modified)
- elif state in self._new:
- self._new.pop(state)
- state._detach(self)
-
- def expunge(self, instance: object) -> None:
- """Remove the `instance` from this ``Session``.
-
- This will free all internal references to the instance. Cascading
- will be applied according to the *expunge* cascade rule.
-
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- raise exc.UnmappedInstanceError(instance) from err
- if state.session_id is not self.hash_key:
- raise sa_exc.InvalidRequestError(
- "Instance %s is not present in this Session" % state_str(state)
- )
-
- cascaded = list(
- state.manager.mapper.cascade_iterator("expunge", state)
- )
- self._expunge_states([state] + [st_ for o, m, st_, dct_ in cascaded])
-
- def _expunge_states(
- self, states: Iterable[InstanceState[Any]], to_transient: bool = False
- ) -> None:
- for state in states:
- if state in self._new:
- self._new.pop(state)
- elif self.identity_map.contains_state(state):
- self.identity_map.safe_discard(state)
- self._deleted.pop(state, None)
- elif self._transaction:
- # state is "detached" from being deleted, but still present
- # in the transaction snapshot
- self._transaction._deleted.pop(state, None)
- statelib.InstanceState._detach_states(
- states, self, to_transient=to_transient
- )
-
- def _register_persistent(self, states: Set[InstanceState[Any]]) -> None:
- """Register all persistent objects from a flush.
-
- This is used both for pending objects moving to the persistent
- state as well as already persistent objects.
-
- """
-
- pending_to_persistent = self.dispatch.pending_to_persistent or None
- for state in states:
- mapper = _state_mapper(state)
-
- # prevent against last minute dereferences of the object
- obj = state.obj()
- if obj is not None:
- instance_key = mapper._identity_key_from_state(state)
-
- if (
- _none_set.intersection(instance_key[1])
- and not mapper.allow_partial_pks
- or _none_set.issuperset(instance_key[1])
- ):
- raise exc.FlushError(
- "Instance %s has a NULL identity key. If this is an "
- "auto-generated value, check that the database table "
- "allows generation of new primary key values, and "
- "that the mapped Column object is configured to "
- "expect these generated values. Ensure also that "
- "this flush() is not occurring at an inappropriate "
- "time, such as within a load() event."
- % state_str(state)
- )
-
- if state.key is None:
- state.key = instance_key
- elif state.key != instance_key:
- # primary key switch. use safe_discard() in case another
- # state has already replaced this one in the identity
- # map (see test/orm/test_naturalpks.py ReversePKsTest)
- self.identity_map.safe_discard(state)
- trans = self._transaction
- assert trans is not None
- if state in trans._key_switches:
- orig_key = trans._key_switches[state][0]
- else:
- orig_key = state.key
- trans._key_switches[state] = (
- orig_key,
- instance_key,
- )
- state.key = instance_key
-
- # there can be an existing state in the identity map
- # that is replaced when the primary keys of two instances
- # are swapped; see test/orm/test_naturalpks.py -> test_reverse
- old = self.identity_map.replace(state)
- if (
- old is not None
- and mapper._identity_key_from_state(old) == instance_key
- and old.obj() is not None
- ):
- util.warn(
- "Identity map already had an identity for %s, "
- "replacing it with newly flushed object. Are there "
- "load operations occurring inside of an event handler "
- "within the flush?" % (instance_key,)
- )
- state._orphaned_outside_of_session = False
-
- statelib.InstanceState._commit_all_states(
- ((state, state.dict) for state in states), self.identity_map
- )
-
- self._register_altered(states)
-
- if pending_to_persistent is not None:
- for state in states.intersection(self._new):
- pending_to_persistent(self, state)
-
- # remove from new last, might be the last strong ref
- for state in set(states).intersection(self._new):
- self._new.pop(state)
-
- def _register_altered(self, states: Iterable[InstanceState[Any]]) -> None:
- if self._transaction:
- for state in states:
- if state in self._new:
- self._transaction._new[state] = True
- else:
- self._transaction._dirty[state] = True
-
- def _remove_newly_deleted(
- self, states: Iterable[InstanceState[Any]]
- ) -> None:
- persistent_to_deleted = self.dispatch.persistent_to_deleted or None
- for state in states:
- if self._transaction:
- self._transaction._deleted[state] = True
-
- if persistent_to_deleted is not None:
- # get a strong reference before we pop out of
- # self._deleted
- obj = state.obj() # noqa
-
- self.identity_map.safe_discard(state)
- self._deleted.pop(state, None)
- state._deleted = True
- # can't call state._detach() here, because this state
- # is still in the transaction snapshot and needs to be
- # tracked as part of that
- if persistent_to_deleted is not None:
- persistent_to_deleted(self, state)
-
- def add(self, instance: object, _warn: bool = True) -> None:
- """Place an object into this :class:`_orm.Session`.
-
- Objects that are in the :term:`transient` state when passed to the
- :meth:`_orm.Session.add` method will move to the
- :term:`pending` state, until the next flush, at which point they
- will move to the :term:`persistent` state.
-
- Objects that are in the :term:`detached` state when passed to the
- :meth:`_orm.Session.add` method will move to the :term:`persistent`
- state directly.
-
- If the transaction used by the :class:`_orm.Session` is rolled back,
- objects which were transient when they were passed to
- :meth:`_orm.Session.add` will be moved back to the
- :term:`transient` state, and will no longer be present within this
- :class:`_orm.Session`.
-
- .. seealso::
-
- :meth:`_orm.Session.add_all`
-
- :ref:`session_adding` - at :ref:`session_basics`
-
- """
- if _warn and self._warn_on_events:
- self._flush_warning("Session.add()")
-
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- raise exc.UnmappedInstanceError(instance) from err
-
- self._save_or_update_state(state)
-
- def add_all(self, instances: Iterable[object]) -> None:
- """Add the given collection of instances to this :class:`_orm.Session`.
-
- See the documentation for :meth:`_orm.Session.add` for a general
- behavioral description.
-
- .. seealso::
-
- :meth:`_orm.Session.add`
-
- :ref:`session_adding` - at :ref:`session_basics`
-
- """
-
- if self._warn_on_events:
- self._flush_warning("Session.add_all()")
-
- for instance in instances:
- self.add(instance, _warn=False)
-
- def _save_or_update_state(self, state: InstanceState[Any]) -> None:
- state._orphaned_outside_of_session = False
- self._save_or_update_impl(state)
-
- mapper = _state_mapper(state)
- for o, m, st_, dct_ in mapper.cascade_iterator(
- "save-update", state, halt_on=self._contains_state
- ):
- self._save_or_update_impl(st_)
-
- def delete(self, instance: object) -> None:
- """Mark an instance as deleted.
-
- The object is assumed to be either :term:`persistent` or
- :term:`detached` when passed; after the method is called, the
- object will remain in the :term:`persistent` state until the next
- flush proceeds. During this time, the object will also be a member
- of the :attr:`_orm.Session.deleted` collection.
-
- When the next flush proceeds, the object will move to the
- :term:`deleted` state, indicating a ``DELETE`` statement was emitted
- for its row within the current transaction. When the transaction
- is successfully committed,
- the deleted object is moved to the :term:`detached` state and is
- no longer present within this :class:`_orm.Session`.
-
- .. seealso::
-
- :ref:`session_deleting` - at :ref:`session_basics`
-
- """
- if self._warn_on_events:
- self._flush_warning("Session.delete()")
-
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- raise exc.UnmappedInstanceError(instance) from err
-
- self._delete_impl(state, instance, head=True)
-
- def _delete_impl(
- self, state: InstanceState[Any], obj: object, head: bool
- ) -> None:
- if state.key is None:
- if head:
- raise sa_exc.InvalidRequestError(
- "Instance '%s' is not persisted" % state_str(state)
- )
- else:
- return
-
- to_attach = self._before_attach(state, obj)
-
- if state in self._deleted:
- return
-
- self.identity_map.add(state)
-
- if to_attach:
- self._after_attach(state, obj)
-
- if head:
- # grab the cascades before adding the item to the deleted list
- # so that autoflush does not delete the item
- # the strong reference to the instance itself is significant here
- cascade_states = list(
- state.manager.mapper.cascade_iterator("delete", state)
- )
- else:
- cascade_states = None
-
- self._deleted[state] = obj
-
- if head:
- if TYPE_CHECKING:
- assert cascade_states is not None
- for o, m, st_, dct_ in cascade_states:
- self._delete_impl(st_, o, False)
-
- def get(
- self,
- entity: _EntityBindKey[_O],
- ident: _PKIdentityArgument,
- *,
- options: Optional[Sequence[ORMOption]] = None,
- populate_existing: bool = False,
- with_for_update: ForUpdateParameter = None,
- identity_token: Optional[Any] = None,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- ) -> Optional[_O]:
- """Return an instance based on the given primary key identifier,
- or ``None`` if not found.
-
- E.g.::
-
- my_user = session.get(User, 5)
-
- some_object = session.get(VersionedFoo, (5, 10))
-
- some_object = session.get(
- VersionedFoo,
- {"id": 5, "version_id": 10}
- )
-
- .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved
- from the now legacy :meth:`_orm.Query.get` method.
-
- :meth:`_orm.Session.get` is special in that it provides direct
- access to the identity map of the :class:`.Session`.
- If the given primary key identifier is present
- in the local identity map, the object is returned
- directly from this collection and no SQL is emitted,
- unless the object has been marked fully expired.
- If not present,
- a SELECT is performed in order to locate the object.
-
- :meth:`_orm.Session.get` also will perform a check if
- the object is present in the identity map and
- marked as expired - a SELECT
- is emitted to refresh the object as well as to
- ensure that the row is still present.
- If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
-
- :param entity: a mapped class or :class:`.Mapper` indicating the
- type of entity to be loaded.
-
- :param ident: A scalar, tuple, or dictionary representing the
- primary key. For a composite (e.g. multiple column) primary key,
- a tuple or dictionary should be passed.
-
- For a single-column primary key, the scalar calling form is typically
- the most expedient. If the primary key of a row is the value "5",
- the call looks like::
-
- my_object = session.get(SomeClass, 5)
-
- The tuple form contains primary key values typically in
- the order in which they correspond to the mapped
- :class:`_schema.Table`
- object's primary key columns, or if the
- :paramref:`_orm.Mapper.primary_key` configuration parameter were
- used, in
- the order used for that parameter. For example, if the primary key
- of a row is represented by the integer
- digits "5, 10" the call would look like::
-
- my_object = session.get(SomeClass, (5, 10))
-
- The dictionary form should include as keys the mapped attribute names
- corresponding to each element of the primary key. If the mapped class
- has the attributes ``id``, ``version_id`` as the attributes which
- store the object's primary key value, the call would look like::
-
- my_object = session.get(SomeClass, {"id": 5, "version_id": 10})
-
- :param options: optional sequence of loader options which will be
- applied to the query, if one is emitted.
-
- :param populate_existing: causes the method to unconditionally emit
- a SQL query and refresh the object with the newly loaded data,
- regardless of whether or not the object is already present.
-
- :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
- should be used, or may be a dictionary containing flags to
- indicate a more specific set of FOR UPDATE flags for the SELECT;
- flags should match the parameters of
- :meth:`_query.Query.with_for_update`.
- Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
-
- :param execution_options: optional dictionary of execution options,
- which will be associated with the query execution if one is emitted.
- This dictionary can provide a subset of the options that are
- accepted by :meth:`_engine.Connection.execution_options`, and may
- also provide additional options understood only in an ORM context.
-
- .. versionadded:: 1.4.29
-
- .. seealso::
-
- :ref:`orm_queryguide_execution_options` - ORM-specific execution
- options
-
- :param bind_arguments: dictionary of additional arguments to determine
- the bind. May include "mapper", "bind", or other custom arguments.
- Contents of this dictionary are passed to the
- :meth:`.Session.get_bind` method.
-
- .. versionadded: 2.0.0rc1
-
- :return: The object instance, or ``None``.
-
- """
- return self._get_impl(
- entity,
- ident,
- loading.load_on_pk_identity,
- options=options,
- populate_existing=populate_existing,
- with_for_update=with_for_update,
- identity_token=identity_token,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- )
-
- def get_one(
- self,
- entity: _EntityBindKey[_O],
- ident: _PKIdentityArgument,
- *,
- options: Optional[Sequence[ORMOption]] = None,
- populate_existing: bool = False,
- with_for_update: ForUpdateParameter = None,
- identity_token: Optional[Any] = None,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- ) -> _O:
- """Return exactly one instance based on the given primary key
- identifier, or raise an exception if not found.
-
- Raises ``sqlalchemy.orm.exc.NoResultFound`` if the query
- selects no rows.
-
- For a detailed documentation of the arguments see the
- method :meth:`.Session.get`.
-
- .. versionadded:: 2.0.22
-
- :return: The object instance.
-
- .. seealso::
-
- :meth:`.Session.get` - equivalent method that instead
- returns ``None`` if no row was found with the provided primary
- key
-
- """
-
- instance = self.get(
- entity,
- ident,
- options=options,
- populate_existing=populate_existing,
- with_for_update=with_for_update,
- identity_token=identity_token,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- )
-
- if instance is None:
- raise sa_exc.NoResultFound(
- "No row was found when one was required"
- )
-
- return instance
-
- def _get_impl(
- self,
- entity: _EntityBindKey[_O],
- primary_key_identity: _PKIdentityArgument,
- db_load_fn: Callable[..., _O],
- *,
- options: Optional[Sequence[ExecutableOption]] = None,
- populate_existing: bool = False,
- with_for_update: ForUpdateParameter = None,
- identity_token: Optional[Any] = None,
- execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
- bind_arguments: Optional[_BindArguments] = None,
- ) -> Optional[_O]:
- # convert composite types to individual args
- if (
- is_composite_class(primary_key_identity)
- and type(primary_key_identity)
- in descriptor_props._composite_getters
- ):
- getter = descriptor_props._composite_getters[
- type(primary_key_identity)
- ]
- primary_key_identity = getter(primary_key_identity)
-
- mapper: Optional[Mapper[_O]] = inspect(entity)
-
- if mapper is None or not mapper.is_mapper:
- raise sa_exc.ArgumentError(
- "Expected mapped class or mapper, got: %r" % entity
- )
-
- is_dict = isinstance(primary_key_identity, dict)
- if not is_dict:
- primary_key_identity = util.to_list(
- primary_key_identity, default=[None]
- )
-
- if len(primary_key_identity) != len(mapper.primary_key):
- raise sa_exc.InvalidRequestError(
- "Incorrect number of values in identifier to formulate "
- "primary key for session.get(); primary key columns "
- "are %s" % ",".join("'%s'" % c for c in mapper.primary_key)
- )
-
- if is_dict:
- pk_synonyms = mapper._pk_synonyms
-
- if pk_synonyms:
- correct_keys = set(pk_synonyms).intersection(
- primary_key_identity
- )
-
- if correct_keys:
- primary_key_identity = dict(primary_key_identity)
- for k in correct_keys:
- primary_key_identity[pk_synonyms[k]] = (
- primary_key_identity[k]
- )
-
- try:
- primary_key_identity = list(
- primary_key_identity[prop.key]
- for prop in mapper._identity_key_props
- )
-
- except KeyError as err:
- raise sa_exc.InvalidRequestError(
- "Incorrect names of values in identifier to formulate "
- "primary key for session.get(); primary key attribute "
- "names are %s (synonym names are also accepted)"
- % ",".join(
- "'%s'" % prop.key
- for prop in mapper._identity_key_props
- )
- ) from err
-
- if (
- not populate_existing
- and not mapper.always_refresh
- and with_for_update is None
- ):
- instance = self._identity_lookup(
- mapper,
- primary_key_identity,
- identity_token=identity_token,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- )
-
- if instance is not None:
- # reject calls for id in identity map but class
- # mismatch.
- if not isinstance(instance, mapper.class_):
- return None
- return instance
-
- # TODO: this was being tested before, but this is not possible
- assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH
-
- # set_label_style() not strictly necessary, however this will ensure
- # that tablename_colname style is used which at the moment is
- # asserted in a lot of unit tests :)
-
- load_options = context.QueryContext.default_load_options
-
- if populate_existing:
- load_options += {"_populate_existing": populate_existing}
- statement = sql.select(mapper).set_label_style(
- LABEL_STYLE_TABLENAME_PLUS_COL
- )
- if with_for_update is not None:
- statement._for_update_arg = ForUpdateArg._from_argument(
- with_for_update
- )
-
- if options:
- statement = statement.options(*options)
- return db_load_fn(
- self,
- statement,
- primary_key_identity,
- load_options=load_options,
- identity_token=identity_token,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- )
-
- def merge(
- self,
- instance: _O,
- *,
- load: bool = True,
- options: Optional[Sequence[ORMOption]] = None,
- ) -> _O:
- """Copy the state of a given instance into a corresponding instance
- within this :class:`.Session`.
-
- :meth:`.Session.merge` examines the primary key attributes of the
- source instance, and attempts to reconcile it with an instance of the
- same primary key in the session. If not found locally, it attempts
- to load the object from the database based on primary key, and if
- none can be located, creates a new instance. The state of each
- attribute on the source instance is then copied to the target
- instance. The resulting target instance is then returned by the
- method; the original source instance is left unmodified, and
- un-associated with the :class:`.Session` if not already.
-
- This operation cascades to associated instances if the association is
- mapped with ``cascade="merge"``.
-
- See :ref:`unitofwork_merging` for a detailed discussion of merging.
-
- :param instance: Instance to be merged.
- :param load: Boolean, when False, :meth:`.merge` switches into
- a "high performance" mode which causes it to forego emitting history
- events as well as all database access. This flag is used for
- cases such as transferring graphs of objects into a :class:`.Session`
- from a second level cache, or to transfer just-loaded objects
- into the :class:`.Session` owned by a worker thread or process
- without re-querying the database.
-
- The ``load=False`` use case adds the caveat that the given
- object has to be in a "clean" state, that is, has no pending changes
- to be flushed - even if the incoming object is detached from any
- :class:`.Session`. This is so that when
- the merge operation populates local attributes and
- cascades to related objects and
- collections, the values can be "stamped" onto the
- target object as is, without generating any history or attribute
- events, and without the need to reconcile the incoming data with
- any existing related objects or collections that might not
- be loaded. The resulting objects from ``load=False`` are always
- produced as "clean", so it is only appropriate that the given objects
- should be "clean" as well, else this suggests a mis-use of the
- method.
- :param options: optional sequence of loader options which will be
- applied to the :meth:`_orm.Session.get` method when the merge
- operation loads the existing version of the object from the database.
-
- .. versionadded:: 1.4.24
-
-
- .. seealso::
-
- :func:`.make_transient_to_detached` - provides for an alternative
- means of "merging" a single object into the :class:`.Session`
-
- """
-
- if self._warn_on_events:
- self._flush_warning("Session.merge()")
-
- _recursive: Dict[InstanceState[Any], object] = {}
- _resolve_conflict_map: Dict[_IdentityKeyType[Any], object] = {}
-
- if load:
- # flush current contents if we expect to load data
- self._autoflush()
-
- object_mapper(instance) # verify mapped
- autoflush = self.autoflush
- try:
- self.autoflush = False
- return self._merge(
- attributes.instance_state(instance),
- attributes.instance_dict(instance),
- load=load,
- options=options,
- _recursive=_recursive,
- _resolve_conflict_map=_resolve_conflict_map,
- )
- finally:
- self.autoflush = autoflush
-
- def _merge(
- self,
- state: InstanceState[_O],
- state_dict: _InstanceDict,
- *,
- options: Optional[Sequence[ORMOption]] = None,
- load: bool,
- _recursive: Dict[Any, object],
- _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
- ) -> _O:
- mapper: Mapper[_O] = _state_mapper(state)
- if state in _recursive:
- return cast(_O, _recursive[state])
-
- new_instance = False
- key = state.key
-
- merged: Optional[_O]
-
- if key is None:
- if state in self._new:
- util.warn(
- "Instance %s is already pending in this Session yet is "
- "being merged again; this is probably not what you want "
- "to do" % state_str(state)
- )
-
- if not load:
- raise sa_exc.InvalidRequestError(
- "merge() with load=False option does not support "
- "objects transient (i.e. unpersisted) objects. flush() "
- "all changes on mapped instances before merging with "
- "load=False."
- )
- key = mapper._identity_key_from_state(state)
- key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[
- 1
- ] and (
- not _none_set.intersection(key[1])
- or (
- mapper.allow_partial_pks
- and not _none_set.issuperset(key[1])
- )
- )
- else:
- key_is_persistent = True
-
- if key in self.identity_map:
- try:
- merged = self.identity_map[key]
- except KeyError:
- # object was GC'ed right as we checked for it
- merged = None
- else:
- merged = None
-
- if merged is None:
- if key_is_persistent and key in _resolve_conflict_map:
- merged = cast(_O, _resolve_conflict_map[key])
-
- elif not load:
- if state.modified:
- raise sa_exc.InvalidRequestError(
- "merge() with load=False option does not support "
- "objects marked as 'dirty'. flush() all changes on "
- "mapped instances before merging with load=False."
- )
- merged = mapper.class_manager.new_instance()
- merged_state = attributes.instance_state(merged)
- merged_state.key = key
- self._update_impl(merged_state)
- new_instance = True
-
- elif key_is_persistent:
- merged = self.get(
- mapper.class_,
- key[1],
- identity_token=key[2],
- options=options,
- )
-
- if merged is None:
- merged = mapper.class_manager.new_instance()
- merged_state = attributes.instance_state(merged)
- merged_dict = attributes.instance_dict(merged)
- new_instance = True
- self._save_or_update_state(merged_state)
- else:
- merged_state = attributes.instance_state(merged)
- merged_dict = attributes.instance_dict(merged)
-
- _recursive[state] = merged
- _resolve_conflict_map[key] = merged
-
- # check that we didn't just pull the exact same
- # state out.
- if state is not merged_state:
- # version check if applicable
- if mapper.version_id_col is not None:
- existing_version = mapper._get_state_attr_by_column(
- state,
- state_dict,
- mapper.version_id_col,
- passive=PassiveFlag.PASSIVE_NO_INITIALIZE,
- )
-
- merged_version = mapper._get_state_attr_by_column(
- merged_state,
- merged_dict,
- mapper.version_id_col,
- passive=PassiveFlag.PASSIVE_NO_INITIALIZE,
- )
-
- if (
- existing_version
- is not LoaderCallableStatus.PASSIVE_NO_RESULT
- and merged_version
- is not LoaderCallableStatus.PASSIVE_NO_RESULT
- and existing_version != merged_version
- ):
- raise exc.StaleDataError(
- "Version id '%s' on merged state %s "
- "does not match existing version '%s'. "
- "Leave the version attribute unset when "
- "merging to update the most recent version."
- % (
- existing_version,
- state_str(merged_state),
- merged_version,
- )
- )
-
- merged_state.load_path = state.load_path
- merged_state.load_options = state.load_options
-
- # since we are copying load_options, we need to copy
- # the callables_ that would have been generated by those
- # load_options.
- # assumes that the callables we put in state.callables_
- # are not instance-specific (which they should not be)
- merged_state._copy_callables(state)
-
- for prop in mapper.iterate_properties:
- prop.merge(
- self,
- state,
- state_dict,
- merged_state,
- merged_dict,
- load,
- _recursive,
- _resolve_conflict_map,
- )
-
- if not load:
- # remove any history
- merged_state._commit_all(merged_dict, self.identity_map)
- merged_state.manager.dispatch._sa_event_merge_wo_load(
- merged_state, None
- )
-
- if new_instance:
- merged_state.manager.dispatch.load(merged_state, None)
-
- return merged
-
- def _validate_persistent(self, state: InstanceState[Any]) -> None:
- if not self.identity_map.contains_state(state):
- raise sa_exc.InvalidRequestError(
- "Instance '%s' is not persistent within this Session"
- % state_str(state)
- )
-
- def _save_impl(self, state: InstanceState[Any]) -> None:
- if state.key is not None:
- raise sa_exc.InvalidRequestError(
- "Object '%s' already has an identity - "
- "it can't be registered as pending" % state_str(state)
- )
-
- obj = state.obj()
- to_attach = self._before_attach(state, obj)
- if state not in self._new:
- self._new[state] = obj
- state.insert_order = len(self._new)
- if to_attach:
- self._after_attach(state, obj)
-
- def _update_impl(
- self, state: InstanceState[Any], revert_deletion: bool = False
- ) -> None:
- if state.key is None:
- raise sa_exc.InvalidRequestError(
- "Instance '%s' is not persisted" % state_str(state)
- )
-
- if state._deleted:
- if revert_deletion:
- if not state._attached:
- return
- del state._deleted
- else:
- raise sa_exc.InvalidRequestError(
- "Instance '%s' has been deleted. "
- "Use the make_transient() "
- "function to send this object back "
- "to the transient state." % state_str(state)
- )
-
- obj = state.obj()
-
- # check for late gc
- if obj is None:
- return
-
- to_attach = self._before_attach(state, obj)
-
- self._deleted.pop(state, None)
- if revert_deletion:
- self.identity_map.replace(state)
- else:
- self.identity_map.add(state)
-
- if to_attach:
- self._after_attach(state, obj)
- elif revert_deletion:
- self.dispatch.deleted_to_persistent(self, state)
-
- def _save_or_update_impl(self, state: InstanceState[Any]) -> None:
- if state.key is None:
- self._save_impl(state)
- else:
- self._update_impl(state)
-
- def enable_relationship_loading(self, obj: object) -> None:
- """Associate an object with this :class:`.Session` for related
- object loading.
-
- .. warning::
-
- :meth:`.enable_relationship_loading` exists to serve special
- use cases and is not recommended for general use.
-
- Accesses of attributes mapped with :func:`_orm.relationship`
- will attempt to load a value from the database using this
- :class:`.Session` as the source of connectivity. The values
- will be loaded based on foreign key and primary key values
- present on this object - if not present, then those relationships
- will be unavailable.
-
- The object will be attached to this session, but will
- **not** participate in any persistence operations; its state
- for almost all purposes will remain either "transient" or
- "detached", except for the case of relationship loading.
-
- Also note that backrefs will often not work as expected.
- Altering a relationship-bound attribute on the target object
- may not fire off a backref event, if the effective value
- is what was already loaded from a foreign-key-holding value.
-
- The :meth:`.Session.enable_relationship_loading` method is
- similar to the ``load_on_pending`` flag on :func:`_orm.relationship`.
- Unlike that flag, :meth:`.Session.enable_relationship_loading` allows
- an object to remain transient while still being able to load
- related items.
-
- To make a transient object associated with a :class:`.Session`
- via :meth:`.Session.enable_relationship_loading` pending, add
- it to the :class:`.Session` using :meth:`.Session.add` normally.
- If the object instead represents an existing identity in the database,
- it should be merged using :meth:`.Session.merge`.
-
- :meth:`.Session.enable_relationship_loading` does not improve
- behavior when the ORM is used normally - object references should be
- constructed at the object level, not at the foreign key level, so
- that they are present in an ordinary way before flush()
- proceeds. This method is not intended for general use.
-
- .. seealso::
-
- :paramref:`_orm.relationship.load_on_pending` - this flag
- allows per-relationship loading of many-to-ones on items that
- are pending.
-
- :func:`.make_transient_to_detached` - allows for an object to
- be added to a :class:`.Session` without SQL emitted, which then
- will unexpire attributes on access.
-
- """
- try:
- state = attributes.instance_state(obj)
- except exc.NO_STATE as err:
- raise exc.UnmappedInstanceError(obj) from err
-
- to_attach = self._before_attach(state, obj)
- state._load_pending = True
- if to_attach:
- self._after_attach(state, obj)
-
- def _before_attach(self, state: InstanceState[Any], obj: object) -> bool:
- self._autobegin_t()
-
- if state.session_id == self.hash_key:
- return False
-
- if state.session_id and state.session_id in _sessions:
- raise sa_exc.InvalidRequestError(
- "Object '%s' is already attached to session '%s' "
- "(this is '%s')"
- % (state_str(state), state.session_id, self.hash_key)
- )
-
- self.dispatch.before_attach(self, state)
-
- return True
-
- def _after_attach(self, state: InstanceState[Any], obj: object) -> None:
- state.session_id = self.hash_key
- if state.modified and state._strong_obj is None:
- state._strong_obj = obj
- self.dispatch.after_attach(self, state)
-
- if state.key:
- self.dispatch.detached_to_persistent(self, state)
- else:
- self.dispatch.transient_to_pending(self, state)
-
- def __contains__(self, instance: object) -> bool:
- """Return True if the instance is associated with this session.
-
- The instance may be pending or persistent within the Session for a
- result of True.
-
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- raise exc.UnmappedInstanceError(instance) from err
- return self._contains_state(state)
-
- def __iter__(self) -> Iterator[object]:
- """Iterate over all pending or persistent instances within this
- Session.
-
- """
- return iter(
- list(self._new.values()) + list(self.identity_map.values())
- )
-
- def _contains_state(self, state: InstanceState[Any]) -> bool:
- return state in self._new or self.identity_map.contains_state(state)
-
- def flush(self, objects: Optional[Sequence[Any]] = None) -> None:
- """Flush all the object changes to the database.
-
- Writes out all pending object creations, deletions and modifications
- to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are
- automatically ordered by the Session's unit of work dependency
- solver.
-
- Database operations will be issued in the current transactional
- context and do not affect the state of the transaction, unless an
- error occurs, in which case the entire transaction is rolled back.
- You may flush() as often as you like within a transaction to move
- changes from Python to the database's transaction buffer.
-
- :param objects: Optional; restricts the flush operation to operate
- only on elements that are in the given collection.
-
- This feature is for an extremely narrow set of use cases where
- particular objects may need to be operated upon before the
- full flush() occurs. It is not intended for general use.
-
- """
-
- if self._flushing:
- raise sa_exc.InvalidRequestError("Session is already flushing")
-
- if self._is_clean():
- return
- try:
- self._flushing = True
- self._flush(objects)
- finally:
- self._flushing = False
-
- def _flush_warning(self, method: Any) -> None:
- util.warn(
- "Usage of the '%s' operation is not currently supported "
- "within the execution stage of the flush process. "
- "Results may not be consistent. Consider using alternative "
- "event listeners or connection-level operations instead." % method
- )
-
- def _is_clean(self) -> bool:
- return (
- not self.identity_map.check_modified()
- and not self._deleted
- and not self._new
- )
-
- def _flush(self, objects: Optional[Sequence[object]] = None) -> None:
- dirty = self._dirty_states
- if not dirty and not self._deleted and not self._new:
- self.identity_map._modified.clear()
- return
-
- flush_context = UOWTransaction(self)
-
- if self.dispatch.before_flush:
- self.dispatch.before_flush(self, flush_context, objects)
- # re-establish "dirty states" in case the listeners
- # added
- dirty = self._dirty_states
-
- deleted = set(self._deleted)
- new = set(self._new)
-
- dirty = set(dirty).difference(deleted)
-
- # create the set of all objects we want to operate upon
- if objects:
- # specific list passed in
- objset = set()
- for o in objects:
- try:
- state = attributes.instance_state(o)
-
- except exc.NO_STATE as err:
- raise exc.UnmappedInstanceError(o) from err
- objset.add(state)
- else:
- objset = None
-
- # store objects whose fate has been decided
- processed = set()
-
- # put all saves/updates into the flush context. detect top-level
- # orphans and throw them into deleted.
- if objset:
- proc = new.union(dirty).intersection(objset).difference(deleted)
- else:
- proc = new.union(dirty).difference(deleted)
-
- for state in proc:
- is_orphan = _state_mapper(state)._is_orphan(state)
-
- is_persistent_orphan = is_orphan and state.has_identity
-
- if (
- is_orphan
- and not is_persistent_orphan
- and state._orphaned_outside_of_session
- ):
- self._expunge_states([state])
- else:
- _reg = flush_context.register_object(
- state, isdelete=is_persistent_orphan
- )
- assert _reg, "Failed to add object to the flush context!"
- processed.add(state)
-
- # put all remaining deletes into the flush context.
- if objset:
- proc = deleted.intersection(objset).difference(processed)
- else:
- proc = deleted.difference(processed)
- for state in proc:
- _reg = flush_context.register_object(state, isdelete=True)
- assert _reg, "Failed to add object to the flush context!"
-
- if not flush_context.has_work:
- return
-
- flush_context.transaction = transaction = self._autobegin_t()._begin()
- try:
- self._warn_on_events = True
- try:
- flush_context.execute()
- finally:
- self._warn_on_events = False
-
- self.dispatch.after_flush(self, flush_context)
-
- flush_context.finalize_flush_changes()
-
- if not objects and self.identity_map._modified:
- len_ = len(self.identity_map._modified)
-
- statelib.InstanceState._commit_all_states(
- [
- (state, state.dict)
- for state in self.identity_map._modified
- ],
- instance_dict=self.identity_map,
- )
- util.warn(
- "Attribute history events accumulated on %d "
- "previously clean instances "
- "within inner-flush event handlers have been "
- "reset, and will not result in database updates. "
- "Consider using set_committed_value() within "
- "inner-flush event handlers to avoid this warning." % len_
- )
-
- # useful assertions:
- # if not objects:
- # assert not self.identity_map._modified
- # else:
- # assert self.identity_map._modified == \
- # self.identity_map._modified.difference(objects)
-
- self.dispatch.after_flush_postexec(self, flush_context)
-
- transaction.commit()
-
- except:
- with util.safe_reraise():
- transaction.rollback(_capture_exception=True)
-
- def bulk_save_objects(
- self,
- objects: Iterable[object],
- return_defaults: bool = False,
- update_changed_only: bool = True,
- preserve_order: bool = True,
- ) -> None:
- """Perform a bulk save of the given list of objects.
-
- .. legacy::
-
- This method is a legacy feature as of the 2.0 series of
- SQLAlchemy. For modern bulk INSERT and UPDATE, see
- the sections :ref:`orm_queryguide_bulk_insert` and
- :ref:`orm_queryguide_bulk_update`.
-
- For general INSERT and UPDATE of existing ORM mapped objects,
- prefer standard :term:`unit of work` data management patterns,
- introduced in the :ref:`unified_tutorial` at
- :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0
- now uses :ref:`engine_insertmanyvalues` with modern dialects
- which solves previous issues of bulk INSERT slowness.
-
- :param objects: a sequence of mapped object instances. The mapped
- objects are persisted as is, and are **not** associated with the
- :class:`.Session` afterwards.
-
- For each object, whether the object is sent as an INSERT or an
- UPDATE is dependent on the same rules used by the :class:`.Session`
- in traditional operation; if the object has the
- :attr:`.InstanceState.key`
- attribute set, then the object is assumed to be "detached" and
- will result in an UPDATE. Otherwise, an INSERT is used.
-
- In the case of an UPDATE, statements are grouped based on which
- attributes have changed, and are thus to be the subject of each
- SET clause. If ``update_changed_only`` is False, then all
- attributes present within each object are applied to the UPDATE
- statement, which may help in allowing the statements to be grouped
- together into a larger executemany(), and will also reduce the
- overhead of checking history on attributes.
-
- :param return_defaults: when True, rows that are missing values which
- generate defaults, namely integer primary key defaults and sequences,
- will be inserted **one at a time**, so that the primary key value
- is available. In particular this will allow joined-inheritance
- and other multi-table mappings to insert correctly without the need
- to provide primary key values ahead of time; however,
- :paramref:`.Session.bulk_save_objects.return_defaults` **greatly
- reduces the performance gains** of the method overall. It is strongly
- advised to please use the standard :meth:`_orm.Session.add_all`
- approach.
-
- :param update_changed_only: when True, UPDATE statements are rendered
- based on those attributes in each state that have logged changes.
- When False, all attributes present are rendered into the SET clause
- with the exception of primary key attributes.
-
- :param preserve_order: when True, the order of inserts and updates
- matches exactly the order in which the objects are given. When
- False, common types of objects are grouped into inserts
- and updates, to allow for more batching opportunities.
-
- .. seealso::
-
- :doc:`queryguide/dml`
-
- :meth:`.Session.bulk_insert_mappings`
-
- :meth:`.Session.bulk_update_mappings`
-
- """
-
- obj_states: Iterable[InstanceState[Any]]
-
- obj_states = (attributes.instance_state(obj) for obj in objects)
-
- if not preserve_order:
- # the purpose of this sort is just so that common mappers
- # and persistence states are grouped together, so that groupby
- # will return a single group for a particular type of mapper.
- # it's not trying to be deterministic beyond that.
- obj_states = sorted(
- obj_states,
- key=lambda state: (id(state.mapper), state.key is not None),
- )
-
- def grouping_key(
- state: InstanceState[_O],
- ) -> Tuple[Mapper[_O], bool]:
- return (state.mapper, state.key is not None)
-
- for (mapper, isupdate), states in itertools.groupby(
- obj_states, grouping_key
- ):
- self._bulk_save_mappings(
- mapper,
- states,
- isupdate,
- True,
- return_defaults,
- update_changed_only,
- False,
- )
-
- def bulk_insert_mappings(
- self,
- mapper: Mapper[Any],
- mappings: Iterable[Dict[str, Any]],
- return_defaults: bool = False,
- render_nulls: bool = False,
- ) -> None:
- """Perform a bulk insert of the given list of mapping dictionaries.
-
- .. legacy::
-
- This method is a legacy feature as of the 2.0 series of
- SQLAlchemy. For modern bulk INSERT and UPDATE, see
- the sections :ref:`orm_queryguide_bulk_insert` and
- :ref:`orm_queryguide_bulk_update`. The 2.0 API shares
- implementation details with this method and adds new features
- as well.
-
- :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
- object,
- representing the single kind of object represented within the mapping
- list.
-
- :param mappings: a sequence of dictionaries, each one containing the
- state of the mapped row to be inserted, in terms of the attribute
- names on the mapped class. If the mapping refers to multiple tables,
- such as a joined-inheritance mapping, each dictionary must contain all
- keys to be populated into all tables.
-
- :param return_defaults: when True, the INSERT process will be altered
- to ensure that newly generated primary key values will be fetched.
- The rationale for this parameter is typically to enable
- :ref:`Joined Table Inheritance <joined_inheritance>` mappings to
- be bulk inserted.
-
- .. note:: for backends that don't support RETURNING, the
- :paramref:`_orm.Session.bulk_insert_mappings.return_defaults`
- parameter can significantly decrease performance as INSERT
- statements can no longer be batched. See
- :ref:`engine_insertmanyvalues`
- for background on which backends are affected.
-
- :param render_nulls: When True, a value of ``None`` will result
- in a NULL value being included in the INSERT statement, rather
- than the column being omitted from the INSERT. This allows all
- the rows being INSERTed to have the identical set of columns which
- allows the full set of rows to be batched to the DBAPI. Normally,
- each column-set that contains a different combination of NULL values
- than the previous row must omit a different series of columns from
- the rendered INSERT statement, which means it must be emitted as a
- separate statement. By passing this flag, the full set of rows
- are guaranteed to be batchable into one batch; the cost however is
- that server-side defaults which are invoked by an omitted column will
- be skipped, so care must be taken to ensure that these are not
- necessary.
-
- .. warning::
-
- When this flag is set, **server side default SQL values will
- not be invoked** for those columns that are inserted as NULL;
- the NULL value will be sent explicitly. Care must be taken
- to ensure that no server-side default functions need to be
- invoked for the operation as a whole.
-
- .. seealso::
-
- :doc:`queryguide/dml`
-
- :meth:`.Session.bulk_save_objects`
-
- :meth:`.Session.bulk_update_mappings`
-
- """
- self._bulk_save_mappings(
- mapper,
- mappings,
- False,
- False,
- return_defaults,
- False,
- render_nulls,
- )
-
- def bulk_update_mappings(
- self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]]
- ) -> None:
- """Perform a bulk update of the given list of mapping dictionaries.
-
- .. legacy::
-
- This method is a legacy feature as of the 2.0 series of
- SQLAlchemy. For modern bulk INSERT and UPDATE, see
- the sections :ref:`orm_queryguide_bulk_insert` and
- :ref:`orm_queryguide_bulk_update`. The 2.0 API shares
- implementation details with this method and adds new features
- as well.
-
- :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
- object,
- representing the single kind of object represented within the mapping
- list.
-
- :param mappings: a sequence of dictionaries, each one containing the
- state of the mapped row to be updated, in terms of the attribute names
- on the mapped class. If the mapping refers to multiple tables, such
- as a joined-inheritance mapping, each dictionary may contain keys
- corresponding to all tables. All those keys which are present and
- are not part of the primary key are applied to the SET clause of the
- UPDATE statement; the primary key values, which are required, are
- applied to the WHERE clause.
-
-
- .. seealso::
-
- :doc:`queryguide/dml`
-
- :meth:`.Session.bulk_insert_mappings`
-
- :meth:`.Session.bulk_save_objects`
-
- """
- self._bulk_save_mappings(
- mapper, mappings, True, False, False, False, False
- )
-
- def _bulk_save_mappings(
- self,
- mapper: Mapper[_O],
- mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
- isupdate: bool,
- isstates: bool,
- return_defaults: bool,
- update_changed_only: bool,
- render_nulls: bool,
- ) -> None:
- mapper = _class_to_mapper(mapper)
- self._flushing = True
-
- transaction = self._autobegin_t()._begin()
- try:
- if isupdate:
- bulk_persistence._bulk_update(
- mapper,
- mappings,
- transaction,
- isstates,
- update_changed_only,
- )
- else:
- bulk_persistence._bulk_insert(
- mapper,
- mappings,
- transaction,
- isstates,
- return_defaults,
- render_nulls,
- )
- transaction.commit()
-
- except:
- with util.safe_reraise():
- transaction.rollback(_capture_exception=True)
- finally:
- self._flushing = False
-
- def is_modified(
- self, instance: object, include_collections: bool = True
- ) -> bool:
- r"""Return ``True`` if the given instance has locally
- modified attributes.
-
- This method retrieves the history for each instrumented
- attribute on the instance and performs a comparison of the current
- value to its previously committed value, if any.
-
- It is in effect a more expensive and accurate
- version of checking for the given instance in the
- :attr:`.Session.dirty` collection; a full test for
- each attribute's net "dirty" status is performed.
-
- E.g.::
-
- return session.is_modified(someobject)
-
- A few caveats to this method apply:
-
- * Instances present in the :attr:`.Session.dirty` collection may
- report ``False`` when tested with this method. This is because
- the object may have received change events via attribute mutation,
- thus placing it in :attr:`.Session.dirty`, but ultimately the state
- is the same as that loaded from the database, resulting in no net
- change here.
- * Scalar attributes may not have recorded the previously set
- value when a new value was applied, if the attribute was not loaded,
- or was expired, at the time the new value was received - in these
- cases, the attribute is assumed to have a change, even if there is
- ultimately no net change against its database value. SQLAlchemy in
- most cases does not need the "old" value when a set event occurs, so
- it skips the expense of a SQL call if the old value isn't present,
- based on the assumption that an UPDATE of the scalar value is
- usually needed, and in those few cases where it isn't, is less
- expensive on average than issuing a defensive SELECT.
-
- The "old" value is fetched unconditionally upon set only if the
- attribute container has the ``active_history`` flag set to ``True``.
- This flag is set typically for primary key attributes and scalar
- object references that are not a simple many-to-one. To set this
- flag for any arbitrary mapped column, use the ``active_history``
- argument with :func:`.column_property`.
-
- :param instance: mapped instance to be tested for pending changes.
- :param include_collections: Indicates if multivalued collections
- should be included in the operation. Setting this to ``False`` is a
- way to detect only local-column based properties (i.e. scalar columns
- or many-to-one foreign keys) that would result in an UPDATE for this
- instance upon flush.
-
- """
- state = object_state(instance)
-
- if not state.modified:
- return False
-
- dict_ = state.dict
-
- for attr in state.manager.attributes:
- if (
- not include_collections
- and hasattr(attr.impl, "get_collection")
- ) or not hasattr(attr.impl, "get_history"):
- continue
-
- (added, unchanged, deleted) = attr.impl.get_history(
- state, dict_, passive=PassiveFlag.NO_CHANGE
- )
-
- if added or deleted:
- return True
- else:
- return False
-
- @property
- def is_active(self) -> bool:
- """True if this :class:`.Session` not in "partial rollback" state.
-
- .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins
- a new transaction immediately, so this attribute will be False
- when the :class:`_orm.Session` is first instantiated.
-
- "partial rollback" state typically indicates that the flush process
- of the :class:`_orm.Session` has failed, and that the
- :meth:`_orm.Session.rollback` method must be emitted in order to
- fully roll back the transaction.
-
- If this :class:`_orm.Session` is not in a transaction at all, the
- :class:`_orm.Session` will autobegin when it is first used, so in this
- case :attr:`_orm.Session.is_active` will return True.
-
- Otherwise, if this :class:`_orm.Session` is within a transaction,
- and that transaction has not been rolled back internally, the
- :attr:`_orm.Session.is_active` will also return True.
-
- .. seealso::
-
- :ref:`faq_session_rollback`
-
- :meth:`_orm.Session.in_transaction`
-
- """
- return self._transaction is None or self._transaction.is_active
-
- @property
- def _dirty_states(self) -> Iterable[InstanceState[Any]]:
- """The set of all persistent states considered dirty.
-
- This method returns all states that were modified including
- those that were possibly deleted.
-
- """
- return self.identity_map._dirty_states()
-
- @property
- def dirty(self) -> IdentitySet:
- """The set of all persistent instances considered dirty.
-
- E.g.::
-
- some_mapped_object in session.dirty
-
- Instances are considered dirty when they were modified but not
- deleted.
-
- Note that this 'dirty' calculation is 'optimistic'; most
- attribute-setting or collection modification operations will
- mark an instance as 'dirty' and place it in this set, even if
- there is no net change to the attribute's value. At flush
- time, the value of each attribute is compared to its
- previously saved value, and if there's no net change, no SQL
- operation will occur (this is a more expensive operation so
- it's only done at flush time).
-
- To check if an instance has actionable net changes to its
- attributes, use the :meth:`.Session.is_modified` method.
-
- """
- return IdentitySet(
- [
- state.obj()
- for state in self._dirty_states
- if state not in self._deleted
- ]
- )
-
- @property
- def deleted(self) -> IdentitySet:
- "The set of all instances marked as 'deleted' within this ``Session``"
-
- return util.IdentitySet(list(self._deleted.values()))
-
- @property
- def new(self) -> IdentitySet:
- "The set of all instances marked as 'new' within this ``Session``."
-
- return util.IdentitySet(list(self._new.values()))
-
-
-_S = TypeVar("_S", bound="Session")
-
-
-class sessionmaker(_SessionClassMethods, Generic[_S]):
- """A configurable :class:`.Session` factory.
-
- The :class:`.sessionmaker` factory generates new
- :class:`.Session` objects when called, creating them given
- the configurational arguments established here.
-
- e.g.::
-
- from sqlalchemy import create_engine
- from sqlalchemy.orm import sessionmaker
-
- # an Engine, which the Session will use for connection
- # resources
- engine = create_engine('postgresql+psycopg2://scott:tiger@localhost/')
-
- Session = sessionmaker(engine)
-
- with Session() as session:
- session.add(some_object)
- session.add(some_other_object)
- session.commit()
-
- Context manager use is optional; otherwise, the returned
- :class:`_orm.Session` object may be closed explicitly via the
- :meth:`_orm.Session.close` method. Using a
- ``try:/finally:`` block is optional, however will ensure that the close
- takes place even if there are database errors::
-
- session = Session()
- try:
- session.add(some_object)
- session.add(some_other_object)
- session.commit()
- finally:
- session.close()
-
- :class:`.sessionmaker` acts as a factory for :class:`_orm.Session`
- objects in the same way as an :class:`_engine.Engine` acts as a factory
- for :class:`_engine.Connection` objects. In this way it also includes
- a :meth:`_orm.sessionmaker.begin` method, that provides a context
- manager which both begins and commits a transaction, as well as closes
- out the :class:`_orm.Session` when complete, rolling back the transaction
- if any errors occur::
-
- Session = sessionmaker(engine)
-
- with Session.begin() as session:
- session.add(some_object)
- session.add(some_other_object)
- # commits transaction, closes session
-
- .. versionadded:: 1.4
-
- When calling upon :class:`_orm.sessionmaker` to construct a
- :class:`_orm.Session`, keyword arguments may also be passed to the
- method; these arguments will override that of the globally configured
- parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain
- :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead
- bound to a specific :class:`_engine.Connection` procured from that engine::
-
- Session = sessionmaker(engine)
-
- # bind an individual session to a connection
-
- with engine.connect() as connection:
- with Session(bind=connection) as session:
- # work with session
-
- The class also includes a method :meth:`_orm.sessionmaker.configure`, which
- can be used to specify additional keyword arguments to the factory, which
- will take effect for subsequent :class:`.Session` objects generated. This
- is usually used to associate one or more :class:`_engine.Engine` objects
- with an existing
- :class:`.sessionmaker` factory before it is first used::
-
- # application starts, sessionmaker does not have
- # an engine bound yet
- Session = sessionmaker()
-
- # ... later, when an engine URL is read from a configuration
- # file or other events allow the engine to be created
- engine = create_engine('sqlite:///foo.db')
- Session.configure(bind=engine)
-
- sess = Session()
- # work with session
-
- .. seealso::
-
- :ref:`session_getting` - introductory text on creating
- sessions using :class:`.sessionmaker`.
-
- """
-
- class_: Type[_S]
-
- @overload
- def __init__(
- self,
- bind: Optional[_SessionBind] = ...,
- *,
- class_: Type[_S],
- autoflush: bool = ...,
- expire_on_commit: bool = ...,
- info: Optional[_InfoType] = ...,
- **kw: Any,
- ): ...
-
- @overload
- def __init__(
- self: "sessionmaker[Session]",
- bind: Optional[_SessionBind] = ...,
- *,
- autoflush: bool = ...,
- expire_on_commit: bool = ...,
- info: Optional[_InfoType] = ...,
- **kw: Any,
- ): ...
-
- def __init__(
- self,
- bind: Optional[_SessionBind] = None,
- *,
- class_: Type[_S] = Session, # type: ignore
- autoflush: bool = True,
- expire_on_commit: bool = True,
- info: Optional[_InfoType] = None,
- **kw: Any,
- ):
- r"""Construct a new :class:`.sessionmaker`.
-
- All arguments here except for ``class_`` correspond to arguments
- accepted by :class:`.Session` directly. See the
- :meth:`.Session.__init__` docstring for more details on parameters.
-
- :param bind: a :class:`_engine.Engine` or other :class:`.Connectable`
- with
- which newly created :class:`.Session` objects will be associated.
- :param class\_: class to use in order to create new :class:`.Session`
- objects. Defaults to :class:`.Session`.
- :param autoflush: The autoflush setting to use with newly created
- :class:`.Session` objects.
-
- .. seealso::
-
- :ref:`session_flushing` - additional background on autoflush
-
- :param expire_on_commit=True: the
- :paramref:`_orm.Session.expire_on_commit` setting to use
- with newly created :class:`.Session` objects.
-
- :param info: optional dictionary of information that will be available
- via :attr:`.Session.info`. Note this dictionary is *updated*, not
- replaced, when the ``info`` parameter is specified to the specific
- :class:`.Session` construction operation.
-
- :param \**kw: all other keyword arguments are passed to the
- constructor of newly created :class:`.Session` objects.
-
- """
- kw["bind"] = bind
- kw["autoflush"] = autoflush
- kw["expire_on_commit"] = expire_on_commit
- if info is not None:
- kw["info"] = info
- self.kw = kw
- # make our own subclass of the given class, so that
- # events can be associated with it specifically.
- self.class_ = type(class_.__name__, (class_,), {})
-
- def begin(self) -> contextlib.AbstractContextManager[_S]:
- """Produce a context manager that both provides a new
- :class:`_orm.Session` as well as a transaction that commits.
-
-
- e.g.::
-
- Session = sessionmaker(some_engine)
-
- with Session.begin() as session:
- session.add(some_object)
-
- # commits transaction, closes session
-
- .. versionadded:: 1.4
-
-
- """
-
- session = self()
- return session._maker_context_manager()
-
- def __call__(self, **local_kw: Any) -> _S:
- """Produce a new :class:`.Session` object using the configuration
- established in this :class:`.sessionmaker`.
-
- In Python, the ``__call__`` method is invoked on an object when
- it is "called" in the same way as a function::
-
- Session = sessionmaker(some_engine)
- session = Session() # invokes sessionmaker.__call__()
-
- """
- for k, v in self.kw.items():
- if k == "info" and "info" in local_kw:
- d = v.copy()
- d.update(local_kw["info"])
- local_kw["info"] = d
- else:
- local_kw.setdefault(k, v)
- return self.class_(**local_kw)
-
- def configure(self, **new_kw: Any) -> None:
- """(Re)configure the arguments for this sessionmaker.
-
- e.g.::
-
- Session = sessionmaker()
-
- Session.configure(bind=create_engine('sqlite://'))
- """
- self.kw.update(new_kw)
-
- def __repr__(self) -> str:
- return "%s(class_=%r, %s)" % (
- self.__class__.__name__,
- self.class_.__name__,
- ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()),
- )
-
-
-def close_all_sessions() -> None:
- """Close all sessions in memory.
-
- This function consults a global registry of all :class:`.Session` objects
- and calls :meth:`.Session.close` on them, which resets them to a clean
- state.
-
- This function is not for general use but may be useful for test suites
- within the teardown scheme.
-
- .. versionadded:: 1.3
-
- """
-
- for sess in _sessions.values():
- sess.close()
-
-
-def make_transient(instance: object) -> None:
- """Alter the state of the given instance so that it is :term:`transient`.
-
- .. note::
-
- :func:`.make_transient` is a special-case function for
- advanced use cases only.
-
- The given mapped instance is assumed to be in the :term:`persistent` or
- :term:`detached` state. The function will remove its association with any
- :class:`.Session` as well as its :attr:`.InstanceState.identity`. The
- effect is that the object will behave as though it were newly constructed,
- except retaining any attribute / collection values that were loaded at the
- time of the call. The :attr:`.InstanceState.deleted` flag is also reset
- if this object had been deleted as a result of using
- :meth:`.Session.delete`.
-
- .. warning::
-
- :func:`.make_transient` does **not** "unexpire" or otherwise eagerly
- load ORM-mapped attributes that are not currently loaded at the time
- the function is called. This includes attributes which:
-
- * were expired via :meth:`.Session.expire`
-
- * were expired as the natural effect of committing a session
- transaction, e.g. :meth:`.Session.commit`
-
- * are normally :term:`lazy loaded` but are not currently loaded
-
- * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are
- not yet loaded
-
- * were not present in the query which loaded this object, such as that
- which is common in joined table inheritance and other scenarios.
-
- After :func:`.make_transient` is called, unloaded attributes such
- as those above will normally resolve to the value ``None`` when
- accessed, or an empty collection for a collection-oriented attribute.
- As the object is transient and un-associated with any database
- identity, it will no longer retrieve these values.
-
- .. seealso::
-
- :func:`.make_transient_to_detached`
-
- """
- state = attributes.instance_state(instance)
- s = _state_session(state)
- if s:
- s._expunge_states([state])
-
- # remove expired state
- state.expired_attributes.clear()
-
- # remove deferred callables
- if state.callables:
- del state.callables
-
- if state.key:
- del state.key
- if state._deleted:
- del state._deleted
-
-
-def make_transient_to_detached(instance: object) -> None:
- """Make the given transient instance :term:`detached`.
-
- .. note::
-
- :func:`.make_transient_to_detached` is a special-case function for
- advanced use cases only.
-
- All attribute history on the given instance
- will be reset as though the instance were freshly loaded
- from a query. Missing attributes will be marked as expired.
- The primary key attributes of the object, which are required, will be made
- into the "key" of the instance.
-
- The object can then be added to a session, or merged
- possibly with the load=False flag, at which point it will look
- as if it were loaded that way, without emitting SQL.
-
- This is a special use case function that differs from a normal
- call to :meth:`.Session.merge` in that a given persistent state
- can be manufactured without any SQL calls.
-
- .. seealso::
-
- :func:`.make_transient`
-
- :meth:`.Session.enable_relationship_loading`
-
- """
- state = attributes.instance_state(instance)
- if state.session_id or state.key:
- raise sa_exc.InvalidRequestError("Given object must be transient")
- state.key = state.mapper._identity_key_from_state(state)
- if state._deleted:
- del state._deleted
- state._commit_all(state.dict)
- state._expire_attributes(state.dict, state.unloaded)
-
-
-def object_session(instance: object) -> Optional[Session]:
- """Return the :class:`.Session` to which the given instance belongs.
-
- This is essentially the same as the :attr:`.InstanceState.session`
- accessor. See that attribute for details.
-
- """
-
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- raise exc.UnmappedInstanceError(instance) from err
- else:
- return _state_session(state)
-
-
-_new_sessionid = util.counter()
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)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/state_changes.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/state_changes.py
deleted file mode 100644
index 56963c6..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/state_changes.py
+++ /dev/null
@@ -1,198 +0,0 @@
-# orm/state_changes.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
-
-"""State tracking utilities used by :class:`_orm.Session`.
-
-"""
-
-from __future__ import annotations
-
-import contextlib
-from enum import Enum
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Iterator
-from typing import NoReturn
-from typing import Optional
-from typing import Tuple
-from typing import TypeVar
-from typing import Union
-
-from .. import exc as sa_exc
-from .. import util
-from ..util.typing import Literal
-
-_F = TypeVar("_F", bound=Callable[..., Any])
-
-
-class _StateChangeState(Enum):
- pass
-
-
-class _StateChangeStates(_StateChangeState):
- ANY = 1
- NO_CHANGE = 2
- CHANGE_IN_PROGRESS = 3
-
-
-class _StateChange:
- """Supplies state assertion decorators.
-
- The current use case is for the :class:`_orm.SessionTransaction` class. The
- :class:`_StateChange` class itself is agnostic of the
- :class:`_orm.SessionTransaction` class so could in theory be generalized
- for other systems as well.
-
- """
-
- _next_state: _StateChangeState = _StateChangeStates.ANY
- _state: _StateChangeState = _StateChangeStates.NO_CHANGE
- _current_fn: Optional[Callable[..., Any]] = None
-
- def _raise_for_prerequisite_state(
- self, operation_name: str, state: _StateChangeState
- ) -> NoReturn:
- raise sa_exc.IllegalStateChangeError(
- f"Can't run operation '{operation_name}()' when Session "
- f"is in state {state!r}",
- code="isce",
- )
-
- @classmethod
- def declare_states(
- cls,
- prerequisite_states: Union[
- Literal[_StateChangeStates.ANY], Tuple[_StateChangeState, ...]
- ],
- moves_to: _StateChangeState,
- ) -> Callable[[_F], _F]:
- """Method decorator declaring valid states.
-
- :param prerequisite_states: sequence of acceptable prerequisite
- states. Can be the single constant _State.ANY to indicate no
- prerequisite state
-
- :param moves_to: the expected state at the end of the method, assuming
- no exceptions raised. Can be the constant _State.NO_CHANGE to
- indicate state should not change at the end of the method.
-
- """
- assert prerequisite_states, "no prequisite states sent"
- has_prerequisite_states = (
- prerequisite_states is not _StateChangeStates.ANY
- )
-
- prerequisite_state_collection = cast(
- "Tuple[_StateChangeState, ...]", prerequisite_states
- )
- expect_state_change = moves_to is not _StateChangeStates.NO_CHANGE
-
- @util.decorator
- def _go(fn: _F, self: Any, *arg: Any, **kw: Any) -> Any:
- current_state = self._state
-
- if (
- has_prerequisite_states
- and current_state not in prerequisite_state_collection
- ):
- self._raise_for_prerequisite_state(fn.__name__, current_state)
-
- next_state = self._next_state
- existing_fn = self._current_fn
- expect_state = moves_to if expect_state_change else current_state
-
- if (
- # destination states are restricted
- next_state is not _StateChangeStates.ANY
- # method seeks to change state
- and expect_state_change
- # destination state incorrect
- and next_state is not expect_state
- ):
- if existing_fn and next_state in (
- _StateChangeStates.NO_CHANGE,
- _StateChangeStates.CHANGE_IN_PROGRESS,
- ):
- raise sa_exc.IllegalStateChangeError(
- f"Method '{fn.__name__}()' can't be called here; "
- f"method '{existing_fn.__name__}()' is already "
- f"in progress and this would cause an unexpected "
- f"state change to {moves_to!r}",
- code="isce",
- )
- else:
- raise sa_exc.IllegalStateChangeError(
- f"Cant run operation '{fn.__name__}()' here; "
- f"will move to state {moves_to!r} where we are "
- f"expecting {next_state!r}",
- code="isce",
- )
-
- self._current_fn = fn
- self._next_state = _StateChangeStates.CHANGE_IN_PROGRESS
- try:
- ret_value = fn(self, *arg, **kw)
- except:
- raise
- else:
- if self._state is expect_state:
- return ret_value
-
- if self._state is current_state:
- raise sa_exc.IllegalStateChangeError(
- f"Method '{fn.__name__}()' failed to "
- "change state "
- f"to {moves_to!r} as expected",
- code="isce",
- )
- elif existing_fn:
- raise sa_exc.IllegalStateChangeError(
- f"While method '{existing_fn.__name__}()' was "
- "running, "
- f"method '{fn.__name__}()' caused an "
- "unexpected "
- f"state change to {self._state!r}",
- code="isce",
- )
- else:
- raise sa_exc.IllegalStateChangeError(
- f"Method '{fn.__name__}()' caused an unexpected "
- f"state change to {self._state!r}",
- code="isce",
- )
-
- finally:
- self._next_state = next_state
- self._current_fn = existing_fn
-
- return _go
-
- @contextlib.contextmanager
- def _expect_state(self, expected: _StateChangeState) -> Iterator[Any]:
- """called within a method that changes states.
-
- method must also use the ``@declare_states()`` decorator.
-
- """
- assert self._next_state is _StateChangeStates.CHANGE_IN_PROGRESS, (
- "Unexpected call to _expect_state outside of "
- "state-changing method"
- )
-
- self._next_state = expected
- try:
- yield
- except:
- raise
- else:
- if self._state is not expected:
- raise sa_exc.IllegalStateChangeError(
- f"Unexpected state change to {self._state!r}", code="isce"
- )
- finally:
- self._next_state = _StateChangeStates.CHANGE_IN_PROGRESS
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/strategies.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/strategies.py
deleted file mode 100644
index 20c3b9c..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/strategies.py
+++ /dev/null
@@ -1,3344 +0,0 @@
-# orm/strategies.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
-# mypy: ignore-errors
-
-
-"""sqlalchemy.orm.interfaces.LoaderStrategy
- implementations, and related MapperOptions."""
-
-from __future__ import annotations
-
-import collections
-import itertools
-from typing import Any
-from typing import Dict
-from typing import Tuple
-from typing import TYPE_CHECKING
-
-from . import attributes
-from . import exc as orm_exc
-from . import interfaces
-from . import loading
-from . import path_registry
-from . import properties
-from . import query
-from . import relationships
-from . import unitofwork
-from . import util as orm_util
-from .base import _DEFER_FOR_STATE
-from .base import _RAISE_FOR_STATE
-from .base import _SET_DEFERRED_EXPIRED
-from .base import ATTR_WAS_SET
-from .base import LoaderCallableStatus
-from .base import PASSIVE_OFF
-from .base import PassiveFlag
-from .context import _column_descriptions
-from .context import ORMCompileState
-from .context import ORMSelectCompileState
-from .context import QueryContext
-from .interfaces import LoaderStrategy
-from .interfaces import StrategizedProperty
-from .session import _state_session
-from .state import InstanceState
-from .strategy_options import Load
-from .util import _none_set
-from .util import AliasedClass
-from .. import event
-from .. import exc as sa_exc
-from .. import inspect
-from .. import log
-from .. import sql
-from .. import util
-from ..sql import util as sql_util
-from ..sql import visitors
-from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
-from ..sql.selectable import Select
-
-if TYPE_CHECKING:
- from .relationships import RelationshipProperty
- from ..sql.elements import ColumnElement
-
-
-def _register_attribute(
- prop,
- mapper,
- useobject,
- compare_function=None,
- typecallable=None,
- callable_=None,
- proxy_property=None,
- active_history=False,
- impl_class=None,
- **kw,
-):
- listen_hooks = []
-
- uselist = useobject and prop.uselist
-
- if useobject and prop.single_parent:
- listen_hooks.append(single_parent_validator)
-
- if prop.key in prop.parent.validators:
- fn, opts = prop.parent.validators[prop.key]
- listen_hooks.append(
- lambda desc, prop: orm_util._validator_events(
- desc, prop.key, fn, **opts
- )
- )
-
- if useobject:
- listen_hooks.append(unitofwork.track_cascade_events)
-
- # need to assemble backref listeners
- # after the singleparentvalidator, mapper validator
- if useobject:
- backref = prop.back_populates
- if backref and prop._effective_sync_backref:
- listen_hooks.append(
- lambda desc, prop: attributes.backref_listeners(
- desc, backref, uselist
- )
- )
-
- # a single MapperProperty is shared down a class inheritance
- # hierarchy, so we set up attribute instrumentation and backref event
- # for each mapper down the hierarchy.
-
- # typically, "mapper" is the same as prop.parent, due to the way
- # the configure_mappers() process runs, however this is not strongly
- # enforced, and in the case of a second configure_mappers() run the
- # mapper here might not be prop.parent; also, a subclass mapper may
- # be called here before a superclass mapper. That is, can't depend
- # on mappers not already being set up so we have to check each one.
-
- for m in mapper.self_and_descendants:
- if prop is m._props.get(
- prop.key
- ) and not m.class_manager._attr_has_impl(prop.key):
- desc = attributes.register_attribute_impl(
- m.class_,
- prop.key,
- parent_token=prop,
- uselist=uselist,
- compare_function=compare_function,
- useobject=useobject,
- trackparent=useobject
- and (
- prop.single_parent
- or prop.direction is interfaces.ONETOMANY
- ),
- typecallable=typecallable,
- callable_=callable_,
- active_history=active_history,
- impl_class=impl_class,
- send_modified_events=not useobject or not prop.viewonly,
- doc=prop.doc,
- **kw,
- )
-
- for hook in listen_hooks:
- hook(desc, prop)
-
-
-@properties.ColumnProperty.strategy_for(instrument=False, deferred=False)
-class UninstrumentedColumnLoader(LoaderStrategy):
- """Represent a non-instrumented MapperProperty.
-
- The polymorphic_on argument of mapper() often results in this,
- if the argument is against the with_polymorphic selectable.
-
- """
-
- __slots__ = ("columns",)
-
- def __init__(self, parent, strategy_key):
- super().__init__(parent, strategy_key)
- self.columns = self.parent_property.columns
-
- def setup_query(
- self,
- compile_state,
- query_entity,
- path,
- loadopt,
- adapter,
- column_collection=None,
- **kwargs,
- ):
- for c in self.columns:
- if adapter:
- c = adapter.columns[c]
- compile_state._append_dedupe_col_collection(c, column_collection)
-
- def create_row_processor(
- self,
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- ):
- pass
-
-
-@log.class_logger
-@properties.ColumnProperty.strategy_for(instrument=True, deferred=False)
-class ColumnLoader(LoaderStrategy):
- """Provide loading behavior for a :class:`.ColumnProperty`."""
-
- __slots__ = "columns", "is_composite"
-
- def __init__(self, parent, strategy_key):
- super().__init__(parent, strategy_key)
- self.columns = self.parent_property.columns
- self.is_composite = hasattr(self.parent_property, "composite_class")
-
- def setup_query(
- self,
- compile_state,
- query_entity,
- path,
- loadopt,
- adapter,
- column_collection,
- memoized_populators,
- check_for_adapt=False,
- **kwargs,
- ):
- for c in self.columns:
- if adapter:
- if check_for_adapt:
- c = adapter.adapt_check_present(c)
- if c is None:
- return
- else:
- c = adapter.columns[c]
-
- compile_state._append_dedupe_col_collection(c, column_collection)
-
- fetch = self.columns[0]
- if adapter:
- fetch = adapter.columns[fetch]
- if fetch is None:
- # None happens here only for dml bulk_persistence cases
- # when context.DMLReturningColFilter is used
- return
-
- memoized_populators[self.parent_property] = fetch
-
- def init_class_attribute(self, mapper):
- self.is_class_level = True
- coltype = self.columns[0].type
- # TODO: check all columns ? check for foreign key as well?
- active_history = (
- self.parent_property.active_history
- or self.columns[0].primary_key
- or (
- mapper.version_id_col is not None
- and mapper._columntoproperty.get(mapper.version_id_col, None)
- is self.parent_property
- )
- )
-
- _register_attribute(
- self.parent_property,
- mapper,
- useobject=False,
- compare_function=coltype.compare_values,
- active_history=active_history,
- )
-
- def create_row_processor(
- self,
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- ):
- # look through list of columns represented here
- # to see which, if any, is present in the row.
-
- for col in self.columns:
- if adapter:
- col = adapter.columns[col]
- getter = result._getter(col, False)
- if getter:
- populators["quick"].append((self.key, getter))
- break
- else:
- populators["expire"].append((self.key, True))
-
-
-@log.class_logger
-@properties.ColumnProperty.strategy_for(query_expression=True)
-class ExpressionColumnLoader(ColumnLoader):
- def __init__(self, parent, strategy_key):
- super().__init__(parent, strategy_key)
-
- # compare to the "default" expression that is mapped in
- # the column. If it's sql.null, we don't need to render
- # unless an expr is passed in the options.
- null = sql.null().label(None)
- self._have_default_expression = any(
- not c.compare(null) for c in self.parent_property.columns
- )
-
- def setup_query(
- self,
- compile_state,
- query_entity,
- path,
- loadopt,
- adapter,
- column_collection,
- memoized_populators,
- **kwargs,
- ):
- columns = None
- if loadopt and loadopt._extra_criteria:
- columns = loadopt._extra_criteria
-
- elif self._have_default_expression:
- columns = self.parent_property.columns
-
- if columns is None:
- return
-
- for c in columns:
- if adapter:
- c = adapter.columns[c]
- compile_state._append_dedupe_col_collection(c, column_collection)
-
- fetch = columns[0]
- if adapter:
- fetch = adapter.columns[fetch]
- if fetch is None:
- # None is not expected to be the result of any
- # adapter implementation here, however there may be theoretical
- # usages of returning() with context.DMLReturningColFilter
- return
-
- memoized_populators[self.parent_property] = fetch
-
- def create_row_processor(
- self,
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- ):
- # look through list of columns represented here
- # to see which, if any, is present in the row.
- if loadopt and loadopt._extra_criteria:
- columns = loadopt._extra_criteria
-
- for col in columns:
- if adapter:
- col = adapter.columns[col]
- getter = result._getter(col, False)
- if getter:
- populators["quick"].append((self.key, getter))
- break
- else:
- populators["expire"].append((self.key, True))
-
- def init_class_attribute(self, mapper):
- self.is_class_level = True
-
- _register_attribute(
- self.parent_property,
- mapper,
- useobject=False,
- compare_function=self.columns[0].type.compare_values,
- accepts_scalar_loader=False,
- )
-
-
-@log.class_logger
-@properties.ColumnProperty.strategy_for(deferred=True, instrument=True)
-@properties.ColumnProperty.strategy_for(
- deferred=True, instrument=True, raiseload=True
-)
-@properties.ColumnProperty.strategy_for(do_nothing=True)
-class DeferredColumnLoader(LoaderStrategy):
- """Provide loading behavior for a deferred :class:`.ColumnProperty`."""
-
- __slots__ = "columns", "group", "raiseload"
-
- def __init__(self, parent, strategy_key):
- super().__init__(parent, strategy_key)
- if hasattr(self.parent_property, "composite_class"):
- raise NotImplementedError(
- "Deferred loading for composite types not implemented yet"
- )
- self.raiseload = self.strategy_opts.get("raiseload", False)
- self.columns = self.parent_property.columns
- self.group = self.parent_property.group
-
- def create_row_processor(
- self,
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- ):
- # for a DeferredColumnLoader, this method is only used during a
- # "row processor only" query; see test_deferred.py ->
- # tests with "rowproc_only" in their name. As of the 1.0 series,
- # loading._instance_processor doesn't use a "row processing" function
- # to populate columns, instead it uses data in the "populators"
- # dictionary. Normally, the DeferredColumnLoader.setup_query()
- # sets up that data in the "memoized_populators" dictionary
- # and "create_row_processor()" here is never invoked.
-
- if (
- context.refresh_state
- and context.query._compile_options._only_load_props
- and self.key in context.query._compile_options._only_load_props
- ):
- self.parent_property._get_strategy(
- (("deferred", False), ("instrument", True))
- ).create_row_processor(
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- )
-
- elif not self.is_class_level:
- if self.raiseload:
- set_deferred_for_local_state = (
- self.parent_property._raise_column_loader
- )
- else:
- set_deferred_for_local_state = (
- self.parent_property._deferred_column_loader
- )
- populators["new"].append((self.key, set_deferred_for_local_state))
- else:
- populators["expire"].append((self.key, False))
-
- def init_class_attribute(self, mapper):
- self.is_class_level = True
-
- _register_attribute(
- self.parent_property,
- mapper,
- useobject=False,
- compare_function=self.columns[0].type.compare_values,
- callable_=self._load_for_state,
- load_on_unexpire=False,
- )
-
- def setup_query(
- self,
- compile_state,
- query_entity,
- path,
- loadopt,
- adapter,
- column_collection,
- memoized_populators,
- only_load_props=None,
- **kw,
- ):
- if (
- (
- compile_state.compile_options._render_for_subquery
- and self.parent_property._renders_in_subqueries
- )
- or (
- loadopt
- and set(self.columns).intersection(
- self.parent._should_undefer_in_wildcard
- )
- )
- or (
- loadopt
- and self.group
- and loadopt.local_opts.get(
- "undefer_group_%s" % self.group, False
- )
- )
- or (only_load_props and self.key in only_load_props)
- ):
- self.parent_property._get_strategy(
- (("deferred", False), ("instrument", True))
- ).setup_query(
- compile_state,
- query_entity,
- path,
- loadopt,
- adapter,
- column_collection,
- memoized_populators,
- **kw,
- )
- elif self.is_class_level:
- memoized_populators[self.parent_property] = _SET_DEFERRED_EXPIRED
- elif not self.raiseload:
- memoized_populators[self.parent_property] = _DEFER_FOR_STATE
- else:
- memoized_populators[self.parent_property] = _RAISE_FOR_STATE
-
- def _load_for_state(self, state, passive):
- if not state.key:
- return LoaderCallableStatus.ATTR_EMPTY
-
- if not passive & PassiveFlag.SQL_OK:
- return LoaderCallableStatus.PASSIVE_NO_RESULT
-
- localparent = state.manager.mapper
-
- if self.group:
- toload = [
- p.key
- for p in localparent.iterate_properties
- if isinstance(p, StrategizedProperty)
- and isinstance(p.strategy, DeferredColumnLoader)
- and p.group == self.group
- ]
- else:
- toload = [self.key]
-
- # narrow the keys down to just those which have no history
- group = [k for k in toload if k in state.unmodified]
-
- session = _state_session(state)
- if session is None:
- raise orm_exc.DetachedInstanceError(
- "Parent instance %s is not bound to a Session; "
- "deferred load operation of attribute '%s' cannot proceed"
- % (orm_util.state_str(state), self.key)
- )
-
- if self.raiseload:
- self._invoke_raise_load(state, passive, "raise")
-
- loading.load_scalar_attributes(
- state.mapper, state, set(group), PASSIVE_OFF
- )
-
- return LoaderCallableStatus.ATTR_WAS_SET
-
- def _invoke_raise_load(self, state, passive, lazy):
- raise sa_exc.InvalidRequestError(
- "'%s' is not available due to raiseload=True" % (self,)
- )
-
-
-class LoadDeferredColumns:
- """serializable loader object used by DeferredColumnLoader"""
-
- def __init__(self, key: str, raiseload: bool = False):
- self.key = key
- self.raiseload = raiseload
-
- def __call__(self, state, passive=attributes.PASSIVE_OFF):
- key = self.key
-
- localparent = state.manager.mapper
- prop = localparent._props[key]
- if self.raiseload:
- strategy_key = (
- ("deferred", True),
- ("instrument", True),
- ("raiseload", True),
- )
- else:
- strategy_key = (("deferred", True), ("instrument", True))
- strategy = prop._get_strategy(strategy_key)
- return strategy._load_for_state(state, passive)
-
-
-class AbstractRelationshipLoader(LoaderStrategy):
- """LoaderStratgies which deal with related objects."""
-
- __slots__ = "mapper", "target", "uselist", "entity"
-
- def __init__(self, parent, strategy_key):
- super().__init__(parent, strategy_key)
- self.mapper = self.parent_property.mapper
- self.entity = self.parent_property.entity
- self.target = self.parent_property.target
- self.uselist = self.parent_property.uselist
-
- def _immediateload_create_row_processor(
- self,
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- ):
- return self.parent_property._get_strategy(
- (("lazy", "immediate"),)
- ).create_row_processor(
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- )
-
-
-@log.class_logger
-@relationships.RelationshipProperty.strategy_for(do_nothing=True)
-class DoNothingLoader(LoaderStrategy):
- """Relationship loader that makes no change to the object's state.
-
- Compared to NoLoader, this loader does not initialize the
- collection/attribute to empty/none; the usual default LazyLoader will
- take effect.
-
- """
-
-
-@log.class_logger
-@relationships.RelationshipProperty.strategy_for(lazy="noload")
-@relationships.RelationshipProperty.strategy_for(lazy=None)
-class NoLoader(AbstractRelationshipLoader):
- """Provide loading behavior for a :class:`.Relationship`
- with "lazy=None".
-
- """
-
- __slots__ = ()
-
- def init_class_attribute(self, mapper):
- self.is_class_level = True
-
- _register_attribute(
- self.parent_property,
- mapper,
- useobject=True,
- typecallable=self.parent_property.collection_class,
- )
-
- def create_row_processor(
- self,
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- ):
- def invoke_no_load(state, dict_, row):
- if self.uselist:
- attributes.init_state_collection(state, dict_, self.key)
- else:
- dict_[self.key] = None
-
- populators["new"].append((self.key, invoke_no_load))
-
-
-@log.class_logger
-@relationships.RelationshipProperty.strategy_for(lazy=True)
-@relationships.RelationshipProperty.strategy_for(lazy="select")
-@relationships.RelationshipProperty.strategy_for(lazy="raise")
-@relationships.RelationshipProperty.strategy_for(lazy="raise_on_sql")
-@relationships.RelationshipProperty.strategy_for(lazy="baked_select")
-class LazyLoader(
- AbstractRelationshipLoader, util.MemoizedSlots, log.Identified
-):
- """Provide loading behavior for a :class:`.Relationship`
- with "lazy=True", that is loads when first accessed.
-
- """
-
- __slots__ = (
- "_lazywhere",
- "_rev_lazywhere",
- "_lazyload_reverse_option",
- "_order_by",
- "use_get",
- "is_aliased_class",
- "_bind_to_col",
- "_equated_columns",
- "_rev_bind_to_col",
- "_rev_equated_columns",
- "_simple_lazy_clause",
- "_raise_always",
- "_raise_on_sql",
- )
-
- _lazywhere: ColumnElement[bool]
- _bind_to_col: Dict[str, ColumnElement[Any]]
- _rev_lazywhere: ColumnElement[bool]
- _rev_bind_to_col: Dict[str, ColumnElement[Any]]
-
- parent_property: RelationshipProperty[Any]
-
- def __init__(
- self, parent: RelationshipProperty[Any], strategy_key: Tuple[Any, ...]
- ):
- super().__init__(parent, strategy_key)
- self._raise_always = self.strategy_opts["lazy"] == "raise"
- self._raise_on_sql = self.strategy_opts["lazy"] == "raise_on_sql"
-
- self.is_aliased_class = inspect(self.entity).is_aliased_class
-
- join_condition = self.parent_property._join_condition
- (
- self._lazywhere,
- self._bind_to_col,
- self._equated_columns,
- ) = join_condition.create_lazy_clause()
-
- (
- self._rev_lazywhere,
- self._rev_bind_to_col,
- self._rev_equated_columns,
- ) = join_condition.create_lazy_clause(reverse_direction=True)
-
- if self.parent_property.order_by:
- self._order_by = [
- sql_util._deep_annotate(elem, {"_orm_adapt": True})
- for elem in util.to_list(self.parent_property.order_by)
- ]
- else:
- self._order_by = None
-
- self.logger.info("%s lazy loading clause %s", self, self._lazywhere)
-
- # determine if our "lazywhere" clause is the same as the mapper's
- # get() clause. then we can just use mapper.get()
- #
- # TODO: the "not self.uselist" can be taken out entirely; a m2o
- # load that populates for a list (very unusual, but is possible with
- # the API) can still set for "None" and the attribute system will
- # populate as an empty list.
- self.use_get = (
- not self.is_aliased_class
- and not self.uselist
- and self.entity._get_clause[0].compare(
- self._lazywhere,
- use_proxies=True,
- compare_keys=False,
- equivalents=self.mapper._equivalent_columns,
- )
- )
-
- if self.use_get:
- for col in list(self._equated_columns):
- if col in self.mapper._equivalent_columns:
- for c in self.mapper._equivalent_columns[col]:
- self._equated_columns[c] = self._equated_columns[col]
-
- self.logger.info(
- "%s will use Session.get() to optimize instance loads", self
- )
-
- def init_class_attribute(self, mapper):
- self.is_class_level = True
-
- _legacy_inactive_history_style = (
- self.parent_property._legacy_inactive_history_style
- )
-
- if self.parent_property.active_history:
- active_history = True
- _deferred_history = False
-
- elif (
- self.parent_property.direction is not interfaces.MANYTOONE
- or not self.use_get
- ):
- if _legacy_inactive_history_style:
- active_history = True
- _deferred_history = False
- else:
- active_history = False
- _deferred_history = True
- else:
- active_history = _deferred_history = False
-
- _register_attribute(
- self.parent_property,
- mapper,
- useobject=True,
- callable_=self._load_for_state,
- typecallable=self.parent_property.collection_class,
- active_history=active_history,
- _deferred_history=_deferred_history,
- )
-
- def _memoized_attr__simple_lazy_clause(self):
- lazywhere = sql_util._deep_annotate(
- self._lazywhere, {"_orm_adapt": True}
- )
-
- criterion, bind_to_col = (lazywhere, self._bind_to_col)
-
- params = []
-
- def visit_bindparam(bindparam):
- bindparam.unique = False
-
- visitors.traverse(criterion, {}, {"bindparam": visit_bindparam})
-
- def visit_bindparam(bindparam):
- if bindparam._identifying_key in bind_to_col:
- params.append(
- (
- bindparam.key,
- bind_to_col[bindparam._identifying_key],
- None,
- )
- )
- elif bindparam.callable is None:
- params.append((bindparam.key, None, bindparam.value))
-
- criterion = visitors.cloned_traverse(
- criterion, {}, {"bindparam": visit_bindparam}
- )
-
- return criterion, params
-
- def _generate_lazy_clause(self, state, passive):
- criterion, param_keys = self._simple_lazy_clause
-
- if state is None:
- return sql_util.adapt_criterion_to_null(
- criterion, [key for key, ident, value in param_keys]
- )
-
- mapper = self.parent_property.parent
-
- o = state.obj() # strong ref
- dict_ = attributes.instance_dict(o)
-
- if passive & PassiveFlag.INIT_OK:
- passive ^= PassiveFlag.INIT_OK
-
- params = {}
- for key, ident, value in param_keys:
- if ident is not None:
- if passive and passive & PassiveFlag.LOAD_AGAINST_COMMITTED:
- value = mapper._get_committed_state_attr_by_column(
- state, dict_, ident, passive
- )
- else:
- value = mapper._get_state_attr_by_column(
- state, dict_, ident, passive
- )
-
- params[key] = value
-
- return criterion, params
-
- def _invoke_raise_load(self, state, passive, lazy):
- raise sa_exc.InvalidRequestError(
- "'%s' is not available due to lazy='%s'" % (self, lazy)
- )
-
- def _load_for_state(
- self,
- state,
- passive,
- loadopt=None,
- extra_criteria=(),
- extra_options=(),
- alternate_effective_path=None,
- execution_options=util.EMPTY_DICT,
- ):
- if not state.key and (
- (
- not self.parent_property.load_on_pending
- and not state._load_pending
- )
- or not state.session_id
- ):
- return LoaderCallableStatus.ATTR_EMPTY
-
- pending = not state.key
- primary_key_identity = None
-
- use_get = self.use_get and (not loadopt or not loadopt._extra_criteria)
-
- if (not passive & PassiveFlag.SQL_OK and not use_get) or (
- not passive & attributes.NON_PERSISTENT_OK and pending
- ):
- return LoaderCallableStatus.PASSIVE_NO_RESULT
-
- if (
- # we were given lazy="raise"
- self._raise_always
- # the no_raise history-related flag was not passed
- and not passive & PassiveFlag.NO_RAISE
- and (
- # if we are use_get and related_object_ok is disabled,
- # which means we are at most looking in the identity map
- # for history purposes or otherwise returning
- # PASSIVE_NO_RESULT, don't raise. This is also a
- # history-related flag
- not use_get
- or passive & PassiveFlag.RELATED_OBJECT_OK
- )
- ):
- self._invoke_raise_load(state, passive, "raise")
-
- session = _state_session(state)
- if not session:
- if passive & PassiveFlag.NO_RAISE:
- return LoaderCallableStatus.PASSIVE_NO_RESULT
-
- raise orm_exc.DetachedInstanceError(
- "Parent instance %s is not bound to a Session; "
- "lazy load operation of attribute '%s' cannot proceed"
- % (orm_util.state_str(state), self.key)
- )
-
- # if we have a simple primary key load, check the
- # identity map without generating a Query at all
- if use_get:
- primary_key_identity = self._get_ident_for_use_get(
- session, state, passive
- )
- if LoaderCallableStatus.PASSIVE_NO_RESULT in primary_key_identity:
- return LoaderCallableStatus.PASSIVE_NO_RESULT
- elif LoaderCallableStatus.NEVER_SET in primary_key_identity:
- return LoaderCallableStatus.NEVER_SET
-
- if _none_set.issuperset(primary_key_identity):
- return None
-
- if (
- self.key in state.dict
- and not passive & PassiveFlag.DEFERRED_HISTORY_LOAD
- ):
- return LoaderCallableStatus.ATTR_WAS_SET
-
- # look for this identity in the identity map. Delegate to the
- # Query class in use, as it may have special rules for how it
- # does this, including how it decides what the correct
- # identity_token would be for this identity.
-
- instance = session._identity_lookup(
- self.entity,
- primary_key_identity,
- passive=passive,
- lazy_loaded_from=state,
- )
-
- if instance is not None:
- if instance is LoaderCallableStatus.PASSIVE_CLASS_MISMATCH:
- return None
- else:
- return instance
- elif (
- not passive & PassiveFlag.SQL_OK
- or not passive & PassiveFlag.RELATED_OBJECT_OK
- ):
- return LoaderCallableStatus.PASSIVE_NO_RESULT
-
- return self._emit_lazyload(
- session,
- state,
- primary_key_identity,
- passive,
- loadopt,
- extra_criteria,
- extra_options,
- alternate_effective_path,
- execution_options,
- )
-
- def _get_ident_for_use_get(self, session, state, passive):
- instance_mapper = state.manager.mapper
-
- if passive & PassiveFlag.LOAD_AGAINST_COMMITTED:
- get_attr = instance_mapper._get_committed_state_attr_by_column
- else:
- get_attr = instance_mapper._get_state_attr_by_column
-
- dict_ = state.dict
-
- return [
- get_attr(state, dict_, self._equated_columns[pk], passive=passive)
- for pk in self.mapper.primary_key
- ]
-
- @util.preload_module("sqlalchemy.orm.strategy_options")
- def _emit_lazyload(
- self,
- session,
- state,
- primary_key_identity,
- passive,
- loadopt,
- extra_criteria,
- extra_options,
- alternate_effective_path,
- execution_options,
- ):
- strategy_options = util.preloaded.orm_strategy_options
-
- clauseelement = self.entity.__clause_element__()
- stmt = Select._create_raw_select(
- _raw_columns=[clauseelement],
- _propagate_attrs=clauseelement._propagate_attrs,
- _label_style=LABEL_STYLE_TABLENAME_PLUS_COL,
- _compile_options=ORMCompileState.default_compile_options,
- )
- load_options = QueryContext.default_load_options
-
- load_options += {
- "_invoke_all_eagers": False,
- "_lazy_loaded_from": state,
- }
-
- if self.parent_property.secondary is not None:
- stmt = stmt.select_from(
- self.mapper, self.parent_property.secondary
- )
-
- pending = not state.key
-
- # don't autoflush on pending
- if pending or passive & attributes.NO_AUTOFLUSH:
- stmt._execution_options = util.immutabledict({"autoflush": False})
-
- use_get = self.use_get
-
- if state.load_options or (loadopt and loadopt._extra_criteria):
- if alternate_effective_path is None:
- effective_path = state.load_path[self.parent_property]
- else:
- effective_path = alternate_effective_path[self.parent_property]
-
- opts = state.load_options
-
- if loadopt and loadopt._extra_criteria:
- use_get = False
- opts += (
- orm_util.LoaderCriteriaOption(self.entity, extra_criteria),
- )
-
- stmt._with_options = opts
- elif alternate_effective_path is None:
- # this path is used if there are not already any options
- # in the query, but an event may want to add them
- effective_path = state.mapper._path_registry[self.parent_property]
- else:
- # added by immediateloader
- effective_path = alternate_effective_path[self.parent_property]
-
- if extra_options:
- stmt._with_options += extra_options
-
- stmt._compile_options += {"_current_path": effective_path}
-
- if use_get:
- if self._raise_on_sql and not passive & PassiveFlag.NO_RAISE:
- self._invoke_raise_load(state, passive, "raise_on_sql")
-
- return loading.load_on_pk_identity(
- session,
- stmt,
- primary_key_identity,
- load_options=load_options,
- execution_options=execution_options,
- )
-
- if self._order_by:
- stmt._order_by_clauses = self._order_by
-
- def _lazyload_reverse(compile_context):
- for rev in self.parent_property._reverse_property:
- # reverse props that are MANYTOONE are loading *this*
- # object from get(), so don't need to eager out to those.
- if (
- rev.direction is interfaces.MANYTOONE
- and rev._use_get
- and not isinstance(rev.strategy, LazyLoader)
- ):
- strategy_options.Load._construct_for_existing_path(
- compile_context.compile_options._current_path[
- rev.parent
- ]
- ).lazyload(rev).process_compile_state(compile_context)
-
- stmt._with_context_options += (
- (_lazyload_reverse, self.parent_property),
- )
-
- lazy_clause, params = self._generate_lazy_clause(state, passive)
-
- if execution_options:
- execution_options = util.EMPTY_DICT.merge_with(
- execution_options,
- {
- "_sa_orm_load_options": load_options,
- },
- )
- else:
- execution_options = {
- "_sa_orm_load_options": load_options,
- }
-
- if (
- self.key in state.dict
- and not passive & PassiveFlag.DEFERRED_HISTORY_LOAD
- ):
- return LoaderCallableStatus.ATTR_WAS_SET
-
- if pending:
- if util.has_intersection(orm_util._none_set, params.values()):
- return None
-
- elif util.has_intersection(orm_util._never_set, params.values()):
- return None
-
- if self._raise_on_sql and not passive & PassiveFlag.NO_RAISE:
- self._invoke_raise_load(state, passive, "raise_on_sql")
-
- stmt._where_criteria = (lazy_clause,)
-
- result = session.execute(
- stmt, params, execution_options=execution_options
- )
-
- result = result.unique().scalars().all()
-
- if self.uselist:
- return result
- else:
- l = len(result)
- if l:
- if l > 1:
- util.warn(
- "Multiple rows returned with "
- "uselist=False for lazily-loaded attribute '%s' "
- % self.parent_property
- )
-
- return result[0]
- else:
- return None
-
- def create_row_processor(
- self,
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- ):
- key = self.key
-
- if (
- context.load_options._is_user_refresh
- and context.query._compile_options._only_load_props
- and self.key in context.query._compile_options._only_load_props
- ):
- return self._immediateload_create_row_processor(
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- )
-
- if not self.is_class_level or (loadopt and loadopt._extra_criteria):
- # we are not the primary manager for this attribute
- # on this class - set up a
- # per-instance lazyloader, which will override the
- # class-level behavior.
- # this currently only happens when using a
- # "lazyload" option on a "no load"
- # attribute - "eager" attributes always have a
- # class-level lazyloader installed.
- set_lazy_callable = (
- InstanceState._instance_level_callable_processor
- )(
- mapper.class_manager,
- LoadLazyAttribute(
- key,
- self,
- loadopt,
- (
- loadopt._generate_extra_criteria(context)
- if loadopt._extra_criteria
- else None
- ),
- ),
- key,
- )
-
- populators["new"].append((self.key, set_lazy_callable))
- elif context.populate_existing or mapper.always_refresh:
-
- def reset_for_lazy_callable(state, dict_, row):
- # we are the primary manager for this attribute on
- # this class - reset its
- # per-instance attribute state, so that the class-level
- # lazy loader is
- # executed when next referenced on this instance.
- # this is needed in
- # populate_existing() types of scenarios to reset
- # any existing state.
- state._reset(dict_, key)
-
- populators["new"].append((self.key, reset_for_lazy_callable))
-
-
-class LoadLazyAttribute:
- """semi-serializable loader object used by LazyLoader
-
- Historically, this object would be carried along with instances that
- needed to run lazyloaders, so it had to be serializable to support
- cached instances.
-
- this is no longer a general requirement, and the case where this object
- is used is exactly the case where we can't really serialize easily,
- which is when extra criteria in the loader option is present.
-
- We can't reliably serialize that as it refers to mapped entities and
- AliasedClass objects that are local to the current process, which would
- need to be matched up on deserialize e.g. the sqlalchemy.ext.serializer
- approach.
-
- """
-
- def __init__(self, key, initiating_strategy, loadopt, extra_criteria):
- self.key = key
- self.strategy_key = initiating_strategy.strategy_key
- self.loadopt = loadopt
- self.extra_criteria = extra_criteria
-
- def __getstate__(self):
- if self.extra_criteria is not None:
- util.warn(
- "Can't reliably serialize a lazyload() option that "
- "contains additional criteria; please use eager loading "
- "for this case"
- )
- return {
- "key": self.key,
- "strategy_key": self.strategy_key,
- "loadopt": self.loadopt,
- "extra_criteria": (),
- }
-
- def __call__(self, state, passive=attributes.PASSIVE_OFF):
- key = self.key
- instance_mapper = state.manager.mapper
- prop = instance_mapper._props[key]
- strategy = prop._strategies[self.strategy_key]
-
- return strategy._load_for_state(
- state,
- passive,
- loadopt=self.loadopt,
- extra_criteria=self.extra_criteria,
- )
-
-
-class PostLoader(AbstractRelationshipLoader):
- """A relationship loader that emits a second SELECT statement."""
-
- __slots__ = ()
-
- def _setup_for_recursion(self, context, path, loadopt, join_depth=None):
- effective_path = (
- context.compile_state.current_path or orm_util.PathRegistry.root
- ) + path
-
- top_level_context = context._get_top_level_context()
- execution_options = util.immutabledict(
- {"sa_top_level_orm_context": top_level_context}
- )
-
- if loadopt:
- recursion_depth = loadopt.local_opts.get("recursion_depth", None)
- unlimited_recursion = recursion_depth == -1
- else:
- recursion_depth = None
- unlimited_recursion = False
-
- if recursion_depth is not None:
- if not self.parent_property._is_self_referential:
- raise sa_exc.InvalidRequestError(
- f"recursion_depth option on relationship "
- f"{self.parent_property} not valid for "
- "non-self-referential relationship"
- )
- recursion_depth = context.execution_options.get(
- f"_recursion_depth_{id(self)}", recursion_depth
- )
-
- if not unlimited_recursion and recursion_depth < 0:
- return (
- effective_path,
- False,
- execution_options,
- recursion_depth,
- )
-
- if not unlimited_recursion:
- execution_options = execution_options.union(
- {
- f"_recursion_depth_{id(self)}": recursion_depth - 1,
- }
- )
-
- if loading.PostLoad.path_exists(
- context, effective_path, self.parent_property
- ):
- return effective_path, False, execution_options, recursion_depth
-
- path_w_prop = path[self.parent_property]
- effective_path_w_prop = effective_path[self.parent_property]
-
- if not path_w_prop.contains(context.attributes, "loader"):
- if join_depth:
- if effective_path_w_prop.length / 2 > join_depth:
- return (
- effective_path,
- False,
- execution_options,
- recursion_depth,
- )
- elif effective_path_w_prop.contains_mapper(self.mapper):
- return (
- effective_path,
- False,
- execution_options,
- recursion_depth,
- )
-
- return effective_path, True, execution_options, recursion_depth
-
-
-@relationships.RelationshipProperty.strategy_for(lazy="immediate")
-class ImmediateLoader(PostLoader):
- __slots__ = ("join_depth",)
-
- def __init__(self, parent, strategy_key):
- super().__init__(parent, strategy_key)
- self.join_depth = self.parent_property.join_depth
-
- def init_class_attribute(self, mapper):
- self.parent_property._get_strategy(
- (("lazy", "select"),)
- ).init_class_attribute(mapper)
-
- def create_row_processor(
- self,
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- ):
- (
- effective_path,
- run_loader,
- execution_options,
- recursion_depth,
- ) = self._setup_for_recursion(context, path, loadopt, self.join_depth)
- if not run_loader:
- # this will not emit SQL and will only emit for a many-to-one
- # "use get" load. the "_RELATED" part means it may return
- # instance even if its expired, since this is a mutually-recursive
- # load operation.
- flags = attributes.PASSIVE_NO_FETCH_RELATED | PassiveFlag.NO_RAISE
- else:
- flags = attributes.PASSIVE_OFF | PassiveFlag.NO_RAISE
-
- loading.PostLoad.callable_for_path(
- context,
- effective_path,
- self.parent,
- self.parent_property,
- self._load_for_path,
- loadopt,
- flags,
- recursion_depth,
- execution_options,
- )
-
- def _load_for_path(
- self,
- context,
- path,
- states,
- load_only,
- loadopt,
- flags,
- recursion_depth,
- execution_options,
- ):
- if recursion_depth:
- new_opt = Load(loadopt.path.entity)
- new_opt.context = (
- loadopt,
- loadopt._recurse(),
- )
- alternate_effective_path = path._truncate_recursive()
- extra_options = (new_opt,)
- else:
- new_opt = None
- alternate_effective_path = path
- extra_options = ()
-
- key = self.key
- lazyloader = self.parent_property._get_strategy((("lazy", "select"),))
- for state, overwrite in states:
- dict_ = state.dict
-
- if overwrite or key not in dict_:
- value = lazyloader._load_for_state(
- state,
- flags,
- extra_options=extra_options,
- alternate_effective_path=alternate_effective_path,
- execution_options=execution_options,
- )
- if value not in (
- ATTR_WAS_SET,
- LoaderCallableStatus.PASSIVE_NO_RESULT,
- ):
- state.get_impl(key).set_committed_value(
- state, dict_, value
- )
-
-
-@log.class_logger
-@relationships.RelationshipProperty.strategy_for(lazy="subquery")
-class SubqueryLoader(PostLoader):
- __slots__ = ("join_depth",)
-
- def __init__(self, parent, strategy_key):
- super().__init__(parent, strategy_key)
- self.join_depth = self.parent_property.join_depth
-
- def init_class_attribute(self, mapper):
- self.parent_property._get_strategy(
- (("lazy", "select"),)
- ).init_class_attribute(mapper)
-
- def _get_leftmost(
- self,
- orig_query_entity_index,
- subq_path,
- current_compile_state,
- is_root,
- ):
- given_subq_path = subq_path
- subq_path = subq_path.path
- subq_mapper = orm_util._class_to_mapper(subq_path[0])
-
- # determine attributes of the leftmost mapper
- if (
- self.parent.isa(subq_mapper)
- and self.parent_property is subq_path[1]
- ):
- leftmost_mapper, leftmost_prop = self.parent, self.parent_property
- else:
- leftmost_mapper, leftmost_prop = subq_mapper, subq_path[1]
-
- if is_root:
- # the subq_path is also coming from cached state, so when we start
- # building up this path, it has to also be converted to be in terms
- # of the current state. this is for the specific case of the entity
- # is an AliasedClass against a subquery that's not otherwise going
- # to adapt
- new_subq_path = current_compile_state._entities[
- orig_query_entity_index
- ].entity_zero._path_registry[leftmost_prop]
- additional = len(subq_path) - len(new_subq_path)
- if additional:
- new_subq_path += path_registry.PathRegistry.coerce(
- subq_path[-additional:]
- )
- else:
- new_subq_path = given_subq_path
-
- leftmost_cols = leftmost_prop.local_columns
-
- leftmost_attr = [
- getattr(
- new_subq_path.path[0].entity,
- leftmost_mapper._columntoproperty[c].key,
- )
- for c in leftmost_cols
- ]
-
- return leftmost_mapper, leftmost_attr, leftmost_prop, new_subq_path
-
- def _generate_from_original_query(
- self,
- orig_compile_state,
- orig_query,
- leftmost_mapper,
- leftmost_attr,
- leftmost_relationship,
- orig_entity,
- ):
- # reformat the original query
- # to look only for significant columns
- q = orig_query._clone().correlate(None)
-
- # LEGACY: make a Query back from the select() !!
- # This suits at least two legacy cases:
- # 1. applications which expect before_compile() to be called
- # below when we run .subquery() on this query (Keystone)
- # 2. applications which are doing subqueryload with complex
- # from_self() queries, as query.subquery() / .statement
- # has to do the full compile context for multiply-nested
- # from_self() (Neutron) - see test_subqload_from_self
- # for demo.
- q2 = query.Query.__new__(query.Query)
- q2.__dict__.update(q.__dict__)
- q = q2
-
- # set the query's "FROM" list explicitly to what the
- # FROM list would be in any case, as we will be limiting
- # the columns in the SELECT list which may no longer include
- # all entities mentioned in things like WHERE, JOIN, etc.
- if not q._from_obj:
- q._enable_assertions = False
- q.select_from.non_generative(
- q,
- *{
- ent["entity"]
- for ent in _column_descriptions(
- orig_query, compile_state=orig_compile_state
- )
- if ent["entity"] is not None
- },
- )
-
- # select from the identity columns of the outer (specifically, these
- # are the 'local_cols' of the property). This will remove other
- # columns from the query that might suggest the right entity which is
- # why we do set select_from above. The attributes we have are
- # coerced and adapted using the original query's adapter, which is
- # needed only for the case of adapting a subclass column to
- # that of a polymorphic selectable, e.g. we have
- # Engineer.primary_language and the entity is Person. All other
- # adaptations, e.g. from_self, select_entity_from(), will occur
- # within the new query when it compiles, as the compile_state we are
- # using here is only a partial one. If the subqueryload is from a
- # with_polymorphic() or other aliased() object, left_attr will already
- # be the correct attributes so no adaptation is needed.
- target_cols = orig_compile_state._adapt_col_list(
- [
- sql.coercions.expect(sql.roles.ColumnsClauseRole, o)
- for o in leftmost_attr
- ],
- orig_compile_state._get_current_adapter(),
- )
- q._raw_columns = target_cols
-
- distinct_target_key = leftmost_relationship.distinct_target_key
-
- if distinct_target_key is True:
- q._distinct = True
- elif distinct_target_key is None:
- # if target_cols refer to a non-primary key or only
- # part of a composite primary key, set the q as distinct
- for t in {c.table for c in target_cols}:
- if not set(target_cols).issuperset(t.primary_key):
- q._distinct = True
- break
-
- # don't need ORDER BY if no limit/offset
- if not q._has_row_limiting_clause:
- q._order_by_clauses = ()
-
- if q._distinct is True and q._order_by_clauses:
- # the logic to automatically add the order by columns to the query
- # when distinct is True is deprecated in the query
- to_add = sql_util.expand_column_list_from_order_by(
- target_cols, q._order_by_clauses
- )
- if to_add:
- q._set_entities(target_cols + to_add)
-
- # the original query now becomes a subquery
- # which we'll join onto.
- # LEGACY: as "q" is a Query, the before_compile() event is invoked
- # here.
- embed_q = q.set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL).subquery()
- left_alias = orm_util.AliasedClass(
- leftmost_mapper, embed_q, use_mapper_path=True
- )
- return left_alias
-
- def _prep_for_joins(self, left_alias, subq_path):
- # figure out what's being joined. a.k.a. the fun part
- to_join = []
- pairs = list(subq_path.pairs())
-
- for i, (mapper, prop) in enumerate(pairs):
- if i > 0:
- # look at the previous mapper in the chain -
- # if it is as or more specific than this prop's
- # mapper, use that instead.
- # note we have an assumption here that
- # the non-first element is always going to be a mapper,
- # not an AliasedClass
-
- prev_mapper = pairs[i - 1][1].mapper
- to_append = prev_mapper if prev_mapper.isa(mapper) else mapper
- else:
- to_append = mapper
-
- to_join.append((to_append, prop.key))
-
- # determine the immediate parent class we are joining from,
- # which needs to be aliased.
-
- if len(to_join) < 2:
- # in the case of a one level eager load, this is the
- # leftmost "left_alias".
- parent_alias = left_alias
- else:
- info = inspect(to_join[-1][0])
- if info.is_aliased_class:
- parent_alias = info.entity
- else:
- # alias a plain mapper as we may be
- # joining multiple times
- parent_alias = orm_util.AliasedClass(
- info.entity, use_mapper_path=True
- )
-
- local_cols = self.parent_property.local_columns
-
- local_attr = [
- getattr(parent_alias, self.parent._columntoproperty[c].key)
- for c in local_cols
- ]
- return to_join, local_attr, parent_alias
-
- def _apply_joins(
- self, q, to_join, left_alias, parent_alias, effective_entity
- ):
- ltj = len(to_join)
- if ltj == 1:
- to_join = [
- getattr(left_alias, to_join[0][1]).of_type(effective_entity)
- ]
- elif ltj == 2:
- to_join = [
- getattr(left_alias, to_join[0][1]).of_type(parent_alias),
- getattr(parent_alias, to_join[-1][1]).of_type(
- effective_entity
- ),
- ]
- elif ltj > 2:
- middle = [
- (
- (
- orm_util.AliasedClass(item[0])
- if not inspect(item[0]).is_aliased_class
- else item[0].entity
- ),
- item[1],
- )
- for item in to_join[1:-1]
- ]
- inner = []
-
- while middle:
- item = middle.pop(0)
- attr = getattr(item[0], item[1])
- if middle:
- attr = attr.of_type(middle[0][0])
- else:
- attr = attr.of_type(parent_alias)
-
- inner.append(attr)
-
- to_join = (
- [getattr(left_alias, to_join[0][1]).of_type(inner[0].parent)]
- + inner
- + [
- getattr(parent_alias, to_join[-1][1]).of_type(
- effective_entity
- )
- ]
- )
-
- for attr in to_join:
- q = q.join(attr)
-
- return q
-
- def _setup_options(
- self,
- context,
- q,
- subq_path,
- rewritten_path,
- orig_query,
- effective_entity,
- loadopt,
- ):
- # note that because the subqueryload object
- # does not re-use the cached query, instead always making
- # use of the current invoked query, while we have two queries
- # here (orig and context.query), they are both non-cached
- # queries and we can transfer the options as is without
- # adjusting for new criteria. Some work on #6881 / #6889
- # brought this into question.
- new_options = orig_query._with_options
-
- if loadopt and loadopt._extra_criteria:
- new_options += (
- orm_util.LoaderCriteriaOption(
- self.entity,
- loadopt._generate_extra_criteria(context),
- ),
- )
-
- # propagate loader options etc. to the new query.
- # these will fire relative to subq_path.
- q = q._with_current_path(rewritten_path)
- q = q.options(*new_options)
-
- return q
-
- def _setup_outermost_orderby(self, q):
- if self.parent_property.order_by:
-
- def _setup_outermost_orderby(compile_context):
- compile_context.eager_order_by += tuple(
- util.to_list(self.parent_property.order_by)
- )
-
- q = q._add_context_option(
- _setup_outermost_orderby, self.parent_property
- )
-
- return q
-
- class _SubqCollections:
- """Given a :class:`_query.Query` used to emit the "subquery load",
- provide a load interface that executes the query at the
- first moment a value is needed.
-
- """
-
- __slots__ = (
- "session",
- "execution_options",
- "load_options",
- "params",
- "subq",
- "_data",
- )
-
- def __init__(self, context, subq):
- # avoid creating a cycle by storing context
- # even though that's preferable
- self.session = context.session
- self.execution_options = context.execution_options
- self.load_options = context.load_options
- self.params = context.params or {}
- self.subq = subq
- self._data = None
-
- def get(self, key, default):
- if self._data is None:
- self._load()
- return self._data.get(key, default)
-
- def _load(self):
- self._data = collections.defaultdict(list)
-
- q = self.subq
- assert q.session is None
-
- q = q.with_session(self.session)
-
- if self.load_options._populate_existing:
- q = q.populate_existing()
- # to work with baked query, the parameters may have been
- # updated since this query was created, so take these into account
-
- rows = list(q.params(self.params))
- for k, v in itertools.groupby(rows, lambda x: x[1:]):
- self._data[k].extend(vv[0] for vv in v)
-
- def loader(self, state, dict_, row):
- if self._data is None:
- self._load()
-
- def _setup_query_from_rowproc(
- self,
- context,
- query_entity,
- path,
- entity,
- loadopt,
- adapter,
- ):
- compile_state = context.compile_state
- if (
- not compile_state.compile_options._enable_eagerloads
- or compile_state.compile_options._for_refresh_state
- ):
- return
-
- orig_query_entity_index = compile_state._entities.index(query_entity)
- context.loaders_require_buffering = True
-
- path = path[self.parent_property]
-
- # build up a path indicating the path from the leftmost
- # entity to the thing we're subquery loading.
- with_poly_entity = path.get(
- compile_state.attributes, "path_with_polymorphic", None
- )
- if with_poly_entity is not None:
- effective_entity = with_poly_entity
- else:
- effective_entity = self.entity
-
- subq_path, rewritten_path = context.query._execution_options.get(
- ("subquery_paths", None),
- (orm_util.PathRegistry.root, orm_util.PathRegistry.root),
- )
- is_root = subq_path is orm_util.PathRegistry.root
- subq_path = subq_path + path
- rewritten_path = rewritten_path + path
-
- # use the current query being invoked, not the compile state
- # one. this is so that we get the current parameters. however,
- # it means we can't use the existing compile state, we have to make
- # a new one. other approaches include possibly using the
- # compiled query but swapping the params, seems only marginally
- # less time spent but more complicated
- orig_query = context.query._execution_options.get(
- ("orig_query", SubqueryLoader), context.query
- )
-
- # make a new compile_state for the query that's probably cached, but
- # we're sort of undoing a bit of that caching :(
- compile_state_cls = ORMCompileState._get_plugin_class_for_plugin(
- orig_query, "orm"
- )
-
- if orig_query._is_lambda_element:
- if context.load_options._lazy_loaded_from is None:
- util.warn(
- 'subqueryloader for "%s" must invoke lambda callable '
- "at %r in "
- "order to produce a new query, decreasing the efficiency "
- "of caching for this statement. Consider using "
- "selectinload() for more effective full-lambda caching"
- % (self, orig_query)
- )
- orig_query = orig_query._resolved
-
- # this is the more "quick" version, however it's not clear how
- # much of this we need. in particular I can't get a test to
- # fail if the "set_base_alias" is missing and not sure why that is.
- orig_compile_state = compile_state_cls._create_entities_collection(
- orig_query, legacy=False
- )
-
- (
- leftmost_mapper,
- leftmost_attr,
- leftmost_relationship,
- rewritten_path,
- ) = self._get_leftmost(
- orig_query_entity_index,
- rewritten_path,
- orig_compile_state,
- is_root,
- )
-
- # generate a new Query from the original, then
- # produce a subquery from it.
- left_alias = self._generate_from_original_query(
- orig_compile_state,
- orig_query,
- leftmost_mapper,
- leftmost_attr,
- leftmost_relationship,
- entity,
- )
-
- # generate another Query that will join the
- # left alias to the target relationships.
- # basically doing a longhand
- # "from_self()". (from_self() itself not quite industrial
- # strength enough for all contingencies...but very close)
-
- q = query.Query(effective_entity)
-
- q._execution_options = context.query._execution_options.merge_with(
- context.execution_options,
- {
- ("orig_query", SubqueryLoader): orig_query,
- ("subquery_paths", None): (subq_path, rewritten_path),
- },
- )
-
- q = q._set_enable_single_crit(False)
- to_join, local_attr, parent_alias = self._prep_for_joins(
- left_alias, subq_path
- )
-
- q = q.add_columns(*local_attr)
- q = self._apply_joins(
- q, to_join, left_alias, parent_alias, effective_entity
- )
-
- q = self._setup_options(
- context,
- q,
- subq_path,
- rewritten_path,
- orig_query,
- effective_entity,
- loadopt,
- )
- q = self._setup_outermost_orderby(q)
-
- return q
-
- def create_row_processor(
- self,
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- ):
- if context.refresh_state:
- return self._immediateload_create_row_processor(
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- )
-
- _, run_loader, _, _ = self._setup_for_recursion(
- context, path, loadopt, self.join_depth
- )
- if not run_loader:
- return
-
- if not isinstance(context.compile_state, ORMSelectCompileState):
- # issue 7505 - subqueryload() in 1.3 and previous would silently
- # degrade for from_statement() without warning. this behavior
- # is restored here
- return
-
- if not self.parent.class_manager[self.key].impl.supports_population:
- raise sa_exc.InvalidRequestError(
- "'%s' does not support object "
- "population - eager loading cannot be applied." % self
- )
-
- # a little dance here as the "path" is still something that only
- # semi-tracks the exact series of things we are loading, still not
- # telling us about with_polymorphic() and stuff like that when it's at
- # the root.. the initial MapperEntity is more accurate for this case.
- if len(path) == 1:
- if not orm_util._entity_isa(query_entity.entity_zero, self.parent):
- return
- elif not orm_util._entity_isa(path[-1], self.parent):
- return
-
- subq = self._setup_query_from_rowproc(
- context,
- query_entity,
- path,
- path[-1],
- loadopt,
- adapter,
- )
-
- if subq is None:
- return
-
- assert subq.session is None
-
- path = path[self.parent_property]
-
- local_cols = self.parent_property.local_columns
-
- # cache the loaded collections in the context
- # so that inheriting mappers don't re-load when they
- # call upon create_row_processor again
- collections = path.get(context.attributes, "collections")
- if collections is None:
- collections = self._SubqCollections(context, subq)
- path.set(context.attributes, "collections", collections)
-
- if adapter:
- local_cols = [adapter.columns[c] for c in local_cols]
-
- if self.uselist:
- self._create_collection_loader(
- context, result, collections, local_cols, populators
- )
- else:
- self._create_scalar_loader(
- context, result, collections, local_cols, populators
- )
-
- def _create_collection_loader(
- self, context, result, collections, local_cols, populators
- ):
- tuple_getter = result._tuple_getter(local_cols)
-
- def load_collection_from_subq(state, dict_, row):
- collection = collections.get(tuple_getter(row), ())
- state.get_impl(self.key).set_committed_value(
- state, dict_, collection
- )
-
- def load_collection_from_subq_existing_row(state, dict_, row):
- if self.key not in dict_:
- load_collection_from_subq(state, dict_, row)
-
- populators["new"].append((self.key, load_collection_from_subq))
- populators["existing"].append(
- (self.key, load_collection_from_subq_existing_row)
- )
-
- if context.invoke_all_eagers:
- populators["eager"].append((self.key, collections.loader))
-
- def _create_scalar_loader(
- self, context, result, collections, local_cols, populators
- ):
- tuple_getter = result._tuple_getter(local_cols)
-
- def load_scalar_from_subq(state, dict_, row):
- collection = collections.get(tuple_getter(row), (None,))
- if len(collection) > 1:
- util.warn(
- "Multiple rows returned with "
- "uselist=False for eagerly-loaded attribute '%s' " % self
- )
-
- scalar = collection[0]
- state.get_impl(self.key).set_committed_value(state, dict_, scalar)
-
- def load_scalar_from_subq_existing_row(state, dict_, row):
- if self.key not in dict_:
- load_scalar_from_subq(state, dict_, row)
-
- populators["new"].append((self.key, load_scalar_from_subq))
- populators["existing"].append(
- (self.key, load_scalar_from_subq_existing_row)
- )
- if context.invoke_all_eagers:
- populators["eager"].append((self.key, collections.loader))
-
-
-@log.class_logger
-@relationships.RelationshipProperty.strategy_for(lazy="joined")
-@relationships.RelationshipProperty.strategy_for(lazy=False)
-class JoinedLoader(AbstractRelationshipLoader):
- """Provide loading behavior for a :class:`.Relationship`
- using joined eager loading.
-
- """
-
- __slots__ = "join_depth"
-
- def __init__(self, parent, strategy_key):
- super().__init__(parent, strategy_key)
- self.join_depth = self.parent_property.join_depth
-
- def init_class_attribute(self, mapper):
- self.parent_property._get_strategy(
- (("lazy", "select"),)
- ).init_class_attribute(mapper)
-
- def setup_query(
- self,
- compile_state,
- query_entity,
- path,
- loadopt,
- adapter,
- column_collection=None,
- parentmapper=None,
- chained_from_outerjoin=False,
- **kwargs,
- ):
- """Add a left outer join to the statement that's being constructed."""
-
- if not compile_state.compile_options._enable_eagerloads:
- return
- elif self.uselist:
- compile_state.multi_row_eager_loaders = True
-
- path = path[self.parent_property]
-
- with_polymorphic = None
-
- user_defined_adapter = (
- self._init_user_defined_eager_proc(
- loadopt, compile_state, compile_state.attributes
- )
- if loadopt
- else False
- )
-
- if user_defined_adapter is not False:
- # setup an adapter but dont create any JOIN, assume it's already
- # in the query
- (
- clauses,
- adapter,
- add_to_collection,
- ) = self._setup_query_on_user_defined_adapter(
- compile_state,
- query_entity,
- path,
- adapter,
- user_defined_adapter,
- )
-
- # don't do "wrap" for multi-row, we want to wrap
- # limited/distinct SELECT,
- # because we want to put the JOIN on the outside.
-
- else:
- # if not via query option, check for
- # a cycle
- if not path.contains(compile_state.attributes, "loader"):
- if self.join_depth:
- if path.length / 2 > self.join_depth:
- return
- elif path.contains_mapper(self.mapper):
- return
-
- # add the JOIN and create an adapter
- (
- clauses,
- adapter,
- add_to_collection,
- chained_from_outerjoin,
- ) = self._generate_row_adapter(
- compile_state,
- query_entity,
- path,
- loadopt,
- adapter,
- column_collection,
- parentmapper,
- chained_from_outerjoin,
- )
-
- # for multi-row, we want to wrap limited/distinct SELECT,
- # because we want to put the JOIN on the outside.
- compile_state.eager_adding_joins = True
-
- with_poly_entity = path.get(
- compile_state.attributes, "path_with_polymorphic", None
- )
- if with_poly_entity is not None:
- with_polymorphic = inspect(
- with_poly_entity
- ).with_polymorphic_mappers
- else:
- with_polymorphic = None
-
- path = path[self.entity]
-
- loading._setup_entity_query(
- compile_state,
- self.mapper,
- query_entity,
- path,
- clauses,
- add_to_collection,
- with_polymorphic=with_polymorphic,
- parentmapper=self.mapper,
- chained_from_outerjoin=chained_from_outerjoin,
- )
-
- has_nones = util.NONE_SET.intersection(compile_state.secondary_columns)
-
- if has_nones:
- if with_poly_entity is not None:
- raise sa_exc.InvalidRequestError(
- "Detected unaliased columns when generating joined "
- "load. Make sure to use aliased=True or flat=True "
- "when using joined loading with with_polymorphic()."
- )
- else:
- compile_state.secondary_columns = [
- c for c in compile_state.secondary_columns if c is not None
- ]
-
- def _init_user_defined_eager_proc(
- self, loadopt, compile_state, target_attributes
- ):
- # check if the opt applies at all
- if "eager_from_alias" not in loadopt.local_opts:
- # nope
- return False
-
- path = loadopt.path.parent
-
- # the option applies. check if the "user_defined_eager_row_processor"
- # has been built up.
- adapter = path.get(
- compile_state.attributes, "user_defined_eager_row_processor", False
- )
- if adapter is not False:
- # just return it
- return adapter
-
- # otherwise figure it out.
- alias = loadopt.local_opts["eager_from_alias"]
- root_mapper, prop = path[-2:]
-
- if alias is not None:
- if isinstance(alias, str):
- alias = prop.target.alias(alias)
- adapter = orm_util.ORMAdapter(
- orm_util._TraceAdaptRole.JOINEDLOAD_USER_DEFINED_ALIAS,
- prop.mapper,
- selectable=alias,
- equivalents=prop.mapper._equivalent_columns,
- limit_on_entity=False,
- )
- else:
- if path.contains(
- compile_state.attributes, "path_with_polymorphic"
- ):
- with_poly_entity = path.get(
- compile_state.attributes, "path_with_polymorphic"
- )
- adapter = orm_util.ORMAdapter(
- orm_util._TraceAdaptRole.JOINEDLOAD_PATH_WITH_POLYMORPHIC,
- with_poly_entity,
- equivalents=prop.mapper._equivalent_columns,
- )
- else:
- adapter = compile_state._polymorphic_adapters.get(
- prop.mapper, None
- )
- path.set(
- target_attributes,
- "user_defined_eager_row_processor",
- adapter,
- )
-
- return adapter
-
- def _setup_query_on_user_defined_adapter(
- self, context, entity, path, adapter, user_defined_adapter
- ):
- # apply some more wrapping to the "user defined adapter"
- # if we are setting up the query for SQL render.
- adapter = entity._get_entity_clauses(context)
-
- if adapter and user_defined_adapter:
- user_defined_adapter = user_defined_adapter.wrap(adapter)
- path.set(
- context.attributes,
- "user_defined_eager_row_processor",
- user_defined_adapter,
- )
- elif adapter:
- user_defined_adapter = adapter
- path.set(
- context.attributes,
- "user_defined_eager_row_processor",
- user_defined_adapter,
- )
-
- add_to_collection = context.primary_columns
- return user_defined_adapter, adapter, add_to_collection
-
- def _generate_row_adapter(
- self,
- compile_state,
- entity,
- path,
- loadopt,
- adapter,
- column_collection,
- parentmapper,
- chained_from_outerjoin,
- ):
- with_poly_entity = path.get(
- compile_state.attributes, "path_with_polymorphic", None
- )
- if with_poly_entity:
- to_adapt = with_poly_entity
- else:
- insp = inspect(self.entity)
- if insp.is_aliased_class:
- alt_selectable = insp.selectable
- else:
- alt_selectable = None
-
- to_adapt = orm_util.AliasedClass(
- self.mapper,
- alias=(
- alt_selectable._anonymous_fromclause(flat=True)
- if alt_selectable is not None
- else None
- ),
- flat=True,
- use_mapper_path=True,
- )
-
- to_adapt_insp = inspect(to_adapt)
-
- clauses = to_adapt_insp._memo(
- ("joinedloader_ormadapter", self),
- orm_util.ORMAdapter,
- orm_util._TraceAdaptRole.JOINEDLOAD_MEMOIZED_ADAPTER,
- to_adapt_insp,
- equivalents=self.mapper._equivalent_columns,
- adapt_required=True,
- allow_label_resolve=False,
- anonymize_labels=True,
- )
-
- assert clauses.is_aliased_class
-
- innerjoin = (
- loadopt.local_opts.get("innerjoin", self.parent_property.innerjoin)
- if loadopt is not None
- else self.parent_property.innerjoin
- )
-
- if not innerjoin:
- # if this is an outer join, all non-nested eager joins from
- # this path must also be outer joins
- chained_from_outerjoin = True
-
- compile_state.create_eager_joins.append(
- (
- self._create_eager_join,
- entity,
- path,
- adapter,
- parentmapper,
- clauses,
- innerjoin,
- chained_from_outerjoin,
- loadopt._extra_criteria if loadopt else (),
- )
- )
-
- add_to_collection = compile_state.secondary_columns
- path.set(compile_state.attributes, "eager_row_processor", clauses)
-
- return clauses, adapter, add_to_collection, chained_from_outerjoin
-
- def _create_eager_join(
- self,
- compile_state,
- query_entity,
- path,
- adapter,
- parentmapper,
- clauses,
- innerjoin,
- chained_from_outerjoin,
- extra_criteria,
- ):
- if parentmapper is None:
- localparent = query_entity.mapper
- else:
- localparent = parentmapper
-
- # whether or not the Query will wrap the selectable in a subquery,
- # and then attach eager load joins to that (i.e., in the case of
- # LIMIT/OFFSET etc.)
- should_nest_selectable = (
- compile_state.multi_row_eager_loaders
- and compile_state._should_nest_selectable
- )
-
- query_entity_key = None
-
- if (
- query_entity not in compile_state.eager_joins
- and not should_nest_selectable
- and compile_state.from_clauses
- ):
- indexes = sql_util.find_left_clause_that_matches_given(
- compile_state.from_clauses, query_entity.selectable
- )
-
- if len(indexes) > 1:
- # for the eager load case, I can't reproduce this right
- # now. For query.join() I can.
- raise sa_exc.InvalidRequestError(
- "Can't identify which query entity in which to joined "
- "eager load from. Please use an exact match when "
- "specifying the join path."
- )
-
- if indexes:
- clause = compile_state.from_clauses[indexes[0]]
- # join to an existing FROM clause on the query.
- # key it to its list index in the eager_joins dict.
- # Query._compile_context will adapt as needed and
- # append to the FROM clause of the select().
- query_entity_key, default_towrap = indexes[0], clause
-
- if query_entity_key is None:
- query_entity_key, default_towrap = (
- query_entity,
- query_entity.selectable,
- )
-
- towrap = compile_state.eager_joins.setdefault(
- query_entity_key, default_towrap
- )
-
- if adapter:
- if getattr(adapter, "is_aliased_class", False):
- # joining from an adapted entity. The adapted entity
- # might be a "with_polymorphic", so resolve that to our
- # specific mapper's entity before looking for our attribute
- # name on it.
- efm = adapter.aliased_insp._entity_for_mapper(
- localparent
- if localparent.isa(self.parent)
- else self.parent
- )
-
- # look for our attribute on the adapted entity, else fall back
- # to our straight property
- onclause = getattr(efm.entity, self.key, self.parent_property)
- else:
- onclause = getattr(
- orm_util.AliasedClass(
- self.parent, adapter.selectable, use_mapper_path=True
- ),
- self.key,
- self.parent_property,
- )
-
- else:
- onclause = self.parent_property
-
- assert clauses.is_aliased_class
-
- attach_on_outside = (
- not chained_from_outerjoin
- or not innerjoin
- or innerjoin == "unnested"
- or query_entity.entity_zero.represents_outer_join
- )
-
- extra_join_criteria = extra_criteria
- additional_entity_criteria = compile_state.global_attributes.get(
- ("additional_entity_criteria", self.mapper), ()
- )
- if additional_entity_criteria:
- extra_join_criteria += tuple(
- ae._resolve_where_criteria(self.mapper)
- for ae in additional_entity_criteria
- if ae.propagate_to_loaders
- )
-
- if attach_on_outside:
- # this is the "classic" eager join case.
- eagerjoin = orm_util._ORMJoin(
- towrap,
- clauses.aliased_insp,
- onclause,
- isouter=not innerjoin
- or query_entity.entity_zero.represents_outer_join
- or (chained_from_outerjoin and isinstance(towrap, sql.Join)),
- _left_memo=self.parent,
- _right_memo=self.mapper,
- _extra_criteria=extra_join_criteria,
- )
- else:
- # all other cases are innerjoin=='nested' approach
- eagerjoin = self._splice_nested_inner_join(
- path, towrap, clauses, onclause, extra_join_criteria
- )
-
- compile_state.eager_joins[query_entity_key] = eagerjoin
-
- # send a hint to the Query as to where it may "splice" this join
- eagerjoin.stop_on = query_entity.selectable
-
- if not parentmapper:
- # for parentclause that is the non-eager end of the join,
- # ensure all the parent cols in the primaryjoin are actually
- # in the
- # columns clause (i.e. are not deferred), so that aliasing applied
- # by the Query propagates those columns outward.
- # This has the effect
- # of "undefering" those columns.
- for col in sql_util._find_columns(
- self.parent_property.primaryjoin
- ):
- if localparent.persist_selectable.c.contains_column(col):
- if adapter:
- col = adapter.columns[col]
- compile_state._append_dedupe_col_collection(
- col, compile_state.primary_columns
- )
-
- if self.parent_property.order_by:
- compile_state.eager_order_by += tuple(
- (eagerjoin._target_adapter.copy_and_process)(
- util.to_list(self.parent_property.order_by)
- )
- )
-
- def _splice_nested_inner_join(
- self, path, join_obj, clauses, onclause, extra_criteria, splicing=False
- ):
- # recursive fn to splice a nested join into an existing one.
- # splicing=False means this is the outermost call, and it
- # should return a value. splicing=<from object> is the recursive
- # form, where it can return None to indicate the end of the recursion
-
- if splicing is False:
- # first call is always handed a join object
- # from the outside
- assert isinstance(join_obj, orm_util._ORMJoin)
- elif isinstance(join_obj, sql.selectable.FromGrouping):
- return self._splice_nested_inner_join(
- path,
- join_obj.element,
- clauses,
- onclause,
- extra_criteria,
- splicing,
- )
- elif not isinstance(join_obj, orm_util._ORMJoin):
- if path[-2].isa(splicing):
- return orm_util._ORMJoin(
- join_obj,
- clauses.aliased_insp,
- onclause,
- isouter=False,
- _left_memo=splicing,
- _right_memo=path[-1].mapper,
- _extra_criteria=extra_criteria,
- )
- else:
- return None
-
- target_join = self._splice_nested_inner_join(
- path,
- join_obj.right,
- clauses,
- onclause,
- extra_criteria,
- join_obj._right_memo,
- )
- if target_join is None:
- right_splice = False
- target_join = self._splice_nested_inner_join(
- path,
- join_obj.left,
- clauses,
- onclause,
- extra_criteria,
- join_obj._left_memo,
- )
- if target_join is None:
- # should only return None when recursively called,
- # e.g. splicing refers to a from obj
- assert (
- splicing is not False
- ), "assertion failed attempting to produce joined eager loads"
- return None
- else:
- right_splice = True
-
- if right_splice:
- # for a right splice, attempt to flatten out
- # a JOIN b JOIN c JOIN .. to avoid needless
- # parenthesis nesting
- if not join_obj.isouter and not target_join.isouter:
- eagerjoin = join_obj._splice_into_center(target_join)
- else:
- eagerjoin = orm_util._ORMJoin(
- join_obj.left,
- target_join,
- join_obj.onclause,
- isouter=join_obj.isouter,
- _left_memo=join_obj._left_memo,
- )
- else:
- eagerjoin = orm_util._ORMJoin(
- target_join,
- join_obj.right,
- join_obj.onclause,
- isouter=join_obj.isouter,
- _right_memo=join_obj._right_memo,
- )
-
- eagerjoin._target_adapter = target_join._target_adapter
- return eagerjoin
-
- def _create_eager_adapter(self, context, result, adapter, path, loadopt):
- compile_state = context.compile_state
-
- user_defined_adapter = (
- self._init_user_defined_eager_proc(
- loadopt, compile_state, context.attributes
- )
- if loadopt
- else False
- )
-
- if user_defined_adapter is not False:
- decorator = user_defined_adapter
- # user defined eagerloads are part of the "primary"
- # portion of the load.
- # the adapters applied to the Query should be honored.
- if compile_state.compound_eager_adapter and decorator:
- decorator = decorator.wrap(
- compile_state.compound_eager_adapter
- )
- elif compile_state.compound_eager_adapter:
- decorator = compile_state.compound_eager_adapter
- else:
- decorator = path.get(
- compile_state.attributes, "eager_row_processor"
- )
- if decorator is None:
- return False
-
- if self.mapper._result_has_identity_key(result, decorator):
- return decorator
- else:
- # no identity key - don't return a row
- # processor, will cause a degrade to lazy
- return False
-
- def create_row_processor(
- self,
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- ):
- if not self.parent.class_manager[self.key].impl.supports_population:
- raise sa_exc.InvalidRequestError(
- "'%s' does not support object "
- "population - eager loading cannot be applied." % self
- )
-
- if self.uselist:
- context.loaders_require_uniquing = True
-
- our_path = path[self.parent_property]
-
- eager_adapter = self._create_eager_adapter(
- context, result, adapter, our_path, loadopt
- )
-
- if eager_adapter is not False:
- key = self.key
-
- _instance = loading._instance_processor(
- query_entity,
- self.mapper,
- context,
- result,
- our_path[self.entity],
- eager_adapter,
- )
-
- if not self.uselist:
- self._create_scalar_loader(context, key, _instance, populators)
- else:
- self._create_collection_loader(
- context, key, _instance, populators
- )
- else:
- self.parent_property._get_strategy(
- (("lazy", "select"),)
- ).create_row_processor(
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- )
-
- def _create_collection_loader(self, context, key, _instance, populators):
- def load_collection_from_joined_new_row(state, dict_, row):
- # note this must unconditionally clear out any existing collection.
- # an existing collection would be present only in the case of
- # populate_existing().
- collection = attributes.init_state_collection(state, dict_, key)
- result_list = util.UniqueAppender(
- collection, "append_without_event"
- )
- context.attributes[(state, key)] = result_list
- inst = _instance(row)
- if inst is not None:
- result_list.append(inst)
-
- def load_collection_from_joined_existing_row(state, dict_, row):
- if (state, key) in context.attributes:
- result_list = context.attributes[(state, key)]
- else:
- # appender_key can be absent from context.attributes
- # with isnew=False when self-referential eager loading
- # is used; the same instance may be present in two
- # distinct sets of result columns
- collection = attributes.init_state_collection(
- state, dict_, key
- )
- result_list = util.UniqueAppender(
- collection, "append_without_event"
- )
- context.attributes[(state, key)] = result_list
- inst = _instance(row)
- if inst is not None:
- result_list.append(inst)
-
- def load_collection_from_joined_exec(state, dict_, row):
- _instance(row)
-
- populators["new"].append(
- (self.key, load_collection_from_joined_new_row)
- )
- populators["existing"].append(
- (self.key, load_collection_from_joined_existing_row)
- )
- if context.invoke_all_eagers:
- populators["eager"].append(
- (self.key, load_collection_from_joined_exec)
- )
-
- def _create_scalar_loader(self, context, key, _instance, populators):
- def load_scalar_from_joined_new_row(state, dict_, row):
- # set a scalar object instance directly on the parent
- # object, bypassing InstrumentedAttribute event handlers.
- dict_[key] = _instance(row)
-
- def load_scalar_from_joined_existing_row(state, dict_, row):
- # call _instance on the row, even though the object has
- # been created, so that we further descend into properties
- existing = _instance(row)
-
- # conflicting value already loaded, this shouldn't happen
- if key in dict_:
- if existing is not dict_[key]:
- util.warn(
- "Multiple rows returned with "
- "uselist=False for eagerly-loaded attribute '%s' "
- % self
- )
- else:
- # this case is when one row has multiple loads of the
- # same entity (e.g. via aliasing), one has an attribute
- # that the other doesn't.
- dict_[key] = existing
-
- def load_scalar_from_joined_exec(state, dict_, row):
- _instance(row)
-
- populators["new"].append((self.key, load_scalar_from_joined_new_row))
- populators["existing"].append(
- (self.key, load_scalar_from_joined_existing_row)
- )
- if context.invoke_all_eagers:
- populators["eager"].append(
- (self.key, load_scalar_from_joined_exec)
- )
-
-
-@log.class_logger
-@relationships.RelationshipProperty.strategy_for(lazy="selectin")
-class SelectInLoader(PostLoader, util.MemoizedSlots):
- __slots__ = (
- "join_depth",
- "omit_join",
- "_parent_alias",
- "_query_info",
- "_fallback_query_info",
- )
-
- query_info = collections.namedtuple(
- "queryinfo",
- [
- "load_only_child",
- "load_with_join",
- "in_expr",
- "pk_cols",
- "zero_idx",
- "child_lookup_cols",
- ],
- )
-
- _chunksize = 500
-
- def __init__(self, parent, strategy_key):
- super().__init__(parent, strategy_key)
- self.join_depth = self.parent_property.join_depth
- is_m2o = self.parent_property.direction is interfaces.MANYTOONE
-
- if self.parent_property.omit_join is not None:
- self.omit_join = self.parent_property.omit_join
- else:
- lazyloader = self.parent_property._get_strategy(
- (("lazy", "select"),)
- )
- if is_m2o:
- self.omit_join = lazyloader.use_get
- else:
- self.omit_join = self.parent._get_clause[0].compare(
- lazyloader._rev_lazywhere,
- use_proxies=True,
- compare_keys=False,
- equivalents=self.parent._equivalent_columns,
- )
-
- if self.omit_join:
- if is_m2o:
- self._query_info = self._init_for_omit_join_m2o()
- self._fallback_query_info = self._init_for_join()
- else:
- self._query_info = self._init_for_omit_join()
- else:
- self._query_info = self._init_for_join()
-
- def _init_for_omit_join(self):
- pk_to_fk = dict(
- self.parent_property._join_condition.local_remote_pairs
- )
- pk_to_fk.update(
- (equiv, pk_to_fk[k])
- for k in list(pk_to_fk)
- for equiv in self.parent._equivalent_columns.get(k, ())
- )
-
- pk_cols = fk_cols = [
- pk_to_fk[col] for col in self.parent.primary_key if col in pk_to_fk
- ]
- if len(fk_cols) > 1:
- in_expr = sql.tuple_(*fk_cols)
- zero_idx = False
- else:
- in_expr = fk_cols[0]
- zero_idx = True
-
- return self.query_info(False, False, in_expr, pk_cols, zero_idx, None)
-
- def _init_for_omit_join_m2o(self):
- pk_cols = self.mapper.primary_key
- if len(pk_cols) > 1:
- in_expr = sql.tuple_(*pk_cols)
- zero_idx = False
- else:
- in_expr = pk_cols[0]
- zero_idx = True
-
- lazyloader = self.parent_property._get_strategy((("lazy", "select"),))
- lookup_cols = [lazyloader._equated_columns[pk] for pk in pk_cols]
-
- return self.query_info(
- True, False, in_expr, pk_cols, zero_idx, lookup_cols
- )
-
- def _init_for_join(self):
- self._parent_alias = AliasedClass(self.parent.class_)
- pa_insp = inspect(self._parent_alias)
- pk_cols = [
- pa_insp._adapt_element(col) for col in self.parent.primary_key
- ]
- if len(pk_cols) > 1:
- in_expr = sql.tuple_(*pk_cols)
- zero_idx = False
- else:
- in_expr = pk_cols[0]
- zero_idx = True
- return self.query_info(False, True, in_expr, pk_cols, zero_idx, None)
-
- def init_class_attribute(self, mapper):
- self.parent_property._get_strategy(
- (("lazy", "select"),)
- ).init_class_attribute(mapper)
-
- def create_row_processor(
- self,
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- ):
- if context.refresh_state:
- return self._immediateload_create_row_processor(
- context,
- query_entity,
- path,
- loadopt,
- mapper,
- result,
- adapter,
- populators,
- )
-
- (
- effective_path,
- run_loader,
- execution_options,
- recursion_depth,
- ) = self._setup_for_recursion(
- context, path, loadopt, join_depth=self.join_depth
- )
-
- if not run_loader:
- return
-
- if not self.parent.class_manager[self.key].impl.supports_population:
- raise sa_exc.InvalidRequestError(
- "'%s' does not support object "
- "population - eager loading cannot be applied." % self
- )
-
- # a little dance here as the "path" is still something that only
- # semi-tracks the exact series of things we are loading, still not
- # telling us about with_polymorphic() and stuff like that when it's at
- # the root.. the initial MapperEntity is more accurate for this case.
- if len(path) == 1:
- if not orm_util._entity_isa(query_entity.entity_zero, self.parent):
- return
- elif not orm_util._entity_isa(path[-1], self.parent):
- return
-
- selectin_path = effective_path
-
- path_w_prop = path[self.parent_property]
-
- # build up a path indicating the path from the leftmost
- # entity to the thing we're subquery loading.
- with_poly_entity = path_w_prop.get(
- context.attributes, "path_with_polymorphic", None
- )
- if with_poly_entity is not None:
- effective_entity = inspect(with_poly_entity)
- else:
- effective_entity = self.entity
-
- loading.PostLoad.callable_for_path(
- context,
- selectin_path,
- self.parent,
- self.parent_property,
- self._load_for_path,
- effective_entity,
- loadopt,
- recursion_depth,
- execution_options,
- )
-
- def _load_for_path(
- self,
- context,
- path,
- states,
- load_only,
- effective_entity,
- loadopt,
- recursion_depth,
- execution_options,
- ):
- if load_only and self.key not in load_only:
- return
-
- query_info = self._query_info
-
- if query_info.load_only_child:
- our_states = collections.defaultdict(list)
- none_states = []
-
- mapper = self.parent
-
- for state, overwrite in states:
- state_dict = state.dict
- related_ident = tuple(
- mapper._get_state_attr_by_column(
- state,
- state_dict,
- lk,
- passive=attributes.PASSIVE_NO_FETCH,
- )
- for lk in query_info.child_lookup_cols
- )
- # if the loaded parent objects do not have the foreign key
- # to the related item loaded, then degrade into the joined
- # version of selectinload
- if LoaderCallableStatus.PASSIVE_NO_RESULT in related_ident:
- query_info = self._fallback_query_info
- break
-
- # organize states into lists keyed to particular foreign
- # key values.
- if None not in related_ident:
- our_states[related_ident].append(
- (state, state_dict, overwrite)
- )
- else:
- # For FK values that have None, add them to a
- # separate collection that will be populated separately
- none_states.append((state, state_dict, overwrite))
-
- # note the above conditional may have changed query_info
- if not query_info.load_only_child:
- our_states = [
- (state.key[1], state, state.dict, overwrite)
- for state, overwrite in states
- ]
-
- pk_cols = query_info.pk_cols
- in_expr = query_info.in_expr
-
- if not query_info.load_with_join:
- # in "omit join" mode, the primary key column and the
- # "in" expression are in terms of the related entity. So
- # if the related entity is polymorphic or otherwise aliased,
- # we need to adapt our "pk_cols" and "in_expr" to that
- # entity. in non-"omit join" mode, these are against the
- # parent entity and do not need adaption.
- if effective_entity.is_aliased_class:
- pk_cols = [
- effective_entity._adapt_element(col) for col in pk_cols
- ]
- in_expr = effective_entity._adapt_element(in_expr)
-
- bundle_ent = orm_util.Bundle("pk", *pk_cols)
- bundle_sql = bundle_ent.__clause_element__()
-
- entity_sql = effective_entity.__clause_element__()
- q = Select._create_raw_select(
- _raw_columns=[bundle_sql, entity_sql],
- _label_style=LABEL_STYLE_TABLENAME_PLUS_COL,
- _compile_options=ORMCompileState.default_compile_options,
- _propagate_attrs={
- "compile_state_plugin": "orm",
- "plugin_subject": effective_entity,
- },
- )
-
- if not query_info.load_with_join:
- # the Bundle we have in the "omit_join" case is against raw, non
- # annotated columns, so to ensure the Query knows its primary
- # entity, we add it explicitly. If we made the Bundle against
- # annotated columns, we hit a performance issue in this specific
- # case, which is detailed in issue #4347.
- q = q.select_from(effective_entity)
- else:
- # in the non-omit_join case, the Bundle is against the annotated/
- # mapped column of the parent entity, but the #4347 issue does not
- # occur in this case.
- q = q.select_from(self._parent_alias).join(
- getattr(self._parent_alias, self.parent_property.key).of_type(
- effective_entity
- )
- )
-
- q = q.filter(in_expr.in_(sql.bindparam("primary_keys")))
-
- # a test which exercises what these comments talk about is
- # test_selectin_relations.py -> test_twolevel_selectin_w_polymorphic
- #
- # effective_entity above is given to us in terms of the cached
- # statement, namely this one:
- orig_query = context.compile_state.select_statement
-
- # the actual statement that was requested is this one:
- # context_query = context.query
- #
- # that's not the cached one, however. So while it is of the identical
- # structure, if it has entities like AliasedInsp, which we get from
- # aliased() or with_polymorphic(), the AliasedInsp will likely be a
- # different object identity each time, and will not match up
- # hashing-wise to the corresponding AliasedInsp that's in the
- # cached query, meaning it won't match on paths and loader lookups
- # and loaders like this one will be skipped if it is used in options.
- #
- # as it turns out, standard loader options like selectinload(),
- # lazyload() that have a path need
- # to come from the cached query so that the AliasedInsp etc. objects
- # that are in the query line up with the object that's in the path
- # of the strategy object. however other options like
- # with_loader_criteria() that doesn't have a path (has a fixed entity)
- # and needs to have access to the latest closure state in order to
- # be correct, we need to use the uncached one.
- #
- # as of #8399 we let the loader option itself figure out what it
- # wants to do given cached and uncached version of itself.
-
- effective_path = path[self.parent_property]
-
- if orig_query is context.query:
- new_options = orig_query._with_options
- else:
- cached_options = orig_query._with_options
- uncached_options = context.query._with_options
-
- # propagate compile state options from the original query,
- # updating their "extra_criteria" as necessary.
- # note this will create a different cache key than
- # "orig" options if extra_criteria is present, because the copy
- # of extra_criteria will have different boundparam than that of
- # the QueryableAttribute in the path
- new_options = [
- orig_opt._adapt_cached_option_to_uncached_option(
- context, uncached_opt
- )
- for orig_opt, uncached_opt in zip(
- cached_options, uncached_options
- )
- ]
-
- if loadopt and loadopt._extra_criteria:
- new_options += (
- orm_util.LoaderCriteriaOption(
- effective_entity,
- loadopt._generate_extra_criteria(context),
- ),
- )
-
- if recursion_depth is not None:
- effective_path = effective_path._truncate_recursive()
-
- q = q.options(*new_options)
-
- q = q._update_compile_options({"_current_path": effective_path})
- if context.populate_existing:
- q = q.execution_options(populate_existing=True)
-
- if self.parent_property.order_by:
- if not query_info.load_with_join:
- eager_order_by = self.parent_property.order_by
- if effective_entity.is_aliased_class:
- eager_order_by = [
- effective_entity._adapt_element(elem)
- for elem in eager_order_by
- ]
- q = q.order_by(*eager_order_by)
- else:
-
- def _setup_outermost_orderby(compile_context):
- compile_context.eager_order_by += tuple(
- util.to_list(self.parent_property.order_by)
- )
-
- q = q._add_context_option(
- _setup_outermost_orderby, self.parent_property
- )
-
- if query_info.load_only_child:
- self._load_via_child(
- our_states,
- none_states,
- query_info,
- q,
- context,
- execution_options,
- )
- else:
- self._load_via_parent(
- our_states, query_info, q, context, execution_options
- )
-
- def _load_via_child(
- self,
- our_states,
- none_states,
- query_info,
- q,
- context,
- execution_options,
- ):
- uselist = self.uselist
-
- # this sort is really for the benefit of the unit tests
- our_keys = sorted(our_states)
- while our_keys:
- chunk = our_keys[0 : self._chunksize]
- our_keys = our_keys[self._chunksize :]
- data = {
- k: v
- for k, v in context.session.execute(
- q,
- params={
- "primary_keys": [
- key[0] if query_info.zero_idx else key
- for key in chunk
- ]
- },
- execution_options=execution_options,
- ).unique()
- }
-
- for key in chunk:
- # for a real foreign key and no concurrent changes to the
- # DB while running this method, "key" is always present in
- # data. However, for primaryjoins without real foreign keys
- # a non-None primaryjoin condition may still refer to no
- # related object.
- related_obj = data.get(key, None)
- for state, dict_, overwrite in our_states[key]:
- if not overwrite and self.key in dict_:
- continue
-
- state.get_impl(self.key).set_committed_value(
- state,
- dict_,
- related_obj if not uselist else [related_obj],
- )
- # populate none states with empty value / collection
- for state, dict_, overwrite in none_states:
- if not overwrite and self.key in dict_:
- continue
-
- # note it's OK if this is a uselist=True attribute, the empty
- # collection will be populated
- state.get_impl(self.key).set_committed_value(state, dict_, None)
-
- def _load_via_parent(
- self, our_states, query_info, q, context, execution_options
- ):
- uselist = self.uselist
- _empty_result = () if uselist else None
-
- while our_states:
- chunk = our_states[0 : self._chunksize]
- our_states = our_states[self._chunksize :]
-
- primary_keys = [
- key[0] if query_info.zero_idx else key
- for key, state, state_dict, overwrite in chunk
- ]
-
- data = collections.defaultdict(list)
- for k, v in itertools.groupby(
- context.session.execute(
- q,
- params={"primary_keys": primary_keys},
- execution_options=execution_options,
- ).unique(),
- lambda x: x[0],
- ):
- data[k].extend(vv[1] for vv in v)
-
- for key, state, state_dict, overwrite in chunk:
- if not overwrite and self.key in state_dict:
- continue
-
- collection = data.get(key, _empty_result)
-
- if not uselist and collection:
- if len(collection) > 1:
- util.warn(
- "Multiple rows returned with "
- "uselist=False for eagerly-loaded "
- "attribute '%s' " % self
- )
- state.get_impl(self.key).set_committed_value(
- state, state_dict, collection[0]
- )
- else:
- # note that empty tuple set on uselist=False sets the
- # value to None
- state.get_impl(self.key).set_committed_value(
- state, state_dict, collection
- )
-
-
-def single_parent_validator(desc, prop):
- def _do_check(state, value, oldvalue, initiator):
- if value is not None and initiator.key == prop.key:
- hasparent = initiator.hasparent(attributes.instance_state(value))
- if hasparent and oldvalue is not value:
- raise sa_exc.InvalidRequestError(
- "Instance %s is already associated with an instance "
- "of %s via its %s attribute, and is only allowed a "
- "single parent."
- % (orm_util.instance_str(value), state.class_, prop),
- code="bbf1",
- )
- return value
-
- def append(state, value, initiator):
- return _do_check(state, value, None, initiator)
-
- def set_(state, value, oldvalue, initiator):
- return _do_check(state, value, oldvalue, initiator)
-
- event.listen(
- desc, "append", append, raw=True, retval=True, active_history=True
- )
- event.listen(desc, "set", set_, raw=True, retval=True, active_history=True)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/strategy_options.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/strategy_options.py
deleted file mode 100644
index 25c6332..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/strategy_options.py
+++ /dev/null
@@ -1,2555 +0,0 @@
-# orm/strategy_options.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
-# mypy: allow-untyped-defs, allow-untyped-calls
-
-"""
-
-"""
-
-from __future__ import annotations
-
-import typing
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Dict
-from typing import Iterable
-from typing import Optional
-from typing import overload
-from typing import Sequence
-from typing import Tuple
-from typing import Type
-from typing import TypeVar
-from typing import Union
-
-from . import util as orm_util
-from ._typing import insp_is_aliased_class
-from ._typing import insp_is_attribute
-from ._typing import insp_is_mapper
-from ._typing import insp_is_mapper_property
-from .attributes import QueryableAttribute
-from .base import InspectionAttr
-from .interfaces import LoaderOption
-from .path_registry import _DEFAULT_TOKEN
-from .path_registry import _StrPathToken
-from .path_registry import _WILDCARD_TOKEN
-from .path_registry import AbstractEntityRegistry
-from .path_registry import path_is_property
-from .path_registry import PathRegistry
-from .path_registry import TokenRegistry
-from .util import _orm_full_deannotate
-from .util import AliasedInsp
-from .. import exc as sa_exc
-from .. import inspect
-from .. import util
-from ..sql import and_
-from ..sql import cache_key
-from ..sql import coercions
-from ..sql import roles
-from ..sql import traversals
-from ..sql import visitors
-from ..sql.base import _generative
-from ..util.typing import Final
-from ..util.typing import Literal
-from ..util.typing import Self
-
-_RELATIONSHIP_TOKEN: Final[Literal["relationship"]] = "relationship"
-_COLUMN_TOKEN: Final[Literal["column"]] = "column"
-
-_FN = TypeVar("_FN", bound="Callable[..., Any]")
-
-if typing.TYPE_CHECKING:
- from ._typing import _EntityType
- from ._typing import _InternalEntityType
- from .context import _MapperEntity
- from .context import ORMCompileState
- from .context import QueryContext
- from .interfaces import _StrategyKey
- from .interfaces import MapperProperty
- from .interfaces import ORMOption
- from .mapper import Mapper
- from .path_registry import _PathRepresentation
- from ..sql._typing import _ColumnExpressionArgument
- from ..sql._typing import _FromClauseArgument
- from ..sql.cache_key import _CacheKeyTraversalType
- from ..sql.cache_key import CacheKey
-
-
-_AttrType = Union[Literal["*"], "QueryableAttribute[Any]"]
-
-_WildcardKeyType = Literal["relationship", "column"]
-_StrategySpec = Dict[str, Any]
-_OptsType = Dict[str, Any]
-_AttrGroupType = Tuple[_AttrType, ...]
-
-
-class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
- __slots__ = ("propagate_to_loaders",)
-
- _is_strategy_option = True
- propagate_to_loaders: bool
-
- def contains_eager(
- self,
- attr: _AttrType,
- alias: Optional[_FromClauseArgument] = None,
- _is_chain: bool = False,
- ) -> Self:
- r"""Indicate that the given attribute should be eagerly loaded from
- columns stated manually in the query.
-
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
-
- The option is used in conjunction with an explicit join that loads
- the desired rows, i.e.::
-
- sess.query(Order).join(Order.user).options(
- contains_eager(Order.user)
- )
-
- The above query would join from the ``Order`` entity to its related
- ``User`` entity, and the returned ``Order`` objects would have the
- ``Order.user`` attribute pre-populated.
-
- It may also be used for customizing the entries in an eagerly loaded
- collection; queries will normally want to use the
- :ref:`orm_queryguide_populate_existing` execution option assuming the
- primary collection of parent objects may already have been loaded::
-
- sess.query(User).join(User.addresses).filter(
- Address.email_address.like("%@aol.com")
- ).options(contains_eager(User.addresses)).populate_existing()
-
- See the section :ref:`contains_eager` for complete usage details.
-
- .. seealso::
-
- :ref:`loading_toplevel`
-
- :ref:`contains_eager`
-
- """
- if alias is not None:
- if not isinstance(alias, str):
- coerced_alias = coercions.expect(roles.FromClauseRole, alias)
- else:
- util.warn_deprecated(
- "Passing a string name for the 'alias' argument to "
- "'contains_eager()` is deprecated, and will not work in a "
- "future release. Please use a sqlalchemy.alias() or "
- "sqlalchemy.orm.aliased() construct.",
- version="1.4",
- )
- coerced_alias = alias
-
- elif getattr(attr, "_of_type", None):
- assert isinstance(attr, QueryableAttribute)
- ot: Optional[_InternalEntityType[Any]] = inspect(attr._of_type)
- assert ot is not None
- coerced_alias = ot.selectable
- else:
- coerced_alias = None
-
- cloned = self._set_relationship_strategy(
- attr,
- {"lazy": "joined"},
- propagate_to_loaders=False,
- opts={"eager_from_alias": coerced_alias},
- _reconcile_to_other=True if _is_chain else None,
- )
- return cloned
-
- def load_only(self, *attrs: _AttrType, raiseload: bool = False) -> Self:
- r"""Indicate that for a particular entity, only the given list
- of column-based attribute names should be loaded; all others will be
- deferred.
-
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
-
- Example - given a class ``User``, load only the ``name`` and
- ``fullname`` attributes::
-
- session.query(User).options(load_only(User.name, User.fullname))
-
- Example - given a relationship ``User.addresses -> Address``, specify
- subquery loading for the ``User.addresses`` collection, but on each
- ``Address`` object load only the ``email_address`` attribute::
-
- session.query(User).options(
- subqueryload(User.addresses).load_only(Address.email_address)
- )
-
- For a statement that has multiple entities,
- the lead entity can be
- specifically referred to using the :class:`_orm.Load` constructor::
-
- stmt = (
- select(User, Address)
- .join(User.addresses)
- .options(
- Load(User).load_only(User.name, User.fullname),
- Load(Address).load_only(Address.email_address),
- )
- )
-
- When used together with the
- :ref:`populate_existing <orm_queryguide_populate_existing>`
- execution option only the attributes listed will be refreshed.
-
- :param \*attrs: Attributes to be loaded, all others will be deferred.
-
- :param raiseload: raise :class:`.InvalidRequestError` rather than
- lazy loading a value when a deferred attribute is accessed. Used
- to prevent unwanted SQL from being emitted.
-
- .. versionadded:: 2.0
-
- .. seealso::
-
- :ref:`orm_queryguide_column_deferral` - in the
- :ref:`queryguide_toplevel`
-
- :param \*attrs: Attributes to be loaded, all others will be deferred.
-
- :param raiseload: raise :class:`.InvalidRequestError` rather than
- lazy loading a value when a deferred attribute is accessed. Used
- to prevent unwanted SQL from being emitted.
-
- .. versionadded:: 2.0
-
- """
- cloned = self._set_column_strategy(
- attrs,
- {"deferred": False, "instrument": True},
- )
-
- wildcard_strategy = {"deferred": True, "instrument": True}
- if raiseload:
- wildcard_strategy["raiseload"] = True
-
- cloned = cloned._set_column_strategy(
- ("*",),
- wildcard_strategy,
- )
- return cloned
-
- def joinedload(
- self,
- attr: _AttrType,
- innerjoin: Optional[bool] = None,
- ) -> Self:
- """Indicate that the given attribute should be loaded using joined
- eager loading.
-
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
-
- examples::
-
- # joined-load the "orders" collection on "User"
- select(User).options(joinedload(User.orders))
-
- # joined-load Order.items and then Item.keywords
- select(Order).options(
- joinedload(Order.items).joinedload(Item.keywords)
- )
-
- # lazily load Order.items, but when Items are loaded,
- # joined-load the keywords collection
- select(Order).options(
- lazyload(Order.items).joinedload(Item.keywords)
- )
-
- :param innerjoin: if ``True``, indicates that the joined eager load
- should use an inner join instead of the default of left outer join::
-
- select(Order).options(joinedload(Order.user, innerjoin=True))
-
- In order to chain multiple eager joins together where some may be
- OUTER and others INNER, right-nested joins are used to link them::
-
- select(A).options(
- joinedload(A.bs, innerjoin=False).joinedload(
- B.cs, innerjoin=True
- )
- )
-
- The above query, linking A.bs via "outer" join and B.cs via "inner"
- join would render the joins as "a LEFT OUTER JOIN (b JOIN c)". When
- using older versions of SQLite (< 3.7.16), this form of JOIN is
- translated to use full subqueries as this syntax is otherwise not
- directly supported.
-
- The ``innerjoin`` flag can also be stated with the term ``"unnested"``.
- This indicates that an INNER JOIN should be used, *unless* the join
- is linked to a LEFT OUTER JOIN to the left, in which case it
- will render as LEFT OUTER JOIN. For example, supposing ``A.bs``
- is an outerjoin::
-
- select(A).options(
- joinedload(A.bs).joinedload(B.cs, innerjoin="unnested")
- )
-
-
- The above join will render as "a LEFT OUTER JOIN b LEFT OUTER JOIN c",
- rather than as "a LEFT OUTER JOIN (b JOIN c)".
-
- .. note:: The "unnested" flag does **not** affect the JOIN rendered
- from a many-to-many association table, e.g. a table configured as
- :paramref:`_orm.relationship.secondary`, to the target table; for
- correctness of results, these joins are always INNER and are
- therefore right-nested if linked to an OUTER join.
-
- .. note::
-
- The joins produced by :func:`_orm.joinedload` are **anonymously
- aliased**. The criteria by which the join proceeds cannot be
- modified, nor can the ORM-enabled :class:`_sql.Select` or legacy
- :class:`_query.Query` refer to these joins in any way, including
- ordering. See :ref:`zen_of_eager_loading` for further detail.
-
- To produce a specific SQL JOIN which is explicitly available, use
- :meth:`_sql.Select.join` and :meth:`_query.Query.join`. To combine
- explicit JOINs with eager loading of collections, use
- :func:`_orm.contains_eager`; see :ref:`contains_eager`.
-
- .. seealso::
-
- :ref:`loading_toplevel`
-
- :ref:`joined_eager_loading`
-
- """
- loader = self._set_relationship_strategy(
- attr,
- {"lazy": "joined"},
- opts=(
- {"innerjoin": innerjoin}
- if innerjoin is not None
- else util.EMPTY_DICT
- ),
- )
- return loader
-
- def subqueryload(self, attr: _AttrType) -> Self:
- """Indicate that the given attribute should be loaded using
- subquery eager loading.
-
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
-
- examples::
-
- # subquery-load the "orders" collection on "User"
- select(User).options(subqueryload(User.orders))
-
- # subquery-load Order.items and then Item.keywords
- select(Order).options(
- subqueryload(Order.items).subqueryload(Item.keywords)
- )
-
- # lazily load Order.items, but when Items are loaded,
- # subquery-load the keywords collection
- select(Order).options(
- lazyload(Order.items).subqueryload(Item.keywords)
- )
-
-
- .. seealso::
-
- :ref:`loading_toplevel`
-
- :ref:`subquery_eager_loading`
-
- """
- return self._set_relationship_strategy(attr, {"lazy": "subquery"})
-
- def selectinload(
- self,
- attr: _AttrType,
- recursion_depth: Optional[int] = None,
- ) -> Self:
- """Indicate that the given attribute should be loaded using
- SELECT IN eager loading.
-
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
-
- examples::
-
- # selectin-load the "orders" collection on "User"
- select(User).options(selectinload(User.orders))
-
- # selectin-load Order.items and then Item.keywords
- select(Order).options(
- selectinload(Order.items).selectinload(Item.keywords)
- )
-
- # lazily load Order.items, but when Items are loaded,
- # selectin-load the keywords collection
- select(Order).options(
- lazyload(Order.items).selectinload(Item.keywords)
- )
-
- :param recursion_depth: optional int; when set to a positive integer
- in conjunction with a self-referential relationship,
- indicates "selectin" loading will continue that many levels deep
- automatically until no items are found.
-
- .. note:: The :paramref:`_orm.selectinload.recursion_depth` option
- currently supports only self-referential relationships. There
- is not yet an option to automatically traverse recursive structures
- with more than one relationship involved.
-
- Additionally, the :paramref:`_orm.selectinload.recursion_depth`
- parameter is new and experimental and should be treated as "alpha"
- status for the 2.0 series.
-
- .. versionadded:: 2.0 added
- :paramref:`_orm.selectinload.recursion_depth`
-
-
- .. seealso::
-
- :ref:`loading_toplevel`
-
- :ref:`selectin_eager_loading`
-
- """
- return self._set_relationship_strategy(
- attr,
- {"lazy": "selectin"},
- opts={"recursion_depth": recursion_depth},
- )
-
- def lazyload(self, attr: _AttrType) -> Self:
- """Indicate that the given attribute should be loaded using "lazy"
- loading.
-
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
-
- .. seealso::
-
- :ref:`loading_toplevel`
-
- :ref:`lazy_loading`
-
- """
- return self._set_relationship_strategy(attr, {"lazy": "select"})
-
- def immediateload(
- self,
- attr: _AttrType,
- recursion_depth: Optional[int] = None,
- ) -> Self:
- """Indicate that the given attribute should be loaded using
- an immediate load with a per-attribute SELECT statement.
-
- The load is achieved using the "lazyloader" strategy and does not
- fire off any additional eager loaders.
-
- The :func:`.immediateload` option is superseded in general
- by the :func:`.selectinload` option, which performs the same task
- more efficiently by emitting a SELECT for all loaded objects.
-
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
-
- :param recursion_depth: optional int; when set to a positive integer
- in conjunction with a self-referential relationship,
- indicates "selectin" loading will continue that many levels deep
- automatically until no items are found.
-
- .. note:: The :paramref:`_orm.immediateload.recursion_depth` option
- currently supports only self-referential relationships. There
- is not yet an option to automatically traverse recursive structures
- with more than one relationship involved.
-
- .. warning:: This parameter is new and experimental and should be
- treated as "alpha" status
-
- .. versionadded:: 2.0 added
- :paramref:`_orm.immediateload.recursion_depth`
-
-
- .. seealso::
-
- :ref:`loading_toplevel`
-
- :ref:`selectin_eager_loading`
-
- """
- loader = self._set_relationship_strategy(
- attr,
- {"lazy": "immediate"},
- opts={"recursion_depth": recursion_depth},
- )
- return loader
-
- def noload(self, attr: _AttrType) -> Self:
- """Indicate that the given relationship attribute should remain
- unloaded.
-
- The relationship attribute will return ``None`` when accessed without
- producing any loading effect.
-
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
-
- :func:`_orm.noload` applies to :func:`_orm.relationship` attributes
- only.
-
- .. note:: Setting this loading strategy as the default strategy
- for a relationship using the :paramref:`.orm.relationship.lazy`
- parameter may cause issues with flushes, such if a delete operation
- needs to load related objects and instead ``None`` was returned.
-
- .. seealso::
-
- :ref:`loading_toplevel`
-
- """
-
- return self._set_relationship_strategy(attr, {"lazy": "noload"})
-
- def raiseload(self, attr: _AttrType, sql_only: bool = False) -> Self:
- """Indicate that the given attribute should raise an error if accessed.
-
- A relationship attribute configured with :func:`_orm.raiseload` will
- raise an :exc:`~sqlalchemy.exc.InvalidRequestError` upon access. The
- typical way this is useful is when an application is attempting to
- ensure that all relationship attributes that are accessed in a
- particular context would have been already loaded via eager loading.
- Instead of having to read through SQL logs to ensure lazy loads aren't
- occurring, this strategy will cause them to raise immediately.
-
- :func:`_orm.raiseload` applies to :func:`_orm.relationship` attributes
- only. In order to apply raise-on-SQL behavior to a column-based
- attribute, use the :paramref:`.orm.defer.raiseload` parameter on the
- :func:`.defer` loader option.
-
- :param sql_only: if True, raise only if the lazy load would emit SQL,
- but not if it is only checking the identity map, or determining that
- the related value should just be None due to missing keys. When False,
- the strategy will raise for all varieties of relationship loading.
-
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
-
- .. seealso::
-
- :ref:`loading_toplevel`
-
- :ref:`prevent_lazy_with_raiseload`
-
- :ref:`orm_queryguide_deferred_raiseload`
-
- """
-
- return self._set_relationship_strategy(
- attr, {"lazy": "raise_on_sql" if sql_only else "raise"}
- )
-
- def defaultload(self, attr: _AttrType) -> Self:
- """Indicate an attribute should load using its predefined loader style.
-
- The behavior of this loading option is to not change the current
- loading style of the attribute, meaning that the previously configured
- one is used or, if no previous style was selected, the default
- loading will be used.
-
- This method is used to link to other loader options further into
- a chain of attributes without altering the loader style of the links
- along the chain. For example, to set joined eager loading for an
- element of an element::
-
- session.query(MyClass).options(
- defaultload(MyClass.someattribute).joinedload(
- MyOtherClass.someotherattribute
- )
- )
-
- :func:`.defaultload` is also useful for setting column-level options on
- a related class, namely that of :func:`.defer` and :func:`.undefer`::
-
- session.scalars(
- select(MyClass).options(
- defaultload(MyClass.someattribute)
- .defer("some_column")
- .undefer("some_other_column")
- )
- )
-
- .. seealso::
-
- :ref:`orm_queryguide_relationship_sub_options`
-
- :meth:`_orm.Load.options`
-
- """
- return self._set_relationship_strategy(attr, None)
-
- def defer(self, key: _AttrType, raiseload: bool = False) -> Self:
- r"""Indicate that the given column-oriented attribute should be
- deferred, e.g. not loaded until accessed.
-
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
-
- e.g.::
-
- from sqlalchemy.orm import defer
-
- session.query(MyClass).options(
- defer(MyClass.attribute_one),
- defer(MyClass.attribute_two)
- )
-
- To specify a deferred load of an attribute on a related class,
- the path can be specified one token at a time, specifying the loading
- style for each link along the chain. To leave the loading style
- for a link unchanged, use :func:`_orm.defaultload`::
-
- session.query(MyClass).options(
- defaultload(MyClass.someattr).defer(RelatedClass.some_column)
- )
-
- Multiple deferral options related to a relationship can be bundled
- at once using :meth:`_orm.Load.options`::
-
-
- select(MyClass).options(
- defaultload(MyClass.someattr).options(
- defer(RelatedClass.some_column),
- defer(RelatedClass.some_other_column),
- defer(RelatedClass.another_column)
- )
- )
-
- :param key: Attribute to be deferred.
-
- :param raiseload: raise :class:`.InvalidRequestError` rather than
- lazy loading a value when the deferred attribute is accessed. Used
- to prevent unwanted SQL from being emitted.
-
- .. versionadded:: 1.4
-
- .. seealso::
-
- :ref:`orm_queryguide_column_deferral` - in the
- :ref:`queryguide_toplevel`
-
- :func:`_orm.load_only`
-
- :func:`_orm.undefer`
-
- """
- strategy = {"deferred": True, "instrument": True}
- if raiseload:
- strategy["raiseload"] = True
- return self._set_column_strategy((key,), strategy)
-
- def undefer(self, key: _AttrType) -> Self:
- r"""Indicate that the given column-oriented attribute should be
- undeferred, e.g. specified within the SELECT statement of the entity
- as a whole.
-
- The column being undeferred is typically set up on the mapping as a
- :func:`.deferred` attribute.
-
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
-
- Examples::
-
- # undefer two columns
- session.query(MyClass).options(
- undefer(MyClass.col1), undefer(MyClass.col2)
- )
-
- # undefer all columns specific to a single class using Load + *
- session.query(MyClass, MyOtherClass).options(
- Load(MyClass).undefer("*")
- )
-
- # undefer a column on a related object
- select(MyClass).options(
- defaultload(MyClass.items).undefer(MyClass.text)
- )
-
- :param key: Attribute to be undeferred.
-
- .. seealso::
-
- :ref:`orm_queryguide_column_deferral` - in the
- :ref:`queryguide_toplevel`
-
- :func:`_orm.defer`
-
- :func:`_orm.undefer_group`
-
- """
- return self._set_column_strategy(
- (key,), {"deferred": False, "instrument": True}
- )
-
- def undefer_group(self, name: str) -> Self:
- """Indicate that columns within the given deferred group name should be
- undeferred.
-
- The columns being undeferred are set up on the mapping as
- :func:`.deferred` attributes and include a "group" name.
-
- E.g::
-
- session.query(MyClass).options(undefer_group("large_attrs"))
-
- To undefer a group of attributes on a related entity, the path can be
- spelled out using relationship loader options, such as
- :func:`_orm.defaultload`::
-
- select(MyClass).options(
- defaultload("someattr").undefer_group("large_attrs")
- )
-
- .. seealso::
-
- :ref:`orm_queryguide_column_deferral` - in the
- :ref:`queryguide_toplevel`
-
- :func:`_orm.defer`
-
- :func:`_orm.undefer`
-
- """
- return self._set_column_strategy(
- (_WILDCARD_TOKEN,), None, {f"undefer_group_{name}": True}
- )
-
- def with_expression(
- self,
- key: _AttrType,
- expression: _ColumnExpressionArgument[Any],
- ) -> Self:
- r"""Apply an ad-hoc SQL expression to a "deferred expression"
- attribute.
-
- This option is used in conjunction with the
- :func:`_orm.query_expression` mapper-level construct that indicates an
- attribute which should be the target of an ad-hoc SQL expression.
-
- E.g.::
-
- stmt = select(SomeClass).options(
- with_expression(SomeClass.x_y_expr, SomeClass.x + SomeClass.y)
- )
-
- .. versionadded:: 1.2
-
- :param key: Attribute to be populated
-
- :param expr: SQL expression to be applied to the attribute.
-
- .. seealso::
-
- :ref:`orm_queryguide_with_expression` - background and usage
- examples
-
- """
-
- expression = _orm_full_deannotate(
- coercions.expect(roles.LabeledColumnExprRole, expression)
- )
-
- return self._set_column_strategy(
- (key,), {"query_expression": True}, extra_criteria=(expression,)
- )
-
- def selectin_polymorphic(self, classes: Iterable[Type[Any]]) -> Self:
- """Indicate an eager load should take place for all attributes
- specific to a subclass.
-
- This uses an additional SELECT with IN against all matched primary
- key values, and is the per-query analogue to the ``"selectin"``
- setting on the :paramref:`.mapper.polymorphic_load` parameter.
-
- .. versionadded:: 1.2
-
- .. seealso::
-
- :ref:`polymorphic_selectin`
-
- """
- self = self._set_class_strategy(
- {"selectinload_polymorphic": True},
- opts={
- "entities": tuple(
- sorted((inspect(cls) for cls in classes), key=id)
- )
- },
- )
- return self
-
- @overload
- def _coerce_strat(self, strategy: _StrategySpec) -> _StrategyKey: ...
-
- @overload
- def _coerce_strat(self, strategy: Literal[None]) -> None: ...
-
- def _coerce_strat(
- self, strategy: Optional[_StrategySpec]
- ) -> Optional[_StrategyKey]:
- if strategy is not None:
- strategy_key = tuple(sorted(strategy.items()))
- else:
- strategy_key = None
- return strategy_key
-
- @_generative
- def _set_relationship_strategy(
- self,
- attr: _AttrType,
- strategy: Optional[_StrategySpec],
- propagate_to_loaders: bool = True,
- opts: Optional[_OptsType] = None,
- _reconcile_to_other: Optional[bool] = None,
- ) -> Self:
- strategy_key = self._coerce_strat(strategy)
-
- self._clone_for_bind_strategy(
- (attr,),
- strategy_key,
- _RELATIONSHIP_TOKEN,
- opts=opts,
- propagate_to_loaders=propagate_to_loaders,
- reconcile_to_other=_reconcile_to_other,
- )
- return self
-
- @_generative
- def _set_column_strategy(
- self,
- attrs: Tuple[_AttrType, ...],
- strategy: Optional[_StrategySpec],
- opts: Optional[_OptsType] = None,
- extra_criteria: Optional[Tuple[Any, ...]] = None,
- ) -> Self:
- strategy_key = self._coerce_strat(strategy)
-
- self._clone_for_bind_strategy(
- attrs,
- strategy_key,
- _COLUMN_TOKEN,
- opts=opts,
- attr_group=attrs,
- extra_criteria=extra_criteria,
- )
- return self
-
- @_generative
- def _set_generic_strategy(
- self,
- attrs: Tuple[_AttrType, ...],
- strategy: _StrategySpec,
- _reconcile_to_other: Optional[bool] = None,
- ) -> Self:
- strategy_key = self._coerce_strat(strategy)
- self._clone_for_bind_strategy(
- attrs,
- strategy_key,
- None,
- propagate_to_loaders=True,
- reconcile_to_other=_reconcile_to_other,
- )
- return self
-
- @_generative
- def _set_class_strategy(
- self, strategy: _StrategySpec, opts: _OptsType
- ) -> Self:
- strategy_key = self._coerce_strat(strategy)
-
- self._clone_for_bind_strategy(None, strategy_key, None, opts=opts)
- return self
-
- def _apply_to_parent(self, parent: Load) -> None:
- """apply this :class:`_orm._AbstractLoad` object as a sub-option o
- a :class:`_orm.Load` object.
-
- Implementation is provided by subclasses.
-
- """
- raise NotImplementedError()
-
- def options(self, *opts: _AbstractLoad) -> Self:
- r"""Apply a series of options as sub-options to this
- :class:`_orm._AbstractLoad` object.
-
- Implementation is provided by subclasses.
-
- """
- raise NotImplementedError()
-
- def _clone_for_bind_strategy(
- self,
- attrs: Optional[Tuple[_AttrType, ...]],
- strategy: Optional[_StrategyKey],
- wildcard_key: Optional[_WildcardKeyType],
- opts: Optional[_OptsType] = None,
- attr_group: Optional[_AttrGroupType] = None,
- propagate_to_loaders: bool = True,
- reconcile_to_other: Optional[bool] = None,
- extra_criteria: Optional[Tuple[Any, ...]] = None,
- ) -> Self:
- raise NotImplementedError()
-
- def process_compile_state_replaced_entities(
- self,
- compile_state: ORMCompileState,
- mapper_entities: Sequence[_MapperEntity],
- ) -> None:
- if not compile_state.compile_options._enable_eagerloads:
- return
-
- # process is being run here so that the options given are validated
- # against what the lead entities were, as well as to accommodate
- # for the entities having been replaced with equivalents
- self._process(
- compile_state,
- mapper_entities,
- not bool(compile_state.current_path),
- )
-
- def process_compile_state(self, compile_state: ORMCompileState) -> None:
- if not compile_state.compile_options._enable_eagerloads:
- return
-
- self._process(
- compile_state,
- compile_state._lead_mapper_entities,
- not bool(compile_state.current_path)
- and not compile_state.compile_options._for_refresh_state,
- )
-
- def _process(
- self,
- compile_state: ORMCompileState,
- mapper_entities: Sequence[_MapperEntity],
- raiseerr: bool,
- ) -> None:
- """implemented by subclasses"""
- raise NotImplementedError()
-
- @classmethod
- def _chop_path(
- cls,
- to_chop: _PathRepresentation,
- path: PathRegistry,
- debug: bool = False,
- ) -> Optional[_PathRepresentation]:
- i = -1
-
- for i, (c_token, p_token) in enumerate(
- zip(to_chop, path.natural_path)
- ):
- if isinstance(c_token, str):
- if i == 0 and (
- c_token.endswith(f":{_DEFAULT_TOKEN}")
- or c_token.endswith(f":{_WILDCARD_TOKEN}")
- ):
- return to_chop
- elif (
- c_token != f"{_RELATIONSHIP_TOKEN}:{_WILDCARD_TOKEN}"
- and c_token != p_token.key # type: ignore
- ):
- return None
-
- if c_token is p_token:
- continue
- elif (
- isinstance(c_token, InspectionAttr)
- and insp_is_mapper(c_token)
- and insp_is_mapper(p_token)
- and c_token.isa(p_token)
- ):
- continue
-
- else:
- return None
- return to_chop[i + 1 :]
-
-
-class Load(_AbstractLoad):
- """Represents loader options which modify the state of a
- ORM-enabled :class:`_sql.Select` or a legacy :class:`_query.Query` in
- order to affect how various mapped attributes are loaded.
-
- The :class:`_orm.Load` object is in most cases used implicitly behind the
- scenes when one makes use of a query option like :func:`_orm.joinedload`,
- :func:`_orm.defer`, or similar. It typically is not instantiated directly
- except for in some very specific cases.
-
- .. seealso::
-
- :ref:`orm_queryguide_relationship_per_entity_wildcard` - illustrates an
- example where direct use of :class:`_orm.Load` may be useful
-
- """
-
- __slots__ = (
- "path",
- "context",
- "additional_source_entities",
- )
-
- _traverse_internals = [
- ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key),
- (
- "context",
- visitors.InternalTraversal.dp_has_cache_key_list,
- ),
- ("propagate_to_loaders", visitors.InternalTraversal.dp_boolean),
- (
- "additional_source_entities",
- visitors.InternalTraversal.dp_has_cache_key_list,
- ),
- ]
- _cache_key_traversal = None
-
- path: PathRegistry
- context: Tuple[_LoadElement, ...]
- additional_source_entities: Tuple[_InternalEntityType[Any], ...]
-
- def __init__(self, entity: _EntityType[Any]):
- insp = cast("Union[Mapper[Any], AliasedInsp[Any]]", inspect(entity))
- insp._post_inspect
-
- self.path = insp._path_registry
- self.context = ()
- self.propagate_to_loaders = False
- self.additional_source_entities = ()
-
- def __str__(self) -> str:
- return f"Load({self.path[0]})"
-
- @classmethod
- def _construct_for_existing_path(
- cls, path: AbstractEntityRegistry
- ) -> Load:
- load = cls.__new__(cls)
- load.path = path
- load.context = ()
- load.propagate_to_loaders = False
- load.additional_source_entities = ()
- return load
-
- def _adapt_cached_option_to_uncached_option(
- self, context: QueryContext, uncached_opt: ORMOption
- ) -> ORMOption:
- if uncached_opt is self:
- return self
- return self._adjust_for_extra_criteria(context)
-
- def _prepend_path(self, path: PathRegistry) -> Load:
- cloned = self._clone()
- cloned.context = tuple(
- element._prepend_path(path) for element in self.context
- )
- return cloned
-
- def _adjust_for_extra_criteria(self, context: QueryContext) -> Load:
- """Apply the current bound parameters in a QueryContext to all
- occurrences "extra_criteria" stored within this ``Load`` object,
- returning a new instance of this ``Load`` object.
-
- """
-
- # avoid generating cache keys for the queries if we don't
- # actually have any extra_criteria options, which is the
- # common case
- for value in self.context:
- if value._extra_criteria:
- break
- else:
- return self
-
- replacement_cache_key = context.query._generate_cache_key()
-
- if replacement_cache_key is None:
- return self
-
- orig_query = context.compile_state.select_statement
- orig_cache_key = orig_query._generate_cache_key()
- assert orig_cache_key is not None
-
- def process(
- opt: _LoadElement,
- replacement_cache_key: CacheKey,
- orig_cache_key: CacheKey,
- ) -> _LoadElement:
- cloned_opt = opt._clone()
-
- cloned_opt._extra_criteria = tuple(
- replacement_cache_key._apply_params_to_element(
- orig_cache_key, crit
- )
- for crit in cloned_opt._extra_criteria
- )
-
- return cloned_opt
-
- cloned = self._clone()
- cloned.context = tuple(
- (
- process(value, replacement_cache_key, orig_cache_key)
- if value._extra_criteria
- else value
- )
- for value in self.context
- )
- return cloned
-
- def _reconcile_query_entities_with_us(self, mapper_entities, raiseerr):
- """called at process time to allow adjustment of the root
- entity inside of _LoadElement objects.
-
- """
- path = self.path
-
- ezero = None
- for ent in mapper_entities:
- ezero = ent.entity_zero
- if ezero and orm_util._entity_corresponds_to(
- # technically this can be a token also, but this is
- # safe to pass to _entity_corresponds_to()
- ezero,
- cast("_InternalEntityType[Any]", path[0]),
- ):
- return ezero
-
- return None
-
- def _process(
- self,
- compile_state: ORMCompileState,
- mapper_entities: Sequence[_MapperEntity],
- raiseerr: bool,
- ) -> None:
- reconciled_lead_entity = self._reconcile_query_entities_with_us(
- mapper_entities, raiseerr
- )
-
- for loader in self.context:
- loader.process_compile_state(
- self,
- compile_state,
- mapper_entities,
- reconciled_lead_entity,
- raiseerr,
- )
-
- def _apply_to_parent(self, parent: Load) -> None:
- """apply this :class:`_orm.Load` object as a sub-option of another
- :class:`_orm.Load` object.
-
- This method is used by the :meth:`_orm.Load.options` method.
-
- """
- cloned = self._generate()
-
- assert cloned.propagate_to_loaders == self.propagate_to_loaders
-
- if not any(
- orm_util._entity_corresponds_to_use_path_impl(
- elem, cloned.path.odd_element(0)
- )
- for elem in (parent.path.odd_element(-1),)
- + parent.additional_source_entities
- ):
- if len(cloned.path) > 1:
- attrname = cloned.path[1]
- parent_entity = cloned.path[0]
- else:
- attrname = cloned.path[0]
- parent_entity = cloned.path[0]
- _raise_for_does_not_link(parent.path, attrname, parent_entity)
-
- cloned.path = PathRegistry.coerce(parent.path[0:-1] + cloned.path[:])
-
- if self.context:
- cloned.context = tuple(
- value._prepend_path_from(parent) for value in self.context
- )
-
- if cloned.context:
- parent.context += cloned.context
- parent.additional_source_entities += (
- cloned.additional_source_entities
- )
-
- @_generative
- def options(self, *opts: _AbstractLoad) -> Self:
- r"""Apply a series of options as sub-options to this
- :class:`_orm.Load`
- object.
-
- E.g.::
-
- query = session.query(Author)
- query = query.options(
- joinedload(Author.book).options(
- load_only(Book.summary, Book.excerpt),
- joinedload(Book.citations).options(
- joinedload(Citation.author)
- )
- )
- )
-
- :param \*opts: A series of loader option objects (ultimately
- :class:`_orm.Load` objects) which should be applied to the path
- specified by this :class:`_orm.Load` object.
-
- .. versionadded:: 1.3.6
-
- .. seealso::
-
- :func:`.defaultload`
-
- :ref:`orm_queryguide_relationship_sub_options`
-
- """
- for opt in opts:
- try:
- opt._apply_to_parent(self)
- except AttributeError as ae:
- if not isinstance(opt, _AbstractLoad):
- raise sa_exc.ArgumentError(
- f"Loader option {opt} is not compatible with the "
- "Load.options() method."
- ) from ae
- else:
- raise
- return self
-
- def _clone_for_bind_strategy(
- self,
- attrs: Optional[Tuple[_AttrType, ...]],
- strategy: Optional[_StrategyKey],
- wildcard_key: Optional[_WildcardKeyType],
- opts: Optional[_OptsType] = None,
- attr_group: Optional[_AttrGroupType] = None,
- propagate_to_loaders: bool = True,
- reconcile_to_other: Optional[bool] = None,
- extra_criteria: Optional[Tuple[Any, ...]] = None,
- ) -> Self:
- # for individual strategy that needs to propagate, set the whole
- # Load container to also propagate, so that it shows up in
- # InstanceState.load_options
- if propagate_to_loaders:
- self.propagate_to_loaders = True
-
- if self.path.is_token:
- raise sa_exc.ArgumentError(
- "Wildcard token cannot be followed by another entity"
- )
-
- elif path_is_property(self.path):
- # re-use the lookup which will raise a nicely formatted
- # LoaderStrategyException
- if strategy:
- self.path.prop._strategy_lookup(self.path.prop, strategy[0])
- else:
- raise sa_exc.ArgumentError(
- f"Mapped attribute '{self.path.prop}' does not "
- "refer to a mapped entity"
- )
-
- if attrs is None:
- load_element = _ClassStrategyLoad.create(
- self.path,
- None,
- strategy,
- wildcard_key,
- opts,
- propagate_to_loaders,
- attr_group=attr_group,
- reconcile_to_other=reconcile_to_other,
- extra_criteria=extra_criteria,
- )
- if load_element:
- self.context += (load_element,)
- assert opts is not None
- self.additional_source_entities += cast(
- "Tuple[_InternalEntityType[Any]]", opts["entities"]
- )
-
- else:
- for attr in attrs:
- if isinstance(attr, str):
- load_element = _TokenStrategyLoad.create(
- self.path,
- attr,
- strategy,
- wildcard_key,
- opts,
- propagate_to_loaders,
- attr_group=attr_group,
- reconcile_to_other=reconcile_to_other,
- extra_criteria=extra_criteria,
- )
- else:
- load_element = _AttributeStrategyLoad.create(
- self.path,
- attr,
- strategy,
- wildcard_key,
- opts,
- propagate_to_loaders,
- attr_group=attr_group,
- reconcile_to_other=reconcile_to_other,
- extra_criteria=extra_criteria,
- )
-
- if load_element:
- # for relationship options, update self.path on this Load
- # object with the latest path.
- if wildcard_key is _RELATIONSHIP_TOKEN:
- self.path = load_element.path
- self.context += (load_element,)
-
- # this seems to be effective for selectinloader,
- # giving the extra match to one more level deep.
- # but does not work for immediateloader, which still
- # must add additional options at load time
- if load_element.local_opts.get("recursion_depth", False):
- r1 = load_element._recurse()
- self.context += (r1,)
-
- return self
-
- def __getstate__(self):
- d = self._shallow_to_dict()
- d["path"] = self.path.serialize()
- return d
-
- def __setstate__(self, state):
- state["path"] = PathRegistry.deserialize(state["path"])
- self._shallow_from_dict(state)
-
-
-class _WildcardLoad(_AbstractLoad):
- """represent a standalone '*' load operation"""
-
- __slots__ = ("strategy", "path", "local_opts")
-
- _traverse_internals = [
- ("strategy", visitors.ExtendedInternalTraversal.dp_plain_obj),
- ("path", visitors.ExtendedInternalTraversal.dp_plain_obj),
- (
- "local_opts",
- visitors.ExtendedInternalTraversal.dp_string_multi_dict,
- ),
- ]
- cache_key_traversal: _CacheKeyTraversalType = None
-
- strategy: Optional[Tuple[Any, ...]]
- local_opts: _OptsType
- path: Union[Tuple[()], Tuple[str]]
- propagate_to_loaders = False
-
- def __init__(self) -> None:
- self.path = ()
- self.strategy = None
- self.local_opts = util.EMPTY_DICT
-
- def _clone_for_bind_strategy(
- self,
- attrs,
- strategy,
- wildcard_key,
- opts=None,
- attr_group=None,
- propagate_to_loaders=True,
- reconcile_to_other=None,
- extra_criteria=None,
- ):
- assert attrs is not None
- attr = attrs[0]
- assert (
- wildcard_key
- and isinstance(attr, str)
- and attr in (_WILDCARD_TOKEN, _DEFAULT_TOKEN)
- )
-
- attr = f"{wildcard_key}:{attr}"
-
- self.strategy = strategy
- self.path = (attr,)
- if opts:
- self.local_opts = util.immutabledict(opts)
-
- assert extra_criteria is None
-
- def options(self, *opts: _AbstractLoad) -> Self:
- raise NotImplementedError("Star option does not support sub-options")
-
- def _apply_to_parent(self, parent: Load) -> None:
- """apply this :class:`_orm._WildcardLoad` object as a sub-option of
- a :class:`_orm.Load` object.
-
- This method is used by the :meth:`_orm.Load.options` method. Note
- that :class:`_orm.WildcardLoad` itself can't have sub-options, but
- it may be used as the sub-option of a :class:`_orm.Load` object.
-
- """
- assert self.path
- attr = self.path[0]
- if attr.endswith(_DEFAULT_TOKEN):
- attr = f"{attr.split(':')[0]}:{_WILDCARD_TOKEN}"
-
- effective_path = cast(AbstractEntityRegistry, parent.path).token(attr)
-
- assert effective_path.is_token
-
- loader = _TokenStrategyLoad.create(
- effective_path,
- None,
- self.strategy,
- None,
- self.local_opts,
- self.propagate_to_loaders,
- )
-
- parent.context += (loader,)
-
- def _process(self, compile_state, mapper_entities, raiseerr):
- is_refresh = compile_state.compile_options._for_refresh_state
-
- if is_refresh and not self.propagate_to_loaders:
- return
-
- entities = [ent.entity_zero for ent in mapper_entities]
- current_path = compile_state.current_path
-
- start_path: _PathRepresentation = self.path
-
- if current_path:
- # TODO: no cases in test suite where we actually get
- # None back here
- new_path = self._chop_path(start_path, current_path)
- if new_path is None:
- return
-
- # chop_path does not actually "chop" a wildcard token path,
- # just returns it
- assert new_path == start_path
-
- # start_path is a single-token tuple
- assert start_path and len(start_path) == 1
-
- token = start_path[0]
- assert isinstance(token, str)
- entity = self._find_entity_basestring(entities, token, raiseerr)
-
- if not entity:
- return
-
- path_element = entity
-
- # transfer our entity-less state into a Load() object
- # with a real entity path. Start with the lead entity
- # we just located, then go through the rest of our path
- # tokens and populate into the Load().
-
- assert isinstance(token, str)
- loader = _TokenStrategyLoad.create(
- path_element._path_registry,
- token,
- self.strategy,
- None,
- self.local_opts,
- self.propagate_to_loaders,
- raiseerr=raiseerr,
- )
- if not loader:
- return
-
- assert loader.path.is_token
-
- # don't pass a reconciled lead entity here
- loader.process_compile_state(
- self, compile_state, mapper_entities, None, raiseerr
- )
-
- return loader
-
- def _find_entity_basestring(
- self,
- entities: Iterable[_InternalEntityType[Any]],
- token: str,
- raiseerr: bool,
- ) -> Optional[_InternalEntityType[Any]]:
- if token.endswith(f":{_WILDCARD_TOKEN}"):
- if len(list(entities)) != 1:
- if raiseerr:
- raise sa_exc.ArgumentError(
- "Can't apply wildcard ('*') or load_only() "
- f"loader option to multiple entities "
- f"{', '.join(str(ent) for ent in entities)}. Specify "
- "loader options for each entity individually, such as "
- f"""{
- ", ".join(
- f"Load({ent}).some_option('*')"
- for ent in entities
- )
- }."""
- )
- elif token.endswith(_DEFAULT_TOKEN):
- raiseerr = False
-
- for ent in entities:
- # return only the first _MapperEntity when searching
- # based on string prop name. Ideally object
- # attributes are used to specify more exactly.
- return ent
- else:
- if raiseerr:
- raise sa_exc.ArgumentError(
- "Query has only expression-based entities - "
- f'can\'t find property named "{token}".'
- )
- else:
- return None
-
- def __getstate__(self) -> Dict[str, Any]:
- d = self._shallow_to_dict()
- return d
-
- def __setstate__(self, state: Dict[str, Any]) -> None:
- self._shallow_from_dict(state)
-
-
-class _LoadElement(
- cache_key.HasCacheKey, traversals.HasShallowCopy, visitors.Traversible
-):
- """represents strategy information to select for a LoaderStrategy
- and pass options to it.
-
- :class:`._LoadElement` objects provide the inner datastructure
- stored by a :class:`_orm.Load` object and are also the object passed
- to methods like :meth:`.LoaderStrategy.setup_query`.
-
- .. versionadded:: 2.0
-
- """
-
- __slots__ = (
- "path",
- "strategy",
- "propagate_to_loaders",
- "local_opts",
- "_extra_criteria",
- "_reconcile_to_other",
- )
- __visit_name__ = "load_element"
-
- _traverse_internals = [
- ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key),
- ("strategy", visitors.ExtendedInternalTraversal.dp_plain_obj),
- (
- "local_opts",
- visitors.ExtendedInternalTraversal.dp_string_multi_dict,
- ),
- ("_extra_criteria", visitors.InternalTraversal.dp_clauseelement_list),
- ("propagate_to_loaders", visitors.InternalTraversal.dp_plain_obj),
- ("_reconcile_to_other", visitors.InternalTraversal.dp_plain_obj),
- ]
- _cache_key_traversal = None
-
- _extra_criteria: Tuple[Any, ...]
-
- _reconcile_to_other: Optional[bool]
- strategy: Optional[_StrategyKey]
- path: PathRegistry
- propagate_to_loaders: bool
-
- local_opts: util.immutabledict[str, Any]
-
- is_token_strategy: bool
- is_class_strategy: bool
-
- def __hash__(self) -> int:
- return id(self)
-
- def __eq__(self, other):
- return traversals.compare(self, other)
-
- @property
- def is_opts_only(self) -> bool:
- return bool(self.local_opts and self.strategy is None)
-
- def _clone(self, **kw: Any) -> _LoadElement:
- cls = self.__class__
- s = cls.__new__(cls)
-
- self._shallow_copy_to(s)
- return s
-
- def _update_opts(self, **kw: Any) -> _LoadElement:
- new = self._clone()
- new.local_opts = new.local_opts.union(kw)
- return new
-
- def __getstate__(self) -> Dict[str, Any]:
- d = self._shallow_to_dict()
- d["path"] = self.path.serialize()
- return d
-
- def __setstate__(self, state: Dict[str, Any]) -> None:
- state["path"] = PathRegistry.deserialize(state["path"])
- self._shallow_from_dict(state)
-
- def _raise_for_no_match(self, parent_loader, mapper_entities):
- path = parent_loader.path
-
- found_entities = False
- for ent in mapper_entities:
- ezero = ent.entity_zero
- if ezero:
- found_entities = True
- break
-
- if not found_entities:
- raise sa_exc.ArgumentError(
- "Query has only expression-based entities; "
- f"attribute loader options for {path[0]} can't "
- "be applied here."
- )
- else:
- raise sa_exc.ArgumentError(
- f"Mapped class {path[0]} does not apply to any of the "
- f"root entities in this query, e.g. "
- f"""{
- ", ".join(
- str(x.entity_zero)
- for x in mapper_entities if x.entity_zero
- )}. Please """
- "specify the full path "
- "from one of the root entities to the target "
- "attribute. "
- )
-
- def _adjust_effective_path_for_current_path(
- self, effective_path: PathRegistry, current_path: PathRegistry
- ) -> Optional[PathRegistry]:
- """receives the 'current_path' entry from an :class:`.ORMCompileState`
- instance, which is set during lazy loads and secondary loader strategy
- loads, and adjusts the given path to be relative to the
- current_path.
-
- E.g. given a loader path and current path::
-
- lp: User -> orders -> Order -> items -> Item -> keywords -> Keyword
-
- cp: User -> orders -> Order -> items
-
- The adjusted path would be::
-
- Item -> keywords -> Keyword
-
-
- """
- chopped_start_path = Load._chop_path(
- effective_path.natural_path, current_path
- )
- if not chopped_start_path:
- return None
-
- tokens_removed_from_start_path = len(effective_path) - len(
- chopped_start_path
- )
-
- loader_lead_path_element = self.path[tokens_removed_from_start_path]
-
- effective_path = PathRegistry.coerce(
- (loader_lead_path_element,) + chopped_start_path[1:]
- )
-
- return effective_path
-
- def _init_path(
- self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
- ):
- """Apply ORM attributes and/or wildcard to an existing path, producing
- a new path.
-
- This method is used within the :meth:`.create` method to initialize
- a :class:`._LoadElement` object.
-
- """
- raise NotImplementedError()
-
- def _prepare_for_compile_state(
- self,
- parent_loader,
- compile_state,
- mapper_entities,
- reconciled_lead_entity,
- raiseerr,
- ):
- """implemented by subclasses."""
- raise NotImplementedError()
-
- def process_compile_state(
- self,
- parent_loader,
- compile_state,
- mapper_entities,
- reconciled_lead_entity,
- raiseerr,
- ):
- """populate ORMCompileState.attributes with loader state for this
- _LoadElement.
-
- """
- keys = self._prepare_for_compile_state(
- parent_loader,
- compile_state,
- mapper_entities,
- reconciled_lead_entity,
- raiseerr,
- )
- for key in keys:
- if key in compile_state.attributes:
- compile_state.attributes[key] = _LoadElement._reconcile(
- self, compile_state.attributes[key]
- )
- else:
- compile_state.attributes[key] = self
-
- @classmethod
- def create(
- cls,
- path: PathRegistry,
- attr: Union[_AttrType, _StrPathToken, None],
- strategy: Optional[_StrategyKey],
- wildcard_key: Optional[_WildcardKeyType],
- local_opts: Optional[_OptsType],
- propagate_to_loaders: bool,
- raiseerr: bool = True,
- attr_group: Optional[_AttrGroupType] = None,
- reconcile_to_other: Optional[bool] = None,
- extra_criteria: Optional[Tuple[Any, ...]] = None,
- ) -> _LoadElement:
- """Create a new :class:`._LoadElement` object."""
-
- opt = cls.__new__(cls)
- opt.path = path
- opt.strategy = strategy
- opt.propagate_to_loaders = propagate_to_loaders
- opt.local_opts = (
- util.immutabledict(local_opts) if local_opts else util.EMPTY_DICT
- )
- opt._extra_criteria = ()
-
- if reconcile_to_other is not None:
- opt._reconcile_to_other = reconcile_to_other
- elif strategy is None and not local_opts:
- opt._reconcile_to_other = True
- else:
- opt._reconcile_to_other = None
-
- path = opt._init_path(
- path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
- )
-
- if not path:
- return None # type: ignore
-
- assert opt.is_token_strategy == path.is_token
-
- opt.path = path
- return opt
-
- def __init__(self) -> None:
- raise NotImplementedError()
-
- def _recurse(self) -> _LoadElement:
- cloned = self._clone()
- cloned.path = PathRegistry.coerce(self.path[:] + self.path[-2:])
-
- return cloned
-
- def _prepend_path_from(self, parent: Load) -> _LoadElement:
- """adjust the path of this :class:`._LoadElement` to be
- a subpath of that of the given parent :class:`_orm.Load` object's
- path.
-
- This is used by the :meth:`_orm.Load._apply_to_parent` method,
- which is in turn part of the :meth:`_orm.Load.options` method.
-
- """
-
- if not any(
- orm_util._entity_corresponds_to_use_path_impl(
- elem,
- self.path.odd_element(0),
- )
- for elem in (parent.path.odd_element(-1),)
- + parent.additional_source_entities
- ):
- raise sa_exc.ArgumentError(
- f'Attribute "{self.path[1]}" does not link '
- f'from element "{parent.path[-1]}".'
- )
-
- return self._prepend_path(parent.path)
-
- def _prepend_path(self, path: PathRegistry) -> _LoadElement:
- cloned = self._clone()
-
- assert cloned.strategy == self.strategy
- assert cloned.local_opts == self.local_opts
- assert cloned.is_class_strategy == self.is_class_strategy
-
- cloned.path = PathRegistry.coerce(path[0:-1] + cloned.path[:])
-
- return cloned
-
- @staticmethod
- def _reconcile(
- replacement: _LoadElement, existing: _LoadElement
- ) -> _LoadElement:
- """define behavior for when two Load objects are to be put into
- the context.attributes under the same key.
-
- :param replacement: ``_LoadElement`` that seeks to replace the
- existing one
-
- :param existing: ``_LoadElement`` that is already present.
-
- """
- # mapper inheritance loading requires fine-grained "block other
- # options" / "allow these options to be overridden" behaviors
- # see test_poly_loading.py
-
- if replacement._reconcile_to_other:
- return existing
- elif replacement._reconcile_to_other is False:
- return replacement
- elif existing._reconcile_to_other:
- return replacement
- elif existing._reconcile_to_other is False:
- return existing
-
- if existing is replacement:
- return replacement
- elif (
- existing.strategy == replacement.strategy
- and existing.local_opts == replacement.local_opts
- ):
- return replacement
- elif replacement.is_opts_only:
- existing = existing._clone()
- existing.local_opts = existing.local_opts.union(
- replacement.local_opts
- )
- existing._extra_criteria += replacement._extra_criteria
- return existing
- elif existing.is_opts_only:
- replacement = replacement._clone()
- replacement.local_opts = replacement.local_opts.union(
- existing.local_opts
- )
- replacement._extra_criteria += existing._extra_criteria
- return replacement
- elif replacement.path.is_token:
- # use 'last one wins' logic for wildcard options. this is also
- # kind of inconsistent vs. options that are specific paths which
- # will raise as below
- return replacement
-
- raise sa_exc.InvalidRequestError(
- f"Loader strategies for {replacement.path} conflict"
- )
-
-
-class _AttributeStrategyLoad(_LoadElement):
- """Loader strategies against specific relationship or column paths.
-
- e.g.::
-
- joinedload(User.addresses)
- defer(Order.name)
- selectinload(User.orders).lazyload(Order.items)
-
- """
-
- __slots__ = ("_of_type", "_path_with_polymorphic_path")
-
- __visit_name__ = "attribute_strategy_load_element"
-
- _traverse_internals = _LoadElement._traverse_internals + [
- ("_of_type", visitors.ExtendedInternalTraversal.dp_multi),
- (
- "_path_with_polymorphic_path",
- visitors.ExtendedInternalTraversal.dp_has_cache_key,
- ),
- ]
-
- _of_type: Union[Mapper[Any], AliasedInsp[Any], None]
- _path_with_polymorphic_path: Optional[PathRegistry]
-
- is_class_strategy = False
- is_token_strategy = False
-
- def _init_path(
- self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
- ):
- assert attr is not None
- self._of_type = None
- self._path_with_polymorphic_path = None
- insp, _, prop = _parse_attr_argument(attr)
-
- if insp.is_property:
- # direct property can be sent from internal strategy logic
- # that sets up specific loaders, such as
- # emit_lazyload->_lazyload_reverse
- # prop = found_property = attr
- prop = attr
- path = path[prop]
-
- if path.has_entity:
- path = path.entity_path
- return path
-
- elif not insp.is_attribute:
- # should not reach here;
- assert False
-
- # here we assume we have user-passed InstrumentedAttribute
- if not orm_util._entity_corresponds_to_use_path_impl(
- path[-1], attr.parent
- ):
- if raiseerr:
- if attr_group and attr is not attr_group[0]:
- raise sa_exc.ArgumentError(
- "Can't apply wildcard ('*') or load_only() "
- "loader option to multiple entities in the "
- "same option. Use separate options per entity."
- )
- else:
- _raise_for_does_not_link(path, str(attr), attr.parent)
- else:
- return None
-
- # note the essential logic of this attribute was very different in
- # 1.4, where there were caching failures in e.g.
- # test_relationship_criteria.py::RelationshipCriteriaTest::
- # test_selectinload_nested_criteria[True] if an existing
- # "_extra_criteria" on a Load object were replaced with that coming
- # from an attribute. This appears to have been an artifact of how
- # _UnboundLoad / Load interacted together, which was opaque and
- # poorly defined.
- if extra_criteria:
- assert not attr._extra_criteria
- self._extra_criteria = extra_criteria
- else:
- self._extra_criteria = attr._extra_criteria
-
- if getattr(attr, "_of_type", None):
- ac = attr._of_type
- ext_info = inspect(ac)
- self._of_type = ext_info
-
- self._path_with_polymorphic_path = path.entity_path[prop]
-
- path = path[prop][ext_info]
-
- else:
- path = path[prop]
-
- if path.has_entity:
- path = path.entity_path
-
- return path
-
- def _generate_extra_criteria(self, context):
- """Apply the current bound parameters in a QueryContext to the
- immediate "extra_criteria" stored with this Load object.
-
- Load objects are typically pulled from the cached version of
- the statement from a QueryContext. The statement currently being
- executed will have new values (and keys) for bound parameters in the
- extra criteria which need to be applied by loader strategies when
- they handle this criteria for a result set.
-
- """
-
- assert (
- self._extra_criteria
- ), "this should only be called if _extra_criteria is present"
-
- orig_query = context.compile_state.select_statement
- current_query = context.query
-
- # NOTE: while it seems like we should not do the "apply" operation
- # here if orig_query is current_query, skipping it in the "optimized"
- # case causes the query to be different from a cache key perspective,
- # because we are creating a copy of the criteria which is no longer
- # the same identity of the _extra_criteria in the loader option
- # itself. cache key logic produces a different key for
- # (A, copy_of_A) vs. (A, A), because in the latter case it shortens
- # the second part of the key to just indicate on identity.
-
- # if orig_query is current_query:
- # not cached yet. just do the and_()
- # return and_(*self._extra_criteria)
-
- k1 = orig_query._generate_cache_key()
- k2 = current_query._generate_cache_key()
-
- return k2._apply_params_to_element(k1, and_(*self._extra_criteria))
-
- def _set_of_type_info(self, context, current_path):
- assert self._path_with_polymorphic_path
-
- pwpi = self._of_type
- assert pwpi
- if not pwpi.is_aliased_class:
- pwpi = inspect(
- orm_util.AliasedInsp._with_polymorphic_factory(
- pwpi.mapper.base_mapper,
- (pwpi.mapper,),
- aliased=True,
- _use_mapper_path=True,
- )
- )
- start_path = self._path_with_polymorphic_path
- if current_path:
- new_path = self._adjust_effective_path_for_current_path(
- start_path, current_path
- )
- if new_path is None:
- return
- start_path = new_path
-
- key = ("path_with_polymorphic", start_path.natural_path)
- if key in context:
- existing_aliased_insp = context[key]
- this_aliased_insp = pwpi
- new_aliased_insp = existing_aliased_insp._merge_with(
- this_aliased_insp
- )
- context[key] = new_aliased_insp
- else:
- context[key] = pwpi
-
- def _prepare_for_compile_state(
- self,
- parent_loader,
- compile_state,
- mapper_entities,
- reconciled_lead_entity,
- raiseerr,
- ):
- # _AttributeStrategyLoad
-
- current_path = compile_state.current_path
- is_refresh = compile_state.compile_options._for_refresh_state
- assert not self.path.is_token
-
- if is_refresh and not self.propagate_to_loaders:
- return []
-
- if self._of_type:
- # apply additional with_polymorphic alias that may have been
- # generated. this has to happen even if this is a defaultload
- self._set_of_type_info(compile_state.attributes, current_path)
-
- # omit setting loader attributes for a "defaultload" type of option
- if not self.strategy and not self.local_opts:
- return []
-
- if raiseerr and not reconciled_lead_entity:
- self._raise_for_no_match(parent_loader, mapper_entities)
-
- if self.path.has_entity:
- effective_path = self.path.parent
- else:
- effective_path = self.path
-
- if current_path:
- assert effective_path is not None
- effective_path = self._adjust_effective_path_for_current_path(
- effective_path, current_path
- )
- if effective_path is None:
- return []
-
- return [("loader", cast(PathRegistry, effective_path).natural_path)]
-
- def __getstate__(self):
- d = super().__getstate__()
-
- # can't pickle this. See
- # test_pickled.py -> test_lazyload_extra_criteria_not_supported
- # where we should be emitting a warning for the usual case where this
- # would be non-None
- d["_extra_criteria"] = ()
-
- if self._path_with_polymorphic_path:
- d["_path_with_polymorphic_path"] = (
- self._path_with_polymorphic_path.serialize()
- )
-
- if self._of_type:
- if self._of_type.is_aliased_class:
- d["_of_type"] = None
- elif self._of_type.is_mapper:
- d["_of_type"] = self._of_type.class_
- else:
- assert False, "unexpected object for _of_type"
-
- return d
-
- def __setstate__(self, state):
- super().__setstate__(state)
-
- if state.get("_path_with_polymorphic_path", None):
- self._path_with_polymorphic_path = PathRegistry.deserialize(
- state["_path_with_polymorphic_path"]
- )
- else:
- self._path_with_polymorphic_path = None
-
- if state.get("_of_type", None):
- self._of_type = inspect(state["_of_type"])
- else:
- self._of_type = None
-
-
-class _TokenStrategyLoad(_LoadElement):
- """Loader strategies against wildcard attributes
-
- e.g.::
-
- raiseload('*')
- Load(User).lazyload('*')
- defer('*')
- load_only(User.name, User.email) # will create a defer('*')
- joinedload(User.addresses).raiseload('*')
-
- """
-
- __visit_name__ = "token_strategy_load_element"
-
- inherit_cache = True
- is_class_strategy = False
- is_token_strategy = True
-
- def _init_path(
- self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
- ):
- # assert isinstance(attr, str) or attr is None
- if attr is not None:
- default_token = attr.endswith(_DEFAULT_TOKEN)
- if attr.endswith(_WILDCARD_TOKEN) or default_token:
- if wildcard_key:
- attr = f"{wildcard_key}:{attr}"
-
- path = path.token(attr)
- return path
- else:
- raise sa_exc.ArgumentError(
- "Strings are not accepted for attribute names in loader "
- "options; please use class-bound attributes directly."
- )
- return path
-
- def _prepare_for_compile_state(
- self,
- parent_loader,
- compile_state,
- mapper_entities,
- reconciled_lead_entity,
- raiseerr,
- ):
- # _TokenStrategyLoad
-
- current_path = compile_state.current_path
- is_refresh = compile_state.compile_options._for_refresh_state
-
- assert self.path.is_token
-
- if is_refresh and not self.propagate_to_loaders:
- return []
-
- # omit setting attributes for a "defaultload" type of option
- if not self.strategy and not self.local_opts:
- return []
-
- effective_path = self.path
- if reconciled_lead_entity:
- effective_path = PathRegistry.coerce(
- (reconciled_lead_entity,) + effective_path.path[1:]
- )
-
- if current_path:
- new_effective_path = self._adjust_effective_path_for_current_path(
- effective_path, current_path
- )
- if new_effective_path is None:
- return []
- effective_path = new_effective_path
-
- # for a wildcard token, expand out the path we set
- # to encompass everything from the query entity on
- # forward. not clear if this is necessary when current_path
- # is set.
-
- return [
- ("loader", natural_path)
- for natural_path in (
- cast(
- TokenRegistry, effective_path
- )._generate_natural_for_superclasses()
- )
- ]
-
-
-class _ClassStrategyLoad(_LoadElement):
- """Loader strategies that deals with a class as a target, not
- an attribute path
-
- e.g.::
-
- q = s.query(Person).options(
- selectin_polymorphic(Person, [Engineer, Manager])
- )
-
- """
-
- inherit_cache = True
- is_class_strategy = True
- is_token_strategy = False
-
- __visit_name__ = "class_strategy_load_element"
-
- def _init_path(
- self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
- ):
- return path
-
- def _prepare_for_compile_state(
- self,
- parent_loader,
- compile_state,
- mapper_entities,
- reconciled_lead_entity,
- raiseerr,
- ):
- # _ClassStrategyLoad
-
- current_path = compile_state.current_path
- is_refresh = compile_state.compile_options._for_refresh_state
-
- if is_refresh and not self.propagate_to_loaders:
- return []
-
- # omit setting attributes for a "defaultload" type of option
- if not self.strategy and not self.local_opts:
- return []
-
- effective_path = self.path
-
- if current_path:
- new_effective_path = self._adjust_effective_path_for_current_path(
- effective_path, current_path
- )
- if new_effective_path is None:
- return []
- effective_path = new_effective_path
-
- return [("loader", effective_path.natural_path)]
-
-
-def _generate_from_keys(
- meth: Callable[..., _AbstractLoad],
- keys: Tuple[_AttrType, ...],
- chained: bool,
- kw: Any,
-) -> _AbstractLoad:
- lead_element: Optional[_AbstractLoad] = None
-
- attr: Any
- for is_default, _keys in (True, keys[0:-1]), (False, keys[-1:]):
- for attr in _keys:
- if isinstance(attr, str):
- if attr.startswith("." + _WILDCARD_TOKEN):
- util.warn_deprecated(
- "The undocumented `.{WILDCARD}` format is "
- "deprecated "
- "and will be removed in a future version as "
- "it is "
- "believed to be unused. "
- "If you have been using this functionality, "
- "please "
- "comment on Issue #4390 on the SQLAlchemy project "
- "tracker.",
- version="1.4",
- )
- attr = attr[1:]
-
- if attr == _WILDCARD_TOKEN:
- if is_default:
- raise sa_exc.ArgumentError(
- "Wildcard token cannot be followed by "
- "another entity",
- )
-
- if lead_element is None:
- lead_element = _WildcardLoad()
-
- lead_element = meth(lead_element, _DEFAULT_TOKEN, **kw)
-
- else:
- raise sa_exc.ArgumentError(
- "Strings are not accepted for attribute names in "
- "loader options; please use class-bound "
- "attributes directly.",
- )
- else:
- if lead_element is None:
- _, lead_entity, _ = _parse_attr_argument(attr)
- lead_element = Load(lead_entity)
-
- if is_default:
- if not chained:
- lead_element = lead_element.defaultload(attr)
- else:
- lead_element = meth(
- lead_element, attr, _is_chain=True, **kw
- )
- else:
- lead_element = meth(lead_element, attr, **kw)
-
- assert lead_element
- return lead_element
-
-
-def _parse_attr_argument(
- attr: _AttrType,
-) -> Tuple[InspectionAttr, _InternalEntityType[Any], MapperProperty[Any]]:
- """parse an attribute or wildcard argument to produce an
- :class:`._AbstractLoad` instance.
-
- This is used by the standalone loader strategy functions like
- ``joinedload()``, ``defer()``, etc. to produce :class:`_orm.Load` or
- :class:`._WildcardLoad` objects.
-
- """
- try:
- # TODO: need to figure out this None thing being returned by
- # inspect(), it should not have None as an option in most cases
- # if at all
- insp: InspectionAttr = inspect(attr) # type: ignore
- except sa_exc.NoInspectionAvailable as err:
- raise sa_exc.ArgumentError(
- "expected ORM mapped attribute for loader strategy argument"
- ) from err
-
- lead_entity: _InternalEntityType[Any]
-
- if insp_is_mapper_property(insp):
- lead_entity = insp.parent
- prop = insp
- elif insp_is_attribute(insp):
- lead_entity = insp.parent
- prop = insp.prop
- else:
- raise sa_exc.ArgumentError(
- "expected ORM mapped attribute for loader strategy argument"
- )
-
- return insp, lead_entity, prop
-
-
-def loader_unbound_fn(fn: _FN) -> _FN:
- """decorator that applies docstrings between standalone loader functions
- and the loader methods on :class:`._AbstractLoad`.
-
- """
- bound_fn = getattr(_AbstractLoad, fn.__name__)
- fn_doc = bound_fn.__doc__
- bound_fn.__doc__ = f"""Produce a new :class:`_orm.Load` object with the
-:func:`_orm.{fn.__name__}` option applied.
-
-See :func:`_orm.{fn.__name__}` for usage examples.
-
-"""
-
- fn.__doc__ = fn_doc
- return fn
-
-
-# standalone functions follow. docstrings are filled in
-# by the ``@loader_unbound_fn`` decorator.
-
-
-@loader_unbound_fn
-def contains_eager(*keys: _AttrType, **kw: Any) -> _AbstractLoad:
- return _generate_from_keys(Load.contains_eager, keys, True, kw)
-
-
-@loader_unbound_fn
-def load_only(*attrs: _AttrType, raiseload: bool = False) -> _AbstractLoad:
- # TODO: attrs against different classes. we likely have to
- # add some extra state to Load of some kind
- _, lead_element, _ = _parse_attr_argument(attrs[0])
- return Load(lead_element).load_only(*attrs, raiseload=raiseload)
-
-
-@loader_unbound_fn
-def joinedload(*keys: _AttrType, **kw: Any) -> _AbstractLoad:
- return _generate_from_keys(Load.joinedload, keys, False, kw)
-
-
-@loader_unbound_fn
-def subqueryload(*keys: _AttrType) -> _AbstractLoad:
- return _generate_from_keys(Load.subqueryload, keys, False, {})
-
-
-@loader_unbound_fn
-def selectinload(
- *keys: _AttrType, recursion_depth: Optional[int] = None
-) -> _AbstractLoad:
- return _generate_from_keys(
- Load.selectinload, keys, False, {"recursion_depth": recursion_depth}
- )
-
-
-@loader_unbound_fn
-def lazyload(*keys: _AttrType) -> _AbstractLoad:
- return _generate_from_keys(Load.lazyload, keys, False, {})
-
-
-@loader_unbound_fn
-def immediateload(
- *keys: _AttrType, recursion_depth: Optional[int] = None
-) -> _AbstractLoad:
- return _generate_from_keys(
- Load.immediateload, keys, False, {"recursion_depth": recursion_depth}
- )
-
-
-@loader_unbound_fn
-def noload(*keys: _AttrType) -> _AbstractLoad:
- return _generate_from_keys(Load.noload, keys, False, {})
-
-
-@loader_unbound_fn
-def raiseload(*keys: _AttrType, **kw: Any) -> _AbstractLoad:
- return _generate_from_keys(Load.raiseload, keys, False, kw)
-
-
-@loader_unbound_fn
-def defaultload(*keys: _AttrType) -> _AbstractLoad:
- return _generate_from_keys(Load.defaultload, keys, False, {})
-
-
-@loader_unbound_fn
-def defer(
- key: _AttrType, *addl_attrs: _AttrType, raiseload: bool = False
-) -> _AbstractLoad:
- if addl_attrs:
- util.warn_deprecated(
- "The *addl_attrs on orm.defer is deprecated. Please use "
- "method chaining in conjunction with defaultload() to "
- "indicate a path.",
- version="1.3",
- )
-
- if raiseload:
- kw = {"raiseload": raiseload}
- else:
- kw = {}
-
- return _generate_from_keys(Load.defer, (key,) + addl_attrs, False, kw)
-
-
-@loader_unbound_fn
-def undefer(key: _AttrType, *addl_attrs: _AttrType) -> _AbstractLoad:
- if addl_attrs:
- util.warn_deprecated(
- "The *addl_attrs on orm.undefer is deprecated. Please use "
- "method chaining in conjunction with defaultload() to "
- "indicate a path.",
- version="1.3",
- )
- return _generate_from_keys(Load.undefer, (key,) + addl_attrs, False, {})
-
-
-@loader_unbound_fn
-def undefer_group(name: str) -> _AbstractLoad:
- element = _WildcardLoad()
- return element.undefer_group(name)
-
-
-@loader_unbound_fn
-def with_expression(
- key: _AttrType, expression: _ColumnExpressionArgument[Any]
-) -> _AbstractLoad:
- return _generate_from_keys(
- Load.with_expression, (key,), False, {"expression": expression}
- )
-
-
-@loader_unbound_fn
-def selectin_polymorphic(
- base_cls: _EntityType[Any], classes: Iterable[Type[Any]]
-) -> _AbstractLoad:
- ul = Load(base_cls)
- return ul.selectin_polymorphic(classes)
-
-
-def _raise_for_does_not_link(path, attrname, parent_entity):
- if len(path) > 1:
- path_is_of_type = path[-1].entity is not path[-2].mapper.class_
- if insp_is_aliased_class(parent_entity):
- parent_entity_str = str(parent_entity)
- else:
- parent_entity_str = parent_entity.class_.__name__
-
- raise sa_exc.ArgumentError(
- f'ORM mapped entity or attribute "{attrname}" does not '
- f'link from relationship "{path[-2]}%s".%s'
- % (
- f".of_type({path[-1]})" if path_is_of_type else "",
- (
- " Did you mean to use "
- f'"{path[-2]}'
- f'.of_type({parent_entity_str})" or "loadopt.options('
- f"selectin_polymorphic({path[-2].mapper.class_.__name__}, "
- f'[{parent_entity_str}]), ...)" ?'
- if not path_is_of_type
- and not path[-1].is_aliased_class
- and orm_util._entity_corresponds_to(
- path.entity, inspect(parent_entity).mapper
- )
- else ""
- ),
- )
- )
- else:
- raise sa_exc.ArgumentError(
- f'ORM mapped attribute "{attrname}" does not '
- f'link mapped class "{path[-1]}"'
- )
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/sync.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/sync.py
deleted file mode 100644
index db09a3e..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/sync.py
+++ /dev/null
@@ -1,164 +0,0 @@
-# orm/sync.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
-# mypy: allow-untyped-defs, allow-untyped-calls
-
-
-"""private module containing functions used for copying data
-between instances based on join conditions.
-
-"""
-
-from __future__ import annotations
-
-from . import exc
-from . import util as orm_util
-from .base import PassiveFlag
-
-
-def populate(
- source,
- source_mapper,
- dest,
- dest_mapper,
- synchronize_pairs,
- uowcommit,
- flag_cascaded_pks,
-):
- source_dict = source.dict
- dest_dict = dest.dict
-
- for l, r in synchronize_pairs:
- try:
- # inline of source_mapper._get_state_attr_by_column
- prop = source_mapper._columntoproperty[l]
- value = source.manager[prop.key].impl.get(
- source, source_dict, PassiveFlag.PASSIVE_OFF
- )
- except exc.UnmappedColumnError as err:
- _raise_col_to_prop(False, source_mapper, l, dest_mapper, r, err)
-
- try:
- # inline of dest_mapper._set_state_attr_by_column
- prop = dest_mapper._columntoproperty[r]
- dest.manager[prop.key].impl.set(dest, dest_dict, value, None)
- except exc.UnmappedColumnError as err:
- _raise_col_to_prop(True, source_mapper, l, dest_mapper, r, err)
-
- # technically the "r.primary_key" check isn't
- # needed here, but we check for this condition to limit
- # how often this logic is invoked for memory/performance
- # reasons, since we only need this info for a primary key
- # destination.
- if (
- flag_cascaded_pks
- and l.primary_key
- and r.primary_key
- and r.references(l)
- ):
- uowcommit.attributes[("pk_cascaded", dest, r)] = True
-
-
-def bulk_populate_inherit_keys(source_dict, source_mapper, synchronize_pairs):
- # a simplified version of populate() used by bulk insert mode
- for l, r in synchronize_pairs:
- try:
- prop = source_mapper._columntoproperty[l]
- value = source_dict[prop.key]
- except exc.UnmappedColumnError as err:
- _raise_col_to_prop(False, source_mapper, l, source_mapper, r, err)
-
- try:
- prop = source_mapper._columntoproperty[r]
- source_dict[prop.key] = value
- except exc.UnmappedColumnError as err:
- _raise_col_to_prop(True, source_mapper, l, source_mapper, r, err)
-
-
-def clear(dest, dest_mapper, synchronize_pairs):
- for l, r in synchronize_pairs:
- if (
- r.primary_key
- and dest_mapper._get_state_attr_by_column(dest, dest.dict, r)
- not in orm_util._none_set
- ):
- raise AssertionError(
- f"Dependency rule on column '{l}' "
- "tried to blank-out primary key "
- f"column '{r}' on instance '{orm_util.state_str(dest)}'"
- )
- try:
- dest_mapper._set_state_attr_by_column(dest, dest.dict, r, None)
- except exc.UnmappedColumnError as err:
- _raise_col_to_prop(True, None, l, dest_mapper, r, err)
-
-
-def update(source, source_mapper, dest, old_prefix, synchronize_pairs):
- for l, r in synchronize_pairs:
- try:
- oldvalue = source_mapper._get_committed_attr_by_column(
- source.obj(), l
- )
- value = source_mapper._get_state_attr_by_column(
- source, source.dict, l, passive=PassiveFlag.PASSIVE_OFF
- )
- except exc.UnmappedColumnError as err:
- _raise_col_to_prop(False, source_mapper, l, None, r, err)
- dest[r.key] = value
- dest[old_prefix + r.key] = oldvalue
-
-
-def populate_dict(source, source_mapper, dict_, synchronize_pairs):
- for l, r in synchronize_pairs:
- try:
- value = source_mapper._get_state_attr_by_column(
- source, source.dict, l, passive=PassiveFlag.PASSIVE_OFF
- )
- except exc.UnmappedColumnError as err:
- _raise_col_to_prop(False, source_mapper, l, None, r, err)
-
- dict_[r.key] = value
-
-
-def source_modified(uowcommit, source, source_mapper, synchronize_pairs):
- """return true if the source object has changes from an old to a
- new value on the given synchronize pairs
-
- """
- for l, r in synchronize_pairs:
- try:
- prop = source_mapper._columntoproperty[l]
- except exc.UnmappedColumnError as err:
- _raise_col_to_prop(False, source_mapper, l, None, r, err)
- history = uowcommit.get_attribute_history(
- source, prop.key, PassiveFlag.PASSIVE_NO_INITIALIZE
- )
- if bool(history.deleted):
- return True
- else:
- return False
-
-
-def _raise_col_to_prop(
- isdest, source_mapper, source_column, dest_mapper, dest_column, err
-):
- if isdest:
- raise exc.UnmappedColumnError(
- "Can't execute sync rule for "
- "destination column '%s'; mapper '%s' does not map "
- "this column. Try using an explicit `foreign_keys` "
- "collection which does not include this column (or use "
- "a viewonly=True relation)." % (dest_column, dest_mapper)
- ) from err
- else:
- raise exc.UnmappedColumnError(
- "Can't execute sync rule for "
- "source column '%s'; mapper '%s' does not map this "
- "column. Try using an explicit `foreign_keys` "
- "collection which does not include destination column "
- "'%s' (or use a viewonly=True relation)."
- % (source_column, source_mapper, dest_column)
- ) from err
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/unitofwork.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/unitofwork.py
deleted file mode 100644
index 7e2df2b..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/unitofwork.py
+++ /dev/null
@@ -1,796 +0,0 @@
-# orm/unitofwork.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
-# mypy: ignore-errors
-
-
-"""The internals for the unit of work system.
-
-The session's flush() process passes objects to a contextual object
-here, which assembles flush tasks based on mappers and their properties,
-organizes them in order of dependency, and executes.
-
-"""
-
-from __future__ import annotations
-
-from typing import Any
-from typing import Dict
-from typing import Optional
-from typing import Set
-from typing import TYPE_CHECKING
-
-from . import attributes
-from . import exc as orm_exc
-from . import util as orm_util
-from .. import event
-from .. import util
-from ..util import topological
-
-
-if TYPE_CHECKING:
- from .dependency import DependencyProcessor
- from .interfaces import MapperProperty
- from .mapper import Mapper
- from .session import Session
- from .session import SessionTransaction
- from .state import InstanceState
-
-
-def track_cascade_events(descriptor, prop):
- """Establish event listeners on object attributes which handle
- cascade-on-set/append.
-
- """
- key = prop.key
-
- def append(state, item, initiator, **kw):
- # process "save_update" cascade rules for when
- # an instance is appended to the list of another instance
-
- if item is None:
- return
-
- sess = state.session
- if sess:
- if sess._warn_on_events:
- sess._flush_warning("collection append")
-
- prop = state.manager.mapper._props[key]
- item_state = attributes.instance_state(item)
-
- if (
- prop._cascade.save_update
- and (key == initiator.key)
- and not sess._contains_state(item_state)
- ):
- sess._save_or_update_state(item_state)
- return item
-
- def remove(state, item, initiator, **kw):
- if item is None:
- return
-
- sess = state.session
-
- prop = state.manager.mapper._props[key]
-
- if sess and sess._warn_on_events:
- sess._flush_warning(
- "collection remove"
- if prop.uselist
- else "related attribute delete"
- )
-
- if (
- item is not None
- and item is not attributes.NEVER_SET
- and item is not attributes.PASSIVE_NO_RESULT
- and prop._cascade.delete_orphan
- ):
- # expunge pending orphans
- item_state = attributes.instance_state(item)
-
- if prop.mapper._is_orphan(item_state):
- if sess and item_state in sess._new:
- sess.expunge(item)
- else:
- # the related item may or may not itself be in a
- # Session, however the parent for which we are catching
- # the event is not in a session, so memoize this on the
- # item
- item_state._orphaned_outside_of_session = True
-
- def set_(state, newvalue, oldvalue, initiator, **kw):
- # process "save_update" cascade rules for when an instance
- # is attached to another instance
- if oldvalue is newvalue:
- return newvalue
-
- sess = state.session
- if sess:
- if sess._warn_on_events:
- sess._flush_warning("related attribute set")
-
- prop = state.manager.mapper._props[key]
- if newvalue is not None:
- newvalue_state = attributes.instance_state(newvalue)
- if (
- prop._cascade.save_update
- and (key == initiator.key)
- and not sess._contains_state(newvalue_state)
- ):
- sess._save_or_update_state(newvalue_state)
-
- if (
- oldvalue is not None
- and oldvalue is not attributes.NEVER_SET
- and oldvalue is not attributes.PASSIVE_NO_RESULT
- and prop._cascade.delete_orphan
- ):
- # possible to reach here with attributes.NEVER_SET ?
- oldvalue_state = attributes.instance_state(oldvalue)
-
- if oldvalue_state in sess._new and prop.mapper._is_orphan(
- oldvalue_state
- ):
- sess.expunge(oldvalue)
- return newvalue
-
- event.listen(
- descriptor, "append_wo_mutation", append, raw=True, include_key=True
- )
- event.listen(
- descriptor, "append", append, raw=True, retval=True, include_key=True
- )
- event.listen(
- descriptor, "remove", remove, raw=True, retval=True, include_key=True
- )
- event.listen(
- descriptor, "set", set_, raw=True, retval=True, include_key=True
- )
-
-
-class UOWTransaction:
- session: Session
- transaction: SessionTransaction
- attributes: Dict[str, Any]
- deps: util.defaultdict[Mapper[Any], Set[DependencyProcessor]]
- mappers: util.defaultdict[Mapper[Any], Set[InstanceState[Any]]]
-
- def __init__(self, session: Session):
- self.session = session
-
- # dictionary used by external actors to
- # store arbitrary state information.
- self.attributes = {}
-
- # dictionary of mappers to sets of
- # DependencyProcessors, which are also
- # set to be part of the sorted flush actions,
- # which have that mapper as a parent.
- self.deps = util.defaultdict(set)
-
- # dictionary of mappers to sets of InstanceState
- # items pending for flush which have that mapper
- # as a parent.
- self.mappers = util.defaultdict(set)
-
- # a dictionary of Preprocess objects, which gather
- # additional states impacted by the flush
- # and determine if a flush action is needed
- self.presort_actions = {}
-
- # dictionary of PostSortRec objects, each
- # one issues work during the flush within
- # a certain ordering.
- self.postsort_actions = {}
-
- # a set of 2-tuples, each containing two
- # PostSortRec objects where the second
- # is dependent on the first being executed
- # first
- self.dependencies = set()
-
- # dictionary of InstanceState-> (isdelete, listonly)
- # tuples, indicating if this state is to be deleted
- # or insert/updated, or just refreshed
- self.states = {}
-
- # tracks InstanceStates which will be receiving
- # a "post update" call. Keys are mappers,
- # values are a set of states and a set of the
- # columns which should be included in the update.
- self.post_update_states = util.defaultdict(lambda: (set(), set()))
-
- @property
- def has_work(self):
- return bool(self.states)
-
- def was_already_deleted(self, state):
- """Return ``True`` if the given state is expired and was deleted
- previously.
- """
- if state.expired:
- try:
- state._load_expired(state, attributes.PASSIVE_OFF)
- except orm_exc.ObjectDeletedError:
- self.session._remove_newly_deleted([state])
- return True
- return False
-
- def is_deleted(self, state):
- """Return ``True`` if the given state is marked as deleted
- within this uowtransaction."""
-
- return state in self.states and self.states[state][0]
-
- def memo(self, key, callable_):
- if key in self.attributes:
- return self.attributes[key]
- else:
- self.attributes[key] = ret = callable_()
- return ret
-
- def remove_state_actions(self, state):
- """Remove pending actions for a state from the uowtransaction."""
-
- isdelete = self.states[state][0]
-
- self.states[state] = (isdelete, True)
-
- def get_attribute_history(
- self, state, key, passive=attributes.PASSIVE_NO_INITIALIZE
- ):
- """Facade to attributes.get_state_history(), including
- caching of results."""
-
- hashkey = ("history", state, key)
-
- # cache the objects, not the states; the strong reference here
- # prevents newly loaded objects from being dereferenced during the
- # flush process
-
- if hashkey in self.attributes:
- history, state_history, cached_passive = self.attributes[hashkey]
- # if the cached lookup was "passive" and now
- # we want non-passive, do a non-passive lookup and re-cache
-
- if (
- not cached_passive & attributes.SQL_OK
- and passive & attributes.SQL_OK
- ):
- impl = state.manager[key].impl
- history = impl.get_history(
- state,
- state.dict,
- attributes.PASSIVE_OFF
- | attributes.LOAD_AGAINST_COMMITTED
- | attributes.NO_RAISE,
- )
- if history and impl.uses_objects:
- state_history = history.as_state()
- else:
- state_history = history
- self.attributes[hashkey] = (history, state_history, passive)
- else:
- impl = state.manager[key].impl
- # TODO: store the history as (state, object) tuples
- # so we don't have to keep converting here
- history = impl.get_history(
- state,
- state.dict,
- passive
- | attributes.LOAD_AGAINST_COMMITTED
- | attributes.NO_RAISE,
- )
- if history and impl.uses_objects:
- state_history = history.as_state()
- else:
- state_history = history
- self.attributes[hashkey] = (history, state_history, passive)
-
- return state_history
-
- def has_dep(self, processor):
- return (processor, True) in self.presort_actions
-
- def register_preprocessor(self, processor, fromparent):
- key = (processor, fromparent)
- if key not in self.presort_actions:
- self.presort_actions[key] = Preprocess(processor, fromparent)
-
- def register_object(
- self,
- state: InstanceState[Any],
- isdelete: bool = False,
- listonly: bool = False,
- cancel_delete: bool = False,
- operation: Optional[str] = None,
- prop: Optional[MapperProperty] = None,
- ) -> bool:
- if not self.session._contains_state(state):
- # this condition is normal when objects are registered
- # as part of a relationship cascade operation. it should
- # not occur for the top-level register from Session.flush().
- if not state.deleted and operation is not None:
- util.warn(
- "Object of type %s not in session, %s operation "
- "along '%s' will not proceed"
- % (orm_util.state_class_str(state), operation, prop)
- )
- return False
-
- if state not in self.states:
- mapper = state.manager.mapper
-
- if mapper not in self.mappers:
- self._per_mapper_flush_actions(mapper)
-
- self.mappers[mapper].add(state)
- self.states[state] = (isdelete, listonly)
- else:
- if not listonly and (isdelete or cancel_delete):
- self.states[state] = (isdelete, False)
- return True
-
- def register_post_update(self, state, post_update_cols):
- mapper = state.manager.mapper.base_mapper
- states, cols = self.post_update_states[mapper]
- states.add(state)
- cols.update(post_update_cols)
-
- def _per_mapper_flush_actions(self, mapper):
- saves = SaveUpdateAll(self, mapper.base_mapper)
- deletes = DeleteAll(self, mapper.base_mapper)
- self.dependencies.add((saves, deletes))
-
- for dep in mapper._dependency_processors:
- dep.per_property_preprocessors(self)
-
- for prop in mapper.relationships:
- if prop.viewonly:
- continue
- dep = prop._dependency_processor
- dep.per_property_preprocessors(self)
-
- @util.memoized_property
- def _mapper_for_dep(self):
- """return a dynamic mapping of (Mapper, DependencyProcessor) to
- True or False, indicating if the DependencyProcessor operates
- on objects of that Mapper.
-
- The result is stored in the dictionary persistently once
- calculated.
-
- """
- return util.PopulateDict(
- lambda tup: tup[0]._props.get(tup[1].key) is tup[1].prop
- )
-
- def filter_states_for_dep(self, dep, states):
- """Filter the given list of InstanceStates to those relevant to the
- given DependencyProcessor.
-
- """
- mapper_for_dep = self._mapper_for_dep
- return [s for s in states if mapper_for_dep[(s.manager.mapper, dep)]]
-
- def states_for_mapper_hierarchy(self, mapper, isdelete, listonly):
- checktup = (isdelete, listonly)
- for mapper in mapper.base_mapper.self_and_descendants:
- for state in self.mappers[mapper]:
- if self.states[state] == checktup:
- yield state
-
- def _generate_actions(self):
- """Generate the full, unsorted collection of PostSortRecs as
- well as dependency pairs for this UOWTransaction.
-
- """
- # execute presort_actions, until all states
- # have been processed. a presort_action might
- # add new states to the uow.
- while True:
- ret = False
- for action in list(self.presort_actions.values()):
- if action.execute(self):
- ret = True
- if not ret:
- break
-
- # see if the graph of mapper dependencies has cycles.
- self.cycles = cycles = topological.find_cycles(
- self.dependencies, list(self.postsort_actions.values())
- )
-
- if cycles:
- # if yes, break the per-mapper actions into
- # per-state actions
- convert = {
- rec: set(rec.per_state_flush_actions(self)) for rec in cycles
- }
-
- # rewrite the existing dependencies to point to
- # the per-state actions for those per-mapper actions
- # that were broken up.
- for edge in list(self.dependencies):
- if (
- None in edge
- or edge[0].disabled
- or edge[1].disabled
- or cycles.issuperset(edge)
- ):
- self.dependencies.remove(edge)
- elif edge[0] in cycles:
- self.dependencies.remove(edge)
- for dep in convert[edge[0]]:
- self.dependencies.add((dep, edge[1]))
- elif edge[1] in cycles:
- self.dependencies.remove(edge)
- for dep in convert[edge[1]]:
- self.dependencies.add((edge[0], dep))
-
- return {
- a for a in self.postsort_actions.values() if not a.disabled
- }.difference(cycles)
-
- def execute(self) -> None:
- postsort_actions = self._generate_actions()
-
- postsort_actions = sorted(
- postsort_actions,
- key=lambda item: item.sort_key,
- )
- # sort = topological.sort(self.dependencies, postsort_actions)
- # print "--------------"
- # print "\ndependencies:", self.dependencies
- # print "\ncycles:", self.cycles
- # print "\nsort:", list(sort)
- # print "\nCOUNT OF POSTSORT ACTIONS", len(postsort_actions)
-
- # execute
- if self.cycles:
- for subset in topological.sort_as_subsets(
- self.dependencies, postsort_actions
- ):
- set_ = set(subset)
- while set_:
- n = set_.pop()
- n.execute_aggregate(self, set_)
- else:
- for rec in topological.sort(self.dependencies, postsort_actions):
- rec.execute(self)
-
- def finalize_flush_changes(self) -> None:
- """Mark processed objects as clean / deleted after a successful
- flush().
-
- This method is called within the flush() method after the
- execute() method has succeeded and the transaction has been committed.
-
- """
- if not self.states:
- return
-
- states = set(self.states)
- isdel = {
- s for (s, (isdelete, listonly)) in self.states.items() if isdelete
- }
- other = states.difference(isdel)
- if isdel:
- self.session._remove_newly_deleted(isdel)
- if other:
- self.session._register_persistent(other)
-
-
-class IterateMappersMixin:
- __slots__ = ()
-
- def _mappers(self, uow):
- if self.fromparent:
- return iter(
- m
- for m in self.dependency_processor.parent.self_and_descendants
- if uow._mapper_for_dep[(m, self.dependency_processor)]
- )
- else:
- return self.dependency_processor.mapper.self_and_descendants
-
-
-class Preprocess(IterateMappersMixin):
- __slots__ = (
- "dependency_processor",
- "fromparent",
- "processed",
- "setup_flush_actions",
- )
-
- def __init__(self, dependency_processor, fromparent):
- self.dependency_processor = dependency_processor
- self.fromparent = fromparent
- self.processed = set()
- self.setup_flush_actions = False
-
- def execute(self, uow):
- delete_states = set()
- save_states = set()
-
- for mapper in self._mappers(uow):
- for state in uow.mappers[mapper].difference(self.processed):
- (isdelete, listonly) = uow.states[state]
- if not listonly:
- if isdelete:
- delete_states.add(state)
- else:
- save_states.add(state)
-
- if delete_states:
- self.dependency_processor.presort_deletes(uow, delete_states)
- self.processed.update(delete_states)
- if save_states:
- self.dependency_processor.presort_saves(uow, save_states)
- self.processed.update(save_states)
-
- if delete_states or save_states:
- if not self.setup_flush_actions and (
- self.dependency_processor.prop_has_changes(
- uow, delete_states, True
- )
- or self.dependency_processor.prop_has_changes(
- uow, save_states, False
- )
- ):
- self.dependency_processor.per_property_flush_actions(uow)
- self.setup_flush_actions = True
- return True
- else:
- return False
-
-
-class PostSortRec:
- __slots__ = ("disabled",)
-
- def __new__(cls, uow, *args):
- key = (cls,) + args
- if key in uow.postsort_actions:
- return uow.postsort_actions[key]
- else:
- uow.postsort_actions[key] = ret = object.__new__(cls)
- ret.disabled = False
- return ret
-
- def execute_aggregate(self, uow, recs):
- self.execute(uow)
-
-
-class ProcessAll(IterateMappersMixin, PostSortRec):
- __slots__ = "dependency_processor", "isdelete", "fromparent", "sort_key"
-
- def __init__(self, uow, dependency_processor, isdelete, fromparent):
- self.dependency_processor = dependency_processor
- self.sort_key = (
- "ProcessAll",
- self.dependency_processor.sort_key,
- isdelete,
- )
- self.isdelete = isdelete
- self.fromparent = fromparent
- uow.deps[dependency_processor.parent.base_mapper].add(
- dependency_processor
- )
-
- def execute(self, uow):
- states = self._elements(uow)
- if self.isdelete:
- self.dependency_processor.process_deletes(uow, states)
- else:
- self.dependency_processor.process_saves(uow, states)
-
- def per_state_flush_actions(self, uow):
- # this is handled by SaveUpdateAll and DeleteAll,
- # since a ProcessAll should unconditionally be pulled
- # into per-state if either the parent/child mappers
- # are part of a cycle
- return iter([])
-
- def __repr__(self):
- return "%s(%s, isdelete=%s)" % (
- self.__class__.__name__,
- self.dependency_processor,
- self.isdelete,
- )
-
- def _elements(self, uow):
- for mapper in self._mappers(uow):
- for state in uow.mappers[mapper]:
- (isdelete, listonly) = uow.states[state]
- if isdelete == self.isdelete and not listonly:
- yield state
-
-
-class PostUpdateAll(PostSortRec):
- __slots__ = "mapper", "isdelete", "sort_key"
-
- def __init__(self, uow, mapper, isdelete):
- self.mapper = mapper
- self.isdelete = isdelete
- self.sort_key = ("PostUpdateAll", mapper._sort_key, isdelete)
-
- @util.preload_module("sqlalchemy.orm.persistence")
- def execute(self, uow):
- persistence = util.preloaded.orm_persistence
- states, cols = uow.post_update_states[self.mapper]
- states = [s for s in states if uow.states[s][0] == self.isdelete]
-
- persistence.post_update(self.mapper, states, uow, cols)
-
-
-class SaveUpdateAll(PostSortRec):
- __slots__ = ("mapper", "sort_key")
-
- def __init__(self, uow, mapper):
- self.mapper = mapper
- self.sort_key = ("SaveUpdateAll", mapper._sort_key)
- assert mapper is mapper.base_mapper
-
- @util.preload_module("sqlalchemy.orm.persistence")
- def execute(self, uow):
- util.preloaded.orm_persistence.save_obj(
- self.mapper,
- uow.states_for_mapper_hierarchy(self.mapper, False, False),
- uow,
- )
-
- def per_state_flush_actions(self, uow):
- states = list(
- uow.states_for_mapper_hierarchy(self.mapper, False, False)
- )
- base_mapper = self.mapper.base_mapper
- delete_all = DeleteAll(uow, base_mapper)
- for state in states:
- # keep saves before deletes -
- # this ensures 'row switch' operations work
- action = SaveUpdateState(uow, state)
- uow.dependencies.add((action, delete_all))
- yield action
-
- for dep in uow.deps[self.mapper]:
- states_for_prop = uow.filter_states_for_dep(dep, states)
- dep.per_state_flush_actions(uow, states_for_prop, False)
-
- def __repr__(self):
- return "%s(%s)" % (self.__class__.__name__, self.mapper)
-
-
-class DeleteAll(PostSortRec):
- __slots__ = ("mapper", "sort_key")
-
- def __init__(self, uow, mapper):
- self.mapper = mapper
- self.sort_key = ("DeleteAll", mapper._sort_key)
- assert mapper is mapper.base_mapper
-
- @util.preload_module("sqlalchemy.orm.persistence")
- def execute(self, uow):
- util.preloaded.orm_persistence.delete_obj(
- self.mapper,
- uow.states_for_mapper_hierarchy(self.mapper, True, False),
- uow,
- )
-
- def per_state_flush_actions(self, uow):
- states = list(
- uow.states_for_mapper_hierarchy(self.mapper, True, False)
- )
- base_mapper = self.mapper.base_mapper
- save_all = SaveUpdateAll(uow, base_mapper)
- for state in states:
- # keep saves before deletes -
- # this ensures 'row switch' operations work
- action = DeleteState(uow, state)
- uow.dependencies.add((save_all, action))
- yield action
-
- for dep in uow.deps[self.mapper]:
- states_for_prop = uow.filter_states_for_dep(dep, states)
- dep.per_state_flush_actions(uow, states_for_prop, True)
-
- def __repr__(self):
- return "%s(%s)" % (self.__class__.__name__, self.mapper)
-
-
-class ProcessState(PostSortRec):
- __slots__ = "dependency_processor", "isdelete", "state", "sort_key"
-
- def __init__(self, uow, dependency_processor, isdelete, state):
- self.dependency_processor = dependency_processor
- self.sort_key = ("ProcessState", dependency_processor.sort_key)
- self.isdelete = isdelete
- self.state = state
-
- def execute_aggregate(self, uow, recs):
- cls_ = self.__class__
- dependency_processor = self.dependency_processor
- isdelete = self.isdelete
- our_recs = [
- r
- for r in recs
- if r.__class__ is cls_
- and r.dependency_processor is dependency_processor
- and r.isdelete is isdelete
- ]
- recs.difference_update(our_recs)
- states = [self.state] + [r.state for r in our_recs]
- if isdelete:
- dependency_processor.process_deletes(uow, states)
- else:
- dependency_processor.process_saves(uow, states)
-
- def __repr__(self):
- return "%s(%s, %s, delete=%s)" % (
- self.__class__.__name__,
- self.dependency_processor,
- orm_util.state_str(self.state),
- self.isdelete,
- )
-
-
-class SaveUpdateState(PostSortRec):
- __slots__ = "state", "mapper", "sort_key"
-
- def __init__(self, uow, state):
- self.state = state
- self.mapper = state.mapper.base_mapper
- self.sort_key = ("ProcessState", self.mapper._sort_key)
-
- @util.preload_module("sqlalchemy.orm.persistence")
- def execute_aggregate(self, uow, recs):
- persistence = util.preloaded.orm_persistence
- cls_ = self.__class__
- mapper = self.mapper
- our_recs = [
- r for r in recs if r.__class__ is cls_ and r.mapper is mapper
- ]
- recs.difference_update(our_recs)
- persistence.save_obj(
- mapper, [self.state] + [r.state for r in our_recs], uow
- )
-
- def __repr__(self):
- return "%s(%s)" % (
- self.__class__.__name__,
- orm_util.state_str(self.state),
- )
-
-
-class DeleteState(PostSortRec):
- __slots__ = "state", "mapper", "sort_key"
-
- def __init__(self, uow, state):
- self.state = state
- self.mapper = state.mapper.base_mapper
- self.sort_key = ("DeleteState", self.mapper._sort_key)
-
- @util.preload_module("sqlalchemy.orm.persistence")
- def execute_aggregate(self, uow, recs):
- persistence = util.preloaded.orm_persistence
- cls_ = self.__class__
- mapper = self.mapper
- our_recs = [
- r for r in recs if r.__class__ is cls_ and r.mapper is mapper
- ]
- recs.difference_update(our_recs)
- states = [self.state] + [r.state for r in our_recs]
- persistence.delete_obj(
- mapper, [s for s in states if uow.states[s][0]], uow
- )
-
- def __repr__(self):
- return "%s(%s)" % (
- self.__class__.__name__,
- orm_util.state_str(self.state),
- )
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/util.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/util.py
deleted file mode 100644
index 8e153e6..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/util.py
+++ /dev/null
@@ -1,2416 +0,0 @@
-# orm/util.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
-# mypy: allow-untyped-defs, allow-untyped-calls
-
-from __future__ import annotations
-
-import enum
-import functools
-import re
-import types
-import typing
-from typing import AbstractSet
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Dict
-from typing import FrozenSet
-from typing import Generic
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Match
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-import weakref
-
-from . import attributes # noqa
-from . import exc
-from ._typing import _O
-from ._typing import insp_is_aliased_class
-from ._typing import insp_is_mapper
-from ._typing import prop_is_relationship
-from .base import _class_to_mapper as _class_to_mapper
-from .base import _MappedAnnotationBase
-from .base import _never_set as _never_set # noqa: F401
-from .base import _none_set as _none_set # noqa: F401
-from .base import attribute_str as attribute_str # noqa: F401
-from .base import class_mapper as class_mapper
-from .base import DynamicMapped
-from .base import InspectionAttr as InspectionAttr
-from .base import instance_str as instance_str # noqa: F401
-from .base import Mapped
-from .base import object_mapper as object_mapper
-from .base import object_state as object_state # noqa: F401
-from .base import opt_manager_of_class
-from .base import ORMDescriptor
-from .base import state_attribute_str as state_attribute_str # noqa: F401
-from .base import state_class_str as state_class_str # noqa: F401
-from .base import state_str as state_str # noqa: F401
-from .base import WriteOnlyMapped
-from .interfaces import CriteriaOption
-from .interfaces import MapperProperty as MapperProperty
-from .interfaces import ORMColumnsClauseRole
-from .interfaces import ORMEntityColumnsClauseRole
-from .interfaces import ORMFromClauseRole
-from .path_registry import PathRegistry as PathRegistry
-from .. import event
-from .. import exc as sa_exc
-from .. import inspection
-from .. import sql
-from .. import util
-from ..engine.result import result_tuple
-from ..sql import coercions
-from ..sql import expression
-from ..sql import lambdas
-from ..sql import roles
-from ..sql import util as sql_util
-from ..sql import visitors
-from ..sql._typing import is_selectable
-from ..sql.annotation import SupportsCloneAnnotations
-from ..sql.base import ColumnCollection
-from ..sql.cache_key import HasCacheKey
-from ..sql.cache_key import MemoizedHasCacheKey
-from ..sql.elements import ColumnElement
-from ..sql.elements import KeyedColumnElement
-from ..sql.selectable import FromClause
-from ..util.langhelpers import MemoizedSlots
-from ..util.typing import de_stringify_annotation as _de_stringify_annotation
-from ..util.typing import (
- de_stringify_union_elements as _de_stringify_union_elements,
-)
-from ..util.typing import eval_name_only as _eval_name_only
-from ..util.typing import is_origin_of_cls
-from ..util.typing import Literal
-from ..util.typing import Protocol
-from ..util.typing import typing_get_origin
-
-if typing.TYPE_CHECKING:
- from ._typing import _EntityType
- from ._typing import _IdentityKeyType
- from ._typing import _InternalEntityType
- from ._typing import _ORMCOLEXPR
- from .context import _MapperEntity
- from .context import ORMCompileState
- from .mapper import Mapper
- from .path_registry import AbstractEntityRegistry
- from .query import Query
- from .relationships import RelationshipProperty
- from ..engine import Row
- from ..engine import RowMapping
- from ..sql._typing import _CE
- from ..sql._typing import _ColumnExpressionArgument
- from ..sql._typing import _EquivalentColumnMap
- from ..sql._typing import _FromClauseArgument
- from ..sql._typing import _OnClauseArgument
- from ..sql._typing import _PropagateAttrsType
- from ..sql.annotation import _SA
- from ..sql.base import ReadOnlyColumnCollection
- from ..sql.elements import BindParameter
- from ..sql.selectable import _ColumnsClauseElement
- from ..sql.selectable import Select
- from ..sql.selectable import Selectable
- from ..sql.visitors import anon_map
- from ..util.typing import _AnnotationScanType
- from ..util.typing import ArgsTypeProcotol
-
-_T = TypeVar("_T", bound=Any)
-
-all_cascades = frozenset(
- (
- "delete",
- "delete-orphan",
- "all",
- "merge",
- "expunge",
- "save-update",
- "refresh-expire",
- "none",
- )
-)
-
-
-_de_stringify_partial = functools.partial(
- functools.partial,
- locals_=util.immutabledict(
- {
- "Mapped": Mapped,
- "WriteOnlyMapped": WriteOnlyMapped,
- "DynamicMapped": DynamicMapped,
- }
- ),
-)
-
-# partial is practically useless as we have to write out the whole
-# function and maintain the signature anyway
-
-
-class _DeStringifyAnnotation(Protocol):
- def __call__(
- self,
- cls: Type[Any],
- annotation: _AnnotationScanType,
- originating_module: str,
- *,
- str_cleanup_fn: Optional[Callable[[str, str], str]] = None,
- include_generic: bool = False,
- ) -> Type[Any]: ...
-
-
-de_stringify_annotation = cast(
- _DeStringifyAnnotation, _de_stringify_partial(_de_stringify_annotation)
-)
-
-
-class _DeStringifyUnionElements(Protocol):
- def __call__(
- self,
- cls: Type[Any],
- annotation: ArgsTypeProcotol,
- originating_module: str,
- *,
- str_cleanup_fn: Optional[Callable[[str, str], str]] = None,
- ) -> Type[Any]: ...
-
-
-de_stringify_union_elements = cast(
- _DeStringifyUnionElements,
- _de_stringify_partial(_de_stringify_union_elements),
-)
-
-
-class _EvalNameOnly(Protocol):
- def __call__(self, name: str, module_name: str) -> Any: ...
-
-
-eval_name_only = cast(_EvalNameOnly, _de_stringify_partial(_eval_name_only))
-
-
-class CascadeOptions(FrozenSet[str]):
- """Keeps track of the options sent to
- :paramref:`.relationship.cascade`"""
-
- _add_w_all_cascades = all_cascades.difference(
- ["all", "none", "delete-orphan"]
- )
- _allowed_cascades = all_cascades
-
- _viewonly_cascades = ["expunge", "all", "none", "refresh-expire", "merge"]
-
- __slots__ = (
- "save_update",
- "delete",
- "refresh_expire",
- "merge",
- "expunge",
- "delete_orphan",
- )
-
- save_update: bool
- delete: bool
- refresh_expire: bool
- merge: bool
- expunge: bool
- delete_orphan: bool
-
- def __new__(
- cls, value_list: Optional[Union[Iterable[str], str]]
- ) -> CascadeOptions:
- if isinstance(value_list, str) or value_list is None:
- return cls.from_string(value_list) # type: ignore
- values = set(value_list)
- if values.difference(cls._allowed_cascades):
- raise sa_exc.ArgumentError(
- "Invalid cascade option(s): %s"
- % ", ".join(
- [
- repr(x)
- for x in sorted(
- values.difference(cls._allowed_cascades)
- )
- ]
- )
- )
-
- if "all" in values:
- values.update(cls._add_w_all_cascades)
- if "none" in values:
- values.clear()
- values.discard("all")
-
- self = super().__new__(cls, values)
- self.save_update = "save-update" in values
- self.delete = "delete" in values
- self.refresh_expire = "refresh-expire" in values
- self.merge = "merge" in values
- self.expunge = "expunge" in values
- self.delete_orphan = "delete-orphan" in values
-
- if self.delete_orphan and not self.delete:
- util.warn("The 'delete-orphan' cascade option requires 'delete'.")
- return self
-
- def __repr__(self):
- return "CascadeOptions(%r)" % (",".join([x for x in sorted(self)]))
-
- @classmethod
- def from_string(cls, arg):
- values = [c for c in re.split(r"\s*,\s*", arg or "") if c]
- return cls(values)
-
-
-def _validator_events(desc, key, validator, include_removes, include_backrefs):
- """Runs a validation method on an attribute value to be set or
- appended.
- """
-
- if not include_backrefs:
-
- def detect_is_backref(state, initiator):
- impl = state.manager[key].impl
- return initiator.impl is not impl
-
- if include_removes:
-
- def append(state, value, initiator):
- if initiator.op is not attributes.OP_BULK_REPLACE and (
- include_backrefs or not detect_is_backref(state, initiator)
- ):
- return validator(state.obj(), key, value, False)
- else:
- return value
-
- def bulk_set(state, values, initiator):
- if include_backrefs or not detect_is_backref(state, initiator):
- obj = state.obj()
- values[:] = [
- validator(obj, key, value, False) for value in values
- ]
-
- def set_(state, value, oldvalue, initiator):
- if include_backrefs or not detect_is_backref(state, initiator):
- return validator(state.obj(), key, value, False)
- else:
- return value
-
- def remove(state, value, initiator):
- if include_backrefs or not detect_is_backref(state, initiator):
- validator(state.obj(), key, value, True)
-
- else:
-
- def append(state, value, initiator):
- if initiator.op is not attributes.OP_BULK_REPLACE and (
- include_backrefs or not detect_is_backref(state, initiator)
- ):
- return validator(state.obj(), key, value)
- else:
- return value
-
- def bulk_set(state, values, initiator):
- if include_backrefs or not detect_is_backref(state, initiator):
- obj = state.obj()
- values[:] = [validator(obj, key, value) for value in values]
-
- def set_(state, value, oldvalue, initiator):
- if include_backrefs or not detect_is_backref(state, initiator):
- return validator(state.obj(), key, value)
- else:
- return value
-
- event.listen(desc, "append", append, raw=True, retval=True)
- event.listen(desc, "bulk_replace", bulk_set, raw=True)
- event.listen(desc, "set", set_, raw=True, retval=True)
- if include_removes:
- event.listen(desc, "remove", remove, raw=True, retval=True)
-
-
-def polymorphic_union(
- table_map, typecolname, aliasname="p_union", cast_nulls=True
-):
- """Create a ``UNION`` statement used by a polymorphic mapper.
-
- See :ref:`concrete_inheritance` for an example of how
- this is used.
-
- :param table_map: mapping of polymorphic identities to
- :class:`_schema.Table` objects.
- :param typecolname: string name of a "discriminator" column, which will be
- derived from the query, producing the polymorphic identity for
- each row. If ``None``, no polymorphic discriminator is generated.
- :param aliasname: name of the :func:`~sqlalchemy.sql.expression.alias()`
- construct generated.
- :param cast_nulls: if True, non-existent columns, which are represented
- as labeled NULLs, will be passed into CAST. This is a legacy behavior
- that is problematic on some backends such as Oracle - in which case it
- can be set to False.
-
- """
-
- colnames: util.OrderedSet[str] = util.OrderedSet()
- colnamemaps = {}
- types = {}
- for key in table_map:
- table = table_map[key]
-
- table = coercions.expect(
- roles.StrictFromClauseRole, table, allow_select=True
- )
- table_map[key] = table
-
- m = {}
- for c in table.c:
- if c.key == typecolname:
- raise sa_exc.InvalidRequestError(
- "Polymorphic union can't use '%s' as the discriminator "
- "column due to mapped column %r; please apply the "
- "'typecolname' "
- "argument; this is available on "
- "ConcreteBase as '_concrete_discriminator_name'"
- % (typecolname, c)
- )
- colnames.add(c.key)
- m[c.key] = c
- types[c.key] = c.type
- colnamemaps[table] = m
-
- def col(name, table):
- try:
- return colnamemaps[table][name]
- except KeyError:
- if cast_nulls:
- return sql.cast(sql.null(), types[name]).label(name)
- else:
- return sql.type_coerce(sql.null(), types[name]).label(name)
-
- result = []
- for type_, table in table_map.items():
- if typecolname is not None:
- result.append(
- sql.select(
- *(
- [col(name, table) for name in colnames]
- + [
- sql.literal_column(
- sql_util._quote_ddl_expr(type_)
- ).label(typecolname)
- ]
- )
- ).select_from(table)
- )
- else:
- result.append(
- sql.select(
- *[col(name, table) for name in colnames]
- ).select_from(table)
- )
- return sql.union_all(*result).alias(aliasname)
-
-
-def identity_key(
- class_: Optional[Type[_T]] = None,
- ident: Union[Any, Tuple[Any, ...]] = None,
- *,
- instance: Optional[_T] = None,
- row: Optional[Union[Row[Any], RowMapping]] = None,
- identity_token: Optional[Any] = None,
-) -> _IdentityKeyType[_T]:
- r"""Generate "identity key" tuples, as are used as keys in the
- :attr:`.Session.identity_map` dictionary.
-
- This function has several call styles:
-
- * ``identity_key(class, ident, identity_token=token)``
-
- This form receives a mapped class and a primary key scalar or
- tuple as an argument.
-
- E.g.::
-
- >>> identity_key(MyClass, (1, 2))
- (<class '__main__.MyClass'>, (1, 2), None)
-
- :param class: mapped class (must be a positional argument)
- :param ident: primary key, may be a scalar or tuple argument.
- :param identity_token: optional identity token
-
- .. versionadded:: 1.2 added identity_token
-
-
- * ``identity_key(instance=instance)``
-
- This form will produce the identity key for a given instance. The
- instance need not be persistent, only that its primary key attributes
- are populated (else the key will contain ``None`` for those missing
- values).
-
- E.g.::
-
- >>> instance = MyClass(1, 2)
- >>> identity_key(instance=instance)
- (<class '__main__.MyClass'>, (1, 2), None)
-
- In this form, the given instance is ultimately run though
- :meth:`_orm.Mapper.identity_key_from_instance`, which will have the
- effect of performing a database check for the corresponding row
- if the object is expired.
-
- :param instance: object instance (must be given as a keyword arg)
-
- * ``identity_key(class, row=row, identity_token=token)``
-
- This form is similar to the class/tuple form, except is passed a
- database result row as a :class:`.Row` or :class:`.RowMapping` object.
-
- E.g.::
-
- >>> row = engine.execute(\
- text("select * from table where a=1 and b=2")\
- ).first()
- >>> identity_key(MyClass, row=row)
- (<class '__main__.MyClass'>, (1, 2), None)
-
- :param class: mapped class (must be a positional argument)
- :param row: :class:`.Row` row returned by a :class:`_engine.CursorResult`
- (must be given as a keyword arg)
- :param identity_token: optional identity token
-
- .. versionadded:: 1.2 added identity_token
-
- """
- if class_ is not None:
- mapper = class_mapper(class_)
- if row is None:
- if ident is None:
- raise sa_exc.ArgumentError("ident or row is required")
- return mapper.identity_key_from_primary_key(
- tuple(util.to_list(ident)), identity_token=identity_token
- )
- else:
- return mapper.identity_key_from_row(
- row, identity_token=identity_token
- )
- elif instance is not None:
- mapper = object_mapper(instance)
- return mapper.identity_key_from_instance(instance)
- else:
- raise sa_exc.ArgumentError("class or instance is required")
-
-
-class _TraceAdaptRole(enum.Enum):
- """Enumeration of all the use cases for ORMAdapter.
-
- ORMAdapter remains one of the most complicated aspects of the ORM, as it is
- used for in-place adaption of column expressions to be applied to a SELECT,
- replacing :class:`.Table` and other objects that are mapped to classes with
- aliases of those tables in the case of joined eager loading, or in the case
- of polymorphic loading as used with concrete mappings or other custom "with
- polymorphic" parameters, with whole user-defined subqueries. The
- enumerations provide an overview of all the use cases used by ORMAdapter, a
- layer of formality as to the introduction of new ORMAdapter use cases (of
- which none are anticipated), as well as a means to trace the origins of a
- particular ORMAdapter within runtime debugging.
-
- SQLAlchemy 2.0 has greatly scaled back ORM features which relied heavily on
- open-ended statement adaption, including the ``Query.with_polymorphic()``
- method and the ``Query.select_from_entity()`` methods, favoring
- user-explicit aliasing schemes using the ``aliased()`` and
- ``with_polymorphic()`` standalone constructs; these still use adaption,
- however the adaption is applied in a narrower scope.
-
- """
-
- # aliased() use that is used to adapt individual attributes at query
- # construction time
- ALIASED_INSP = enum.auto()
-
- # joinedload cases; typically adapt an ON clause of a relationship
- # join
- JOINEDLOAD_USER_DEFINED_ALIAS = enum.auto()
- JOINEDLOAD_PATH_WITH_POLYMORPHIC = enum.auto()
- JOINEDLOAD_MEMOIZED_ADAPTER = enum.auto()
-
- # polymorphic cases - these are complex ones that replace FROM
- # clauses, replacing tables with subqueries
- MAPPER_POLYMORPHIC_ADAPTER = enum.auto()
- WITH_POLYMORPHIC_ADAPTER = enum.auto()
- WITH_POLYMORPHIC_ADAPTER_RIGHT_JOIN = enum.auto()
- DEPRECATED_JOIN_ADAPT_RIGHT_SIDE = enum.auto()
-
- # the from_statement() case, used only to adapt individual attributes
- # from a given statement to local ORM attributes at result fetching
- # time. assigned to ORMCompileState._from_obj_alias
- ADAPT_FROM_STATEMENT = enum.auto()
-
- # the joinedload for queries that have LIMIT/OFFSET/DISTINCT case;
- # the query is placed inside of a subquery with the LIMIT/OFFSET/etc.,
- # joinedloads are then placed on the outside.
- # assigned to ORMCompileState.compound_eager_adapter
- COMPOUND_EAGER_STATEMENT = enum.auto()
-
- # the legacy Query._set_select_from() case.
- # this is needed for Query's set operations (i.e. UNION, etc. )
- # as well as "legacy from_self()", which while removed from 2.0 as
- # public API, is used for the Query.count() method. this one
- # still does full statement traversal
- # assigned to ORMCompileState._from_obj_alias
- LEGACY_SELECT_FROM_ALIAS = enum.auto()
-
-
-class ORMStatementAdapter(sql_util.ColumnAdapter):
- """ColumnAdapter which includes a role attribute."""
-
- __slots__ = ("role",)
-
- def __init__(
- self,
- role: _TraceAdaptRole,
- selectable: Selectable,
- *,
- equivalents: Optional[_EquivalentColumnMap] = None,
- adapt_required: bool = False,
- allow_label_resolve: bool = True,
- anonymize_labels: bool = False,
- adapt_on_names: bool = False,
- adapt_from_selectables: Optional[AbstractSet[FromClause]] = None,
- ):
- self.role = role
- super().__init__(
- selectable,
- equivalents=equivalents,
- adapt_required=adapt_required,
- allow_label_resolve=allow_label_resolve,
- anonymize_labels=anonymize_labels,
- adapt_on_names=adapt_on_names,
- adapt_from_selectables=adapt_from_selectables,
- )
-
-
-class ORMAdapter(sql_util.ColumnAdapter):
- """ColumnAdapter subclass which excludes adaptation of entities from
- non-matching mappers.
-
- """
-
- __slots__ = ("role", "mapper", "is_aliased_class", "aliased_insp")
-
- is_aliased_class: bool
- aliased_insp: Optional[AliasedInsp[Any]]
-
- def __init__(
- self,
- role: _TraceAdaptRole,
- entity: _InternalEntityType[Any],
- *,
- equivalents: Optional[_EquivalentColumnMap] = None,
- adapt_required: bool = False,
- allow_label_resolve: bool = True,
- anonymize_labels: bool = False,
- selectable: Optional[Selectable] = None,
- limit_on_entity: bool = True,
- adapt_on_names: bool = False,
- adapt_from_selectables: Optional[AbstractSet[FromClause]] = None,
- ):
- self.role = role
- self.mapper = entity.mapper
- if selectable is None:
- selectable = entity.selectable
- if insp_is_aliased_class(entity):
- self.is_aliased_class = True
- self.aliased_insp = entity
- else:
- self.is_aliased_class = False
- self.aliased_insp = None
-
- super().__init__(
- selectable,
- equivalents,
- adapt_required=adapt_required,
- allow_label_resolve=allow_label_resolve,
- anonymize_labels=anonymize_labels,
- include_fn=self._include_fn if limit_on_entity else None,
- adapt_on_names=adapt_on_names,
- adapt_from_selectables=adapt_from_selectables,
- )
-
- def _include_fn(self, elem):
- entity = elem._annotations.get("parentmapper", None)
-
- return not entity or entity.isa(self.mapper) or self.mapper.isa(entity)
-
-
-class AliasedClass(
- inspection.Inspectable["AliasedInsp[_O]"], ORMColumnsClauseRole[_O]
-):
- r"""Represents an "aliased" form of a mapped class for usage with Query.
-
- The ORM equivalent of a :func:`~sqlalchemy.sql.expression.alias`
- construct, this object mimics the mapped class using a
- ``__getattr__`` scheme and maintains a reference to a
- real :class:`~sqlalchemy.sql.expression.Alias` object.
-
- A primary purpose of :class:`.AliasedClass` is to serve as an alternate
- within a SQL statement generated by the ORM, such that an existing
- mapped entity can be used in multiple contexts. A simple example::
-
- # find all pairs of users with the same name
- user_alias = aliased(User)
- session.query(User, user_alias).\
- join((user_alias, User.id > user_alias.id)).\
- filter(User.name == user_alias.name)
-
- :class:`.AliasedClass` is also capable of mapping an existing mapped
- class to an entirely new selectable, provided this selectable is column-
- compatible with the existing mapped selectable, and it can also be
- configured in a mapping as the target of a :func:`_orm.relationship`.
- See the links below for examples.
-
- The :class:`.AliasedClass` object is constructed typically using the
- :func:`_orm.aliased` function. It also is produced with additional
- configuration when using the :func:`_orm.with_polymorphic` function.
-
- The resulting object is an instance of :class:`.AliasedClass`.
- This object implements an attribute scheme which produces the
- same attribute and method interface as the original mapped
- class, allowing :class:`.AliasedClass` to be compatible
- with any attribute technique which works on the original class,
- including hybrid attributes (see :ref:`hybrids_toplevel`).
-
- The :class:`.AliasedClass` can be inspected for its underlying
- :class:`_orm.Mapper`, aliased selectable, and other information
- using :func:`_sa.inspect`::
-
- from sqlalchemy import inspect
- my_alias = aliased(MyClass)
- insp = inspect(my_alias)
-
- The resulting inspection object is an instance of :class:`.AliasedInsp`.
-
-
- .. seealso::
-
- :func:`.aliased`
-
- :func:`.with_polymorphic`
-
- :ref:`relationship_aliased_class`
-
- :ref:`relationship_to_window_function`
-
-
- """
-
- __name__: str
-
- def __init__(
- self,
- mapped_class_or_ac: _EntityType[_O],
- alias: Optional[FromClause] = None,
- name: Optional[str] = None,
- flat: bool = False,
- adapt_on_names: bool = False,
- with_polymorphic_mappers: Optional[Sequence[Mapper[Any]]] = None,
- with_polymorphic_discriminator: Optional[ColumnElement[Any]] = None,
- base_alias: Optional[AliasedInsp[Any]] = None,
- use_mapper_path: bool = False,
- represents_outer_join: bool = False,
- ):
- insp = cast(
- "_InternalEntityType[_O]", inspection.inspect(mapped_class_or_ac)
- )
- mapper = insp.mapper
-
- nest_adapters = False
-
- if alias is None:
- if insp.is_aliased_class and insp.selectable._is_subquery:
- alias = insp.selectable.alias()
- else:
- alias = (
- mapper._with_polymorphic_selectable._anonymous_fromclause(
- name=name,
- flat=flat,
- )
- )
- elif insp.is_aliased_class:
- nest_adapters = True
-
- assert alias is not None
- self._aliased_insp = AliasedInsp(
- self,
- insp,
- alias,
- name,
- (
- with_polymorphic_mappers
- if with_polymorphic_mappers
- else mapper.with_polymorphic_mappers
- ),
- (
- with_polymorphic_discriminator
- if with_polymorphic_discriminator is not None
- else mapper.polymorphic_on
- ),
- base_alias,
- use_mapper_path,
- adapt_on_names,
- represents_outer_join,
- nest_adapters,
- )
-
- self.__name__ = f"aliased({mapper.class_.__name__})"
-
- @classmethod
- def _reconstitute_from_aliased_insp(
- cls, aliased_insp: AliasedInsp[_O]
- ) -> AliasedClass[_O]:
- obj = cls.__new__(cls)
- obj.__name__ = f"aliased({aliased_insp.mapper.class_.__name__})"
- obj._aliased_insp = aliased_insp
-
- if aliased_insp._is_with_polymorphic:
- for sub_aliased_insp in aliased_insp._with_polymorphic_entities:
- if sub_aliased_insp is not aliased_insp:
- ent = AliasedClass._reconstitute_from_aliased_insp(
- sub_aliased_insp
- )
- setattr(obj, sub_aliased_insp.class_.__name__, ent)
-
- return obj
-
- def __getattr__(self, key: str) -> Any:
- try:
- _aliased_insp = self.__dict__["_aliased_insp"]
- except KeyError:
- raise AttributeError()
- else:
- target = _aliased_insp._target
- # maintain all getattr mechanics
- attr = getattr(target, key)
-
- # attribute is a method, that will be invoked against a
- # "self"; so just return a new method with the same function and
- # new self
- if hasattr(attr, "__call__") and hasattr(attr, "__self__"):
- return types.MethodType(attr.__func__, self)
-
- # attribute is a descriptor, that will be invoked against a
- # "self"; so invoke the descriptor against this self
- if hasattr(attr, "__get__"):
- attr = attr.__get__(None, self)
-
- # attributes within the QueryableAttribute system will want this
- # to be invoked so the object can be adapted
- if hasattr(attr, "adapt_to_entity"):
- attr = attr.adapt_to_entity(_aliased_insp)
- setattr(self, key, attr)
-
- return attr
-
- def _get_from_serialized(
- self, key: str, mapped_class: _O, aliased_insp: AliasedInsp[_O]
- ) -> Any:
- # this method is only used in terms of the
- # sqlalchemy.ext.serializer extension
- attr = getattr(mapped_class, key)
- if hasattr(attr, "__call__") and hasattr(attr, "__self__"):
- return types.MethodType(attr.__func__, self)
-
- # attribute is a descriptor, that will be invoked against a
- # "self"; so invoke the descriptor against this self
- if hasattr(attr, "__get__"):
- attr = attr.__get__(None, self)
-
- # attributes within the QueryableAttribute system will want this
- # to be invoked so the object can be adapted
- if hasattr(attr, "adapt_to_entity"):
- aliased_insp._weak_entity = weakref.ref(self)
- attr = attr.adapt_to_entity(aliased_insp)
- setattr(self, key, attr)
-
- return attr
-
- def __repr__(self) -> str:
- return "<AliasedClass at 0x%x; %s>" % (
- id(self),
- self._aliased_insp._target.__name__,
- )
-
- def __str__(self) -> str:
- return str(self._aliased_insp)
-
-
-@inspection._self_inspects
-class AliasedInsp(
- ORMEntityColumnsClauseRole[_O],
- ORMFromClauseRole,
- HasCacheKey,
- InspectionAttr,
- MemoizedSlots,
- inspection.Inspectable["AliasedInsp[_O]"],
- Generic[_O],
-):
- """Provide an inspection interface for an
- :class:`.AliasedClass` object.
-
- The :class:`.AliasedInsp` object is returned
- given an :class:`.AliasedClass` using the
- :func:`_sa.inspect` function::
-
- from sqlalchemy import inspect
- from sqlalchemy.orm import aliased
-
- my_alias = aliased(MyMappedClass)
- insp = inspect(my_alias)
-
- Attributes on :class:`.AliasedInsp`
- include:
-
- * ``entity`` - the :class:`.AliasedClass` represented.
- * ``mapper`` - the :class:`_orm.Mapper` mapping the underlying class.
- * ``selectable`` - the :class:`_expression.Alias`
- construct which ultimately
- represents an aliased :class:`_schema.Table` or
- :class:`_expression.Select`
- construct.
- * ``name`` - the name of the alias. Also is used as the attribute
- name when returned in a result tuple from :class:`_query.Query`.
- * ``with_polymorphic_mappers`` - collection of :class:`_orm.Mapper`
- objects
- indicating all those mappers expressed in the select construct
- for the :class:`.AliasedClass`.
- * ``polymorphic_on`` - an alternate column or SQL expression which
- will be used as the "discriminator" for a polymorphic load.
-
- .. seealso::
-
- :ref:`inspection_toplevel`
-
- """
-
- __slots__ = (
- "__weakref__",
- "_weak_entity",
- "mapper",
- "selectable",
- "name",
- "_adapt_on_names",
- "with_polymorphic_mappers",
- "polymorphic_on",
- "_use_mapper_path",
- "_base_alias",
- "represents_outer_join",
- "persist_selectable",
- "local_table",
- "_is_with_polymorphic",
- "_with_polymorphic_entities",
- "_adapter",
- "_target",
- "__clause_element__",
- "_memoized_values",
- "_all_column_expressions",
- "_nest_adapters",
- )
-
- _cache_key_traversal = [
- ("name", visitors.ExtendedInternalTraversal.dp_string),
- ("_adapt_on_names", visitors.ExtendedInternalTraversal.dp_boolean),
- ("_use_mapper_path", visitors.ExtendedInternalTraversal.dp_boolean),
- ("_target", visitors.ExtendedInternalTraversal.dp_inspectable),
- ("selectable", visitors.ExtendedInternalTraversal.dp_clauseelement),
- (
- "with_polymorphic_mappers",
- visitors.InternalTraversal.dp_has_cache_key_list,
- ),
- ("polymorphic_on", visitors.InternalTraversal.dp_clauseelement),
- ]
-
- mapper: Mapper[_O]
- selectable: FromClause
- _adapter: ORMAdapter
- with_polymorphic_mappers: Sequence[Mapper[Any]]
- _with_polymorphic_entities: Sequence[AliasedInsp[Any]]
-
- _weak_entity: weakref.ref[AliasedClass[_O]]
- """the AliasedClass that refers to this AliasedInsp"""
-
- _target: Union[Type[_O], AliasedClass[_O]]
- """the thing referenced by the AliasedClass/AliasedInsp.
-
- In the vast majority of cases, this is the mapped class. However
- it may also be another AliasedClass (alias of alias).
-
- """
-
- def __init__(
- self,
- entity: AliasedClass[_O],
- inspected: _InternalEntityType[_O],
- selectable: FromClause,
- name: Optional[str],
- with_polymorphic_mappers: Optional[Sequence[Mapper[Any]]],
- polymorphic_on: Optional[ColumnElement[Any]],
- _base_alias: Optional[AliasedInsp[Any]],
- _use_mapper_path: bool,
- adapt_on_names: bool,
- represents_outer_join: bool,
- nest_adapters: bool,
- ):
- mapped_class_or_ac = inspected.entity
- mapper = inspected.mapper
-
- self._weak_entity = weakref.ref(entity)
- self.mapper = mapper
- self.selectable = self.persist_selectable = self.local_table = (
- selectable
- )
- self.name = name
- self.polymorphic_on = polymorphic_on
- self._base_alias = weakref.ref(_base_alias or self)
- self._use_mapper_path = _use_mapper_path
- self.represents_outer_join = represents_outer_join
- self._nest_adapters = nest_adapters
-
- if with_polymorphic_mappers:
- self._is_with_polymorphic = True
- self.with_polymorphic_mappers = with_polymorphic_mappers
- self._with_polymorphic_entities = []
- for poly in self.with_polymorphic_mappers:
- if poly is not mapper:
- ent = AliasedClass(
- poly.class_,
- selectable,
- base_alias=self,
- adapt_on_names=adapt_on_names,
- use_mapper_path=_use_mapper_path,
- )
-
- setattr(self.entity, poly.class_.__name__, ent)
- self._with_polymorphic_entities.append(ent._aliased_insp)
-
- else:
- self._is_with_polymorphic = False
- self.with_polymorphic_mappers = [mapper]
-
- self._adapter = ORMAdapter(
- _TraceAdaptRole.ALIASED_INSP,
- mapper,
- selectable=selectable,
- equivalents=mapper._equivalent_columns,
- adapt_on_names=adapt_on_names,
- anonymize_labels=True,
- # make sure the adapter doesn't try to grab other tables that
- # are not even the thing we are mapping, such as embedded
- # selectables in subqueries or CTEs. See issue #6060
- adapt_from_selectables={
- m.selectable
- for m in self.with_polymorphic_mappers
- if not adapt_on_names
- },
- limit_on_entity=False,
- )
-
- if nest_adapters:
- # supports "aliased class of aliased class" use case
- assert isinstance(inspected, AliasedInsp)
- self._adapter = inspected._adapter.wrap(self._adapter)
-
- self._adapt_on_names = adapt_on_names
- self._target = mapped_class_or_ac
-
- @classmethod
- def _alias_factory(
- cls,
- element: Union[_EntityType[_O], FromClause],
- alias: Optional[FromClause] = None,
- name: Optional[str] = None,
- flat: bool = False,
- adapt_on_names: bool = False,
- ) -> Union[AliasedClass[_O], FromClause]:
- if isinstance(element, FromClause):
- if adapt_on_names:
- raise sa_exc.ArgumentError(
- "adapt_on_names only applies to ORM elements"
- )
- if name:
- return element.alias(name=name, flat=flat)
- else:
- return coercions.expect(
- roles.AnonymizedFromClauseRole, element, flat=flat
- )
- else:
- return AliasedClass(
- element,
- alias=alias,
- flat=flat,
- name=name,
- adapt_on_names=adapt_on_names,
- )
-
- @classmethod
- def _with_polymorphic_factory(
- cls,
- base: Union[Type[_O], Mapper[_O]],
- classes: Union[Literal["*"], Iterable[_EntityType[Any]]],
- selectable: Union[Literal[False, None], FromClause] = False,
- flat: bool = False,
- polymorphic_on: Optional[ColumnElement[Any]] = None,
- aliased: bool = False,
- innerjoin: bool = False,
- adapt_on_names: bool = False,
- _use_mapper_path: bool = False,
- ) -> AliasedClass[_O]:
- primary_mapper = _class_to_mapper(base)
-
- if selectable not in (None, False) and flat:
- raise sa_exc.ArgumentError(
- "the 'flat' and 'selectable' arguments cannot be passed "
- "simultaneously to with_polymorphic()"
- )
-
- mappers, selectable = primary_mapper._with_polymorphic_args(
- classes, selectable, innerjoin=innerjoin
- )
- if aliased or flat:
- assert selectable is not None
- selectable = selectable._anonymous_fromclause(flat=flat)
-
- return AliasedClass(
- base,
- selectable,
- with_polymorphic_mappers=mappers,
- adapt_on_names=adapt_on_names,
- with_polymorphic_discriminator=polymorphic_on,
- use_mapper_path=_use_mapper_path,
- represents_outer_join=not innerjoin,
- )
-
- @property
- def entity(self) -> AliasedClass[_O]:
- # to eliminate reference cycles, the AliasedClass is held weakly.
- # this produces some situations where the AliasedClass gets lost,
- # particularly when one is created internally and only the AliasedInsp
- # is passed around.
- # to work around this case, we just generate a new one when we need
- # it, as it is a simple class with very little initial state on it.
- ent = self._weak_entity()
- if ent is None:
- ent = AliasedClass._reconstitute_from_aliased_insp(self)
- self._weak_entity = weakref.ref(ent)
- return ent
-
- is_aliased_class = True
- "always returns True"
-
- def _memoized_method___clause_element__(self) -> FromClause:
- return self.selectable._annotate(
- {
- "parentmapper": self.mapper,
- "parententity": self,
- "entity_namespace": self,
- }
- )._set_propagate_attrs(
- {"compile_state_plugin": "orm", "plugin_subject": self}
- )
-
- @property
- def entity_namespace(self) -> AliasedClass[_O]:
- return self.entity
-
- @property
- def class_(self) -> Type[_O]:
- """Return the mapped class ultimately represented by this
- :class:`.AliasedInsp`."""
- return self.mapper.class_
-
- @property
- def _path_registry(self) -> AbstractEntityRegistry:
- if self._use_mapper_path:
- return self.mapper._path_registry
- else:
- return PathRegistry.per_mapper(self)
-
- def __getstate__(self) -> Dict[str, Any]:
- return {
- "entity": self.entity,
- "mapper": self.mapper,
- "alias": self.selectable,
- "name": self.name,
- "adapt_on_names": self._adapt_on_names,
- "with_polymorphic_mappers": self.with_polymorphic_mappers,
- "with_polymorphic_discriminator": self.polymorphic_on,
- "base_alias": self._base_alias(),
- "use_mapper_path": self._use_mapper_path,
- "represents_outer_join": self.represents_outer_join,
- "nest_adapters": self._nest_adapters,
- }
-
- def __setstate__(self, state: Dict[str, Any]) -> None:
- self.__init__( # type: ignore
- state["entity"],
- state["mapper"],
- state["alias"],
- state["name"],
- state["with_polymorphic_mappers"],
- state["with_polymorphic_discriminator"],
- state["base_alias"],
- state["use_mapper_path"],
- state["adapt_on_names"],
- state["represents_outer_join"],
- state["nest_adapters"],
- )
-
- def _merge_with(self, other: AliasedInsp[_O]) -> AliasedInsp[_O]:
- # assert self._is_with_polymorphic
- # assert other._is_with_polymorphic
-
- primary_mapper = other.mapper
-
- assert self.mapper is primary_mapper
-
- our_classes = util.to_set(
- mp.class_ for mp in self.with_polymorphic_mappers
- )
- new_classes = {mp.class_ for mp in other.with_polymorphic_mappers}
- if our_classes == new_classes:
- return other
- else:
- classes = our_classes.union(new_classes)
-
- mappers, selectable = primary_mapper._with_polymorphic_args(
- classes, None, innerjoin=not other.represents_outer_join
- )
- selectable = selectable._anonymous_fromclause(flat=True)
- return AliasedClass(
- primary_mapper,
- selectable,
- with_polymorphic_mappers=mappers,
- with_polymorphic_discriminator=other.polymorphic_on,
- use_mapper_path=other._use_mapper_path,
- represents_outer_join=other.represents_outer_join,
- )._aliased_insp
-
- def _adapt_element(
- self, expr: _ORMCOLEXPR, key: Optional[str] = None
- ) -> _ORMCOLEXPR:
- assert isinstance(expr, ColumnElement)
- d: Dict[str, Any] = {
- "parententity": self,
- "parentmapper": self.mapper,
- }
- if key:
- d["proxy_key"] = key
-
- # IMO mypy should see this one also as returning the same type
- # we put into it, but it's not
- return (
- self._adapter.traverse(expr)
- ._annotate(d)
- ._set_propagate_attrs(
- {"compile_state_plugin": "orm", "plugin_subject": self}
- )
- )
-
- if TYPE_CHECKING:
- # establish compatibility with the _ORMAdapterProto protocol,
- # which in turn is compatible with _CoreAdapterProto.
-
- def _orm_adapt_element(
- self,
- obj: _CE,
- key: Optional[str] = None,
- ) -> _CE: ...
-
- else:
- _orm_adapt_element = _adapt_element
-
- def _entity_for_mapper(self, mapper):
- self_poly = self.with_polymorphic_mappers
- if mapper in self_poly:
- if mapper is self.mapper:
- return self
- else:
- return getattr(
- self.entity, mapper.class_.__name__
- )._aliased_insp
- elif mapper.isa(self.mapper):
- return self
- else:
- assert False, "mapper %s doesn't correspond to %s" % (mapper, self)
-
- def _memoized_attr__get_clause(self):
- onclause, replacemap = self.mapper._get_clause
- return (
- self._adapter.traverse(onclause),
- {
- self._adapter.traverse(col): param
- for col, param in replacemap.items()
- },
- )
-
- def _memoized_attr__memoized_values(self):
- return {}
-
- def _memoized_attr__all_column_expressions(self):
- if self._is_with_polymorphic:
- cols_plus_keys = self.mapper._columns_plus_keys(
- [ent.mapper for ent in self._with_polymorphic_entities]
- )
- else:
- cols_plus_keys = self.mapper._columns_plus_keys()
-
- cols_plus_keys = [
- (key, self._adapt_element(col)) for key, col in cols_plus_keys
- ]
-
- return ColumnCollection(cols_plus_keys)
-
- def _memo(self, key, callable_, *args, **kw):
- if key in self._memoized_values:
- return self._memoized_values[key]
- else:
- self._memoized_values[key] = value = callable_(*args, **kw)
- return value
-
- def __repr__(self):
- if self.with_polymorphic_mappers:
- with_poly = "(%s)" % ", ".join(
- mp.class_.__name__ for mp in self.with_polymorphic_mappers
- )
- else:
- with_poly = ""
- return "<AliasedInsp at 0x%x; %s%s>" % (
- id(self),
- self.class_.__name__,
- with_poly,
- )
-
- def __str__(self):
- if self._is_with_polymorphic:
- return "with_polymorphic(%s, [%s])" % (
- self._target.__name__,
- ", ".join(
- mp.class_.__name__
- for mp in self.with_polymorphic_mappers
- if mp is not self.mapper
- ),
- )
- else:
- return "aliased(%s)" % (self._target.__name__,)
-
-
-class _WrapUserEntity:
- """A wrapper used within the loader_criteria lambda caller so that
- we can bypass declared_attr descriptors on unmapped mixins, which
- normally emit a warning for such use.
-
- might also be useful for other per-lambda instrumentations should
- the need arise.
-
- """
-
- __slots__ = ("subject",)
-
- def __init__(self, subject):
- self.subject = subject
-
- @util.preload_module("sqlalchemy.orm.decl_api")
- def __getattribute__(self, name):
- decl_api = util.preloaded.orm.decl_api
-
- subject = object.__getattribute__(self, "subject")
- if name in subject.__dict__ and isinstance(
- subject.__dict__[name], decl_api.declared_attr
- ):
- return subject.__dict__[name].fget(subject)
- else:
- return getattr(subject, name)
-
-
-class LoaderCriteriaOption(CriteriaOption):
- """Add additional WHERE criteria to the load for all occurrences of
- a particular entity.
-
- :class:`_orm.LoaderCriteriaOption` is invoked using the
- :func:`_orm.with_loader_criteria` function; see that function for
- details.
-
- .. versionadded:: 1.4
-
- """
-
- __slots__ = (
- "root_entity",
- "entity",
- "deferred_where_criteria",
- "where_criteria",
- "_where_crit_orig",
- "include_aliases",
- "propagate_to_loaders",
- )
-
- _traverse_internals = [
- ("root_entity", visitors.ExtendedInternalTraversal.dp_plain_obj),
- ("entity", visitors.ExtendedInternalTraversal.dp_has_cache_key),
- ("where_criteria", visitors.InternalTraversal.dp_clauseelement),
- ("include_aliases", visitors.InternalTraversal.dp_boolean),
- ("propagate_to_loaders", visitors.InternalTraversal.dp_boolean),
- ]
-
- root_entity: Optional[Type[Any]]
- entity: Optional[_InternalEntityType[Any]]
- where_criteria: Union[ColumnElement[bool], lambdas.DeferredLambdaElement]
- deferred_where_criteria: bool
- include_aliases: bool
- propagate_to_loaders: bool
-
- _where_crit_orig: Any
-
- def __init__(
- self,
- entity_or_base: _EntityType[Any],
- where_criteria: Union[
- _ColumnExpressionArgument[bool],
- Callable[[Any], _ColumnExpressionArgument[bool]],
- ],
- loader_only: bool = False,
- include_aliases: bool = False,
- propagate_to_loaders: bool = True,
- track_closure_variables: bool = True,
- ):
- entity = cast(
- "_InternalEntityType[Any]",
- inspection.inspect(entity_or_base, False),
- )
- if entity is None:
- self.root_entity = cast("Type[Any]", entity_or_base)
- self.entity = None
- else:
- self.root_entity = None
- self.entity = entity
-
- self._where_crit_orig = where_criteria
- if callable(where_criteria):
- if self.root_entity is not None:
- wrap_entity = self.root_entity
- else:
- assert entity is not None
- wrap_entity = entity.entity
-
- self.deferred_where_criteria = True
- self.where_criteria = lambdas.DeferredLambdaElement(
- where_criteria,
- roles.WhereHavingRole,
- lambda_args=(_WrapUserEntity(wrap_entity),),
- opts=lambdas.LambdaOptions(
- track_closure_variables=track_closure_variables
- ),
- )
- else:
- self.deferred_where_criteria = False
- self.where_criteria = coercions.expect(
- roles.WhereHavingRole, where_criteria
- )
-
- self.include_aliases = include_aliases
- self.propagate_to_loaders = propagate_to_loaders
-
- @classmethod
- def _unreduce(
- cls, entity, where_criteria, include_aliases, propagate_to_loaders
- ):
- return LoaderCriteriaOption(
- entity,
- where_criteria,
- include_aliases=include_aliases,
- propagate_to_loaders=propagate_to_loaders,
- )
-
- def __reduce__(self):
- return (
- LoaderCriteriaOption._unreduce,
- (
- self.entity.class_ if self.entity else self.root_entity,
- self._where_crit_orig,
- self.include_aliases,
- self.propagate_to_loaders,
- ),
- )
-
- def _all_mappers(self) -> Iterator[Mapper[Any]]:
- if self.entity:
- yield from self.entity.mapper.self_and_descendants
- else:
- assert self.root_entity
- stack = list(self.root_entity.__subclasses__())
- while stack:
- subclass = stack.pop(0)
- ent = cast(
- "_InternalEntityType[Any]",
- inspection.inspect(subclass, raiseerr=False),
- )
- if ent:
- yield from ent.mapper.self_and_descendants
- else:
- stack.extend(subclass.__subclasses__())
-
- def _should_include(self, compile_state: ORMCompileState) -> bool:
- if (
- compile_state.select_statement._annotations.get(
- "for_loader_criteria", None
- )
- is self
- ):
- return False
- return True
-
- def _resolve_where_criteria(
- self, ext_info: _InternalEntityType[Any]
- ) -> ColumnElement[bool]:
- if self.deferred_where_criteria:
- crit = cast(
- "ColumnElement[bool]",
- self.where_criteria._resolve_with_args(ext_info.entity),
- )
- else:
- crit = self.where_criteria # type: ignore
- assert isinstance(crit, ColumnElement)
- return sql_util._deep_annotate(
- crit,
- {"for_loader_criteria": self},
- detect_subquery_cols=True,
- ind_cols_on_fromclause=True,
- )
-
- def process_compile_state_replaced_entities(
- self,
- compile_state: ORMCompileState,
- mapper_entities: Iterable[_MapperEntity],
- ) -> None:
- self.process_compile_state(compile_state)
-
- def process_compile_state(self, compile_state: ORMCompileState) -> None:
- """Apply a modification to a given :class:`.CompileState`."""
-
- # if options to limit the criteria to immediate query only,
- # use compile_state.attributes instead
-
- self.get_global_criteria(compile_state.global_attributes)
-
- def get_global_criteria(self, attributes: Dict[Any, Any]) -> None:
- for mp in self._all_mappers():
- load_criteria = attributes.setdefault(
- ("additional_entity_criteria", mp), []
- )
-
- load_criteria.append(self)
-
-
-inspection._inspects(AliasedClass)(lambda target: target._aliased_insp)
-
-
-@inspection._inspects(type)
-def _inspect_mc(
- class_: Type[_O],
-) -> Optional[Mapper[_O]]:
- try:
- class_manager = opt_manager_of_class(class_)
- if class_manager is None or not class_manager.is_mapped:
- return None
- mapper = class_manager.mapper
- except exc.NO_STATE:
- return None
- else:
- return mapper
-
-
-GenericAlias = type(List[Any])
-
-
-@inspection._inspects(GenericAlias)
-def _inspect_generic_alias(
- class_: Type[_O],
-) -> Optional[Mapper[_O]]:
- origin = cast("Type[_O]", typing_get_origin(class_))
- return _inspect_mc(origin)
-
-
-@inspection._self_inspects
-class Bundle(
- ORMColumnsClauseRole[_T],
- SupportsCloneAnnotations,
- MemoizedHasCacheKey,
- inspection.Inspectable["Bundle[_T]"],
- InspectionAttr,
-):
- """A grouping of SQL expressions that are returned by a :class:`.Query`
- under one namespace.
-
- The :class:`.Bundle` essentially allows nesting of the tuple-based
- results returned by a column-oriented :class:`_query.Query` object.
- It also
- is extensible via simple subclassing, where the primary capability
- to override is that of how the set of expressions should be returned,
- allowing post-processing as well as custom return types, without
- involving ORM identity-mapped classes.
-
- .. seealso::
-
- :ref:`bundles`
-
-
- """
-
- single_entity = False
- """If True, queries for a single Bundle will be returned as a single
- entity, rather than an element within a keyed tuple."""
-
- is_clause_element = False
-
- is_mapper = False
-
- is_aliased_class = False
-
- is_bundle = True
-
- _propagate_attrs: _PropagateAttrsType = util.immutabledict()
-
- proxy_set = util.EMPTY_SET # type: ignore
-
- exprs: List[_ColumnsClauseElement]
-
- def __init__(
- self, name: str, *exprs: _ColumnExpressionArgument[Any], **kw: Any
- ):
- r"""Construct a new :class:`.Bundle`.
-
- e.g.::
-
- bn = Bundle("mybundle", MyClass.x, MyClass.y)
-
- for row in session.query(bn).filter(
- bn.c.x == 5).filter(bn.c.y == 4):
- print(row.mybundle.x, row.mybundle.y)
-
- :param name: name of the bundle.
- :param \*exprs: columns or SQL expressions comprising the bundle.
- :param single_entity=False: if True, rows for this :class:`.Bundle`
- can be returned as a "single entity" outside of any enclosing tuple
- in the same manner as a mapped entity.
-
- """
- self.name = self._label = name
- coerced_exprs = [
- coercions.expect(
- roles.ColumnsClauseRole, expr, apply_propagate_attrs=self
- )
- for expr in exprs
- ]
- self.exprs = coerced_exprs
-
- self.c = self.columns = ColumnCollection(
- (getattr(col, "key", col._label), col)
- for col in [e._annotations.get("bundle", e) for e in coerced_exprs]
- ).as_readonly()
- self.single_entity = kw.pop("single_entity", self.single_entity)
-
- def _gen_cache_key(
- self, anon_map: anon_map, bindparams: List[BindParameter[Any]]
- ) -> Tuple[Any, ...]:
- return (self.__class__, self.name, self.single_entity) + tuple(
- [expr._gen_cache_key(anon_map, bindparams) for expr in self.exprs]
- )
-
- @property
- def mapper(self) -> Optional[Mapper[Any]]:
- mp: Optional[Mapper[Any]] = self.exprs[0]._annotations.get(
- "parentmapper", None
- )
- return mp
-
- @property
- def entity(self) -> Optional[_InternalEntityType[Any]]:
- ie: Optional[_InternalEntityType[Any]] = self.exprs[
- 0
- ]._annotations.get("parententity", None)
- return ie
-
- @property
- def entity_namespace(
- self,
- ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
- return self.c
-
- columns: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]
-
- """A namespace of SQL expressions referred to by this :class:`.Bundle`.
-
- e.g.::
-
- bn = Bundle("mybundle", MyClass.x, MyClass.y)
-
- q = sess.query(bn).filter(bn.c.x == 5)
-
- Nesting of bundles is also supported::
-
- b1 = Bundle("b1",
- Bundle('b2', MyClass.a, MyClass.b),
- Bundle('b3', MyClass.x, MyClass.y)
- )
-
- q = sess.query(b1).filter(
- b1.c.b2.c.a == 5).filter(b1.c.b3.c.y == 9)
-
- .. seealso::
-
- :attr:`.Bundle.c`
-
- """
-
- c: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]
- """An alias for :attr:`.Bundle.columns`."""
-
- def _clone(self):
- cloned = self.__class__.__new__(self.__class__)
- cloned.__dict__.update(self.__dict__)
- return cloned
-
- def __clause_element__(self):
- # ensure existing entity_namespace remains
- annotations = {"bundle": self, "entity_namespace": self}
- annotations.update(self._annotations)
-
- plugin_subject = self.exprs[0]._propagate_attrs.get(
- "plugin_subject", self.entity
- )
- return (
- expression.ClauseList(
- _literal_as_text_role=roles.ColumnsClauseRole,
- group=False,
- *[e._annotations.get("bundle", e) for e in self.exprs],
- )
- ._annotate(annotations)
- ._set_propagate_attrs(
- # the Bundle *must* use the orm plugin no matter what. the
- # subject can be None but it's much better if it's not.
- {
- "compile_state_plugin": "orm",
- "plugin_subject": plugin_subject,
- }
- )
- )
-
- @property
- def clauses(self):
- return self.__clause_element__().clauses
-
- def label(self, name):
- """Provide a copy of this :class:`.Bundle` passing a new label."""
-
- cloned = self._clone()
- cloned.name = name
- return cloned
-
- def create_row_processor(
- self,
- query: Select[Any],
- procs: Sequence[Callable[[Row[Any]], Any]],
- labels: Sequence[str],
- ) -> Callable[[Row[Any]], Any]:
- """Produce the "row processing" function for this :class:`.Bundle`.
-
- May be overridden by subclasses to provide custom behaviors when
- results are fetched. The method is passed the statement object and a
- set of "row processor" functions at query execution time; these
- processor functions when given a result row will return the individual
- attribute value, which can then be adapted into any kind of return data
- structure.
-
- The example below illustrates replacing the usual :class:`.Row`
- return structure with a straight Python dictionary::
-
- from sqlalchemy.orm import Bundle
-
- class DictBundle(Bundle):
- def create_row_processor(self, query, procs, labels):
- 'Override create_row_processor to return values as
- dictionaries'
-
- def proc(row):
- return dict(
- zip(labels, (proc(row) for proc in procs))
- )
- return proc
-
- A result from the above :class:`_orm.Bundle` will return dictionary
- values::
-
- bn = DictBundle('mybundle', MyClass.data1, MyClass.data2)
- for row in session.execute(select(bn)).where(bn.c.data1 == 'd1'):
- print(row.mybundle['data1'], row.mybundle['data2'])
-
- """
- keyed_tuple = result_tuple(labels, [() for l in labels])
-
- def proc(row: Row[Any]) -> Any:
- return keyed_tuple([proc(row) for proc in procs])
-
- return proc
-
-
-def _orm_annotate(element: _SA, exclude: Optional[Any] = None) -> _SA:
- """Deep copy the given ClauseElement, annotating each element with the
- "_orm_adapt" flag.
-
- Elements within the exclude collection will be cloned but not annotated.
-
- """
- return sql_util._deep_annotate(element, {"_orm_adapt": True}, exclude)
-
-
-def _orm_deannotate(element: _SA) -> _SA:
- """Remove annotations that link a column to a particular mapping.
-
- Note this doesn't affect "remote" and "foreign" annotations
- passed by the :func:`_orm.foreign` and :func:`_orm.remote`
- annotators.
-
- """
-
- return sql_util._deep_deannotate(
- element, values=("_orm_adapt", "parententity")
- )
-
-
-def _orm_full_deannotate(element: _SA) -> _SA:
- return sql_util._deep_deannotate(element)
-
-
-class _ORMJoin(expression.Join):
- """Extend Join to support ORM constructs as input."""
-
- __visit_name__ = expression.Join.__visit_name__
-
- inherit_cache = True
-
- def __init__(
- self,
- left: _FromClauseArgument,
- right: _FromClauseArgument,
- onclause: Optional[_OnClauseArgument] = None,
- isouter: bool = False,
- full: bool = False,
- _left_memo: Optional[Any] = None,
- _right_memo: Optional[Any] = None,
- _extra_criteria: Tuple[ColumnElement[bool], ...] = (),
- ):
- left_info = cast(
- "Union[FromClause, _InternalEntityType[Any]]",
- inspection.inspect(left),
- )
-
- right_info = cast(
- "Union[FromClause, _InternalEntityType[Any]]",
- inspection.inspect(right),
- )
- adapt_to = right_info.selectable
-
- # used by joined eager loader
- self._left_memo = _left_memo
- self._right_memo = _right_memo
-
- if isinstance(onclause, attributes.QueryableAttribute):
- if TYPE_CHECKING:
- assert isinstance(
- onclause.comparator, RelationshipProperty.Comparator
- )
- on_selectable = onclause.comparator._source_selectable()
- prop = onclause.property
- _extra_criteria += onclause._extra_criteria
- elif isinstance(onclause, MapperProperty):
- # used internally by joined eager loader...possibly not ideal
- prop = onclause
- on_selectable = prop.parent.selectable
- else:
- prop = None
- on_selectable = None
-
- left_selectable = left_info.selectable
- if prop:
- adapt_from: Optional[FromClause]
- if sql_util.clause_is_present(on_selectable, left_selectable):
- adapt_from = on_selectable
- else:
- assert isinstance(left_selectable, FromClause)
- adapt_from = left_selectable
-
- (
- pj,
- sj,
- source,
- dest,
- secondary,
- target_adapter,
- ) = prop._create_joins(
- source_selectable=adapt_from,
- dest_selectable=adapt_to,
- source_polymorphic=True,
- of_type_entity=right_info,
- alias_secondary=True,
- extra_criteria=_extra_criteria,
- )
-
- if sj is not None:
- if isouter:
- # note this is an inner join from secondary->right
- right = sql.join(secondary, right, sj)
- onclause = pj
- else:
- left = sql.join(left, secondary, pj, isouter)
- onclause = sj
- else:
- onclause = pj
-
- self._target_adapter = target_adapter
-
- # we don't use the normal coercions logic for _ORMJoin
- # (probably should), so do some gymnastics to get the entity.
- # logic here is for #8721, which was a major bug in 1.4
- # for almost two years, not reported/fixed until 1.4.43 (!)
- if is_selectable(left_info):
- parententity = left_selectable._annotations.get(
- "parententity", None
- )
- elif insp_is_mapper(left_info) or insp_is_aliased_class(left_info):
- parententity = left_info
- else:
- parententity = None
-
- if parententity is not None:
- self._annotations = self._annotations.union(
- {"parententity": parententity}
- )
-
- augment_onclause = bool(_extra_criteria) and not prop
- expression.Join.__init__(self, left, right, onclause, isouter, full)
-
- assert self.onclause is not None
-
- if augment_onclause:
- self.onclause &= sql.and_(*_extra_criteria)
-
- if (
- not prop
- and getattr(right_info, "mapper", None)
- and right_info.mapper.single # type: ignore
- ):
- right_info = cast("_InternalEntityType[Any]", right_info)
- # if single inheritance target and we are using a manual
- # or implicit ON clause, augment it the same way we'd augment the
- # WHERE.
- single_crit = right_info.mapper._single_table_criterion
- if single_crit is not None:
- if insp_is_aliased_class(right_info):
- single_crit = right_info._adapter.traverse(single_crit)
- self.onclause = self.onclause & single_crit
-
- def _splice_into_center(self, other):
- """Splice a join into the center.
-
- Given join(a, b) and join(b, c), return join(a, b).join(c)
-
- """
- leftmost = other
- while isinstance(leftmost, sql.Join):
- leftmost = leftmost.left
-
- assert self.right is leftmost
-
- left = _ORMJoin(
- self.left,
- other.left,
- self.onclause,
- isouter=self.isouter,
- _left_memo=self._left_memo,
- _right_memo=other._left_memo,
- )
-
- return _ORMJoin(
- left,
- other.right,
- other.onclause,
- isouter=other.isouter,
- _right_memo=other._right_memo,
- )
-
- def join(
- self,
- right: _FromClauseArgument,
- onclause: Optional[_OnClauseArgument] = None,
- isouter: bool = False,
- full: bool = False,
- ) -> _ORMJoin:
- return _ORMJoin(self, right, onclause, full=full, isouter=isouter)
-
- def outerjoin(
- self,
- right: _FromClauseArgument,
- onclause: Optional[_OnClauseArgument] = None,
- full: bool = False,
- ) -> _ORMJoin:
- return _ORMJoin(self, right, onclause, isouter=True, full=full)
-
-
-def with_parent(
- instance: object,
- prop: attributes.QueryableAttribute[Any],
- from_entity: Optional[_EntityType[Any]] = None,
-) -> ColumnElement[bool]:
- """Create filtering criterion that relates this query's primary entity
- to the given related instance, using established
- :func:`_orm.relationship()`
- configuration.
-
- E.g.::
-
- stmt = select(Address).where(with_parent(some_user, User.addresses))
-
-
- The SQL rendered is the same as that rendered when a lazy loader
- would fire off from the given parent on that attribute, meaning
- that the appropriate state is taken from the parent object in
- Python without the need to render joins to the parent table
- in the rendered statement.
-
- The given property may also make use of :meth:`_orm.PropComparator.of_type`
- to indicate the left side of the criteria::
-
-
- a1 = aliased(Address)
- a2 = aliased(Address)
- stmt = select(a1, a2).where(
- with_parent(u1, User.addresses.of_type(a2))
- )
-
- The above use is equivalent to using the
- :func:`_orm.with_parent.from_entity` argument::
-
- a1 = aliased(Address)
- a2 = aliased(Address)
- stmt = select(a1, a2).where(
- with_parent(u1, User.addresses, from_entity=a2)
- )
-
- :param instance:
- An instance which has some :func:`_orm.relationship`.
-
- :param property:
- Class-bound attribute, which indicates
- what relationship from the instance should be used to reconcile the
- parent/child relationship.
-
- :param from_entity:
- Entity in which to consider as the left side. This defaults to the
- "zero" entity of the :class:`_query.Query` itself.
-
- .. versionadded:: 1.2
-
- """
- prop_t: RelationshipProperty[Any]
-
- if isinstance(prop, str):
- raise sa_exc.ArgumentError(
- "with_parent() accepts class-bound mapped attributes, not strings"
- )
- elif isinstance(prop, attributes.QueryableAttribute):
- if prop._of_type:
- from_entity = prop._of_type
- mapper_property = prop.property
- if mapper_property is None or not prop_is_relationship(
- mapper_property
- ):
- raise sa_exc.ArgumentError(
- f"Expected relationship property for with_parent(), "
- f"got {mapper_property}"
- )
- prop_t = mapper_property
- else:
- prop_t = prop
-
- return prop_t._with_parent(instance, from_entity=from_entity)
-
-
-def has_identity(object_: object) -> bool:
- """Return True if the given object has a database
- identity.
-
- This typically corresponds to the object being
- in either the persistent or detached state.
-
- .. seealso::
-
- :func:`.was_deleted`
-
- """
- state = attributes.instance_state(object_)
- return state.has_identity
-
-
-def was_deleted(object_: object) -> bool:
- """Return True if the given object was deleted
- within a session flush.
-
- This is regardless of whether or not the object is
- persistent or detached.
-
- .. seealso::
-
- :attr:`.InstanceState.was_deleted`
-
- """
-
- state = attributes.instance_state(object_)
- return state.was_deleted
-
-
-def _entity_corresponds_to(
- given: _InternalEntityType[Any], entity: _InternalEntityType[Any]
-) -> bool:
- """determine if 'given' corresponds to 'entity', in terms
- of an entity passed to Query that would match the same entity
- being referred to elsewhere in the query.
-
- """
- if insp_is_aliased_class(entity):
- if insp_is_aliased_class(given):
- if entity._base_alias() is given._base_alias():
- return True
- return False
- elif insp_is_aliased_class(given):
- if given._use_mapper_path:
- return entity in given.with_polymorphic_mappers
- else:
- return entity is given
-
- assert insp_is_mapper(given)
- return entity.common_parent(given)
-
-
-def _entity_corresponds_to_use_path_impl(
- given: _InternalEntityType[Any], entity: _InternalEntityType[Any]
-) -> bool:
- """determine if 'given' corresponds to 'entity', in terms
- of a path of loader options where a mapped attribute is taken to
- be a member of a parent entity.
-
- e.g.::
-
- someoption(A).someoption(A.b) # -> fn(A, A) -> True
- someoption(A).someoption(C.d) # -> fn(A, C) -> False
-
- a1 = aliased(A)
- someoption(a1).someoption(A.b) # -> fn(a1, A) -> False
- someoption(a1).someoption(a1.b) # -> fn(a1, a1) -> True
-
- wp = with_polymorphic(A, [A1, A2])
- someoption(wp).someoption(A1.foo) # -> fn(wp, A1) -> False
- someoption(wp).someoption(wp.A1.foo) # -> fn(wp, wp.A1) -> True
-
-
- """
- if insp_is_aliased_class(given):
- return (
- insp_is_aliased_class(entity)
- and not entity._use_mapper_path
- and (given is entity or entity in given._with_polymorphic_entities)
- )
- elif not insp_is_aliased_class(entity):
- return given.isa(entity.mapper)
- else:
- return (
- entity._use_mapper_path
- and given in entity.with_polymorphic_mappers
- )
-
-
-def _entity_isa(given: _InternalEntityType[Any], mapper: Mapper[Any]) -> bool:
- """determine if 'given' "is a" mapper, in terms of the given
- would load rows of type 'mapper'.
-
- """
- if given.is_aliased_class:
- return mapper in given.with_polymorphic_mappers or given.mapper.isa(
- mapper
- )
- elif given.with_polymorphic_mappers:
- return mapper in given.with_polymorphic_mappers
- else:
- return given.isa(mapper)
-
-
-def _getitem(iterable_query: Query[Any], item: Any) -> Any:
- """calculate __getitem__ in terms of an iterable query object
- that also has a slice() method.
-
- """
-
- def _no_negative_indexes():
- raise IndexError(
- "negative indexes are not accepted by SQL "
- "index / slice operators"
- )
-
- if isinstance(item, slice):
- start, stop, step = util.decode_slice(item)
-
- if (
- isinstance(stop, int)
- and isinstance(start, int)
- and stop - start <= 0
- ):
- return []
-
- elif (isinstance(start, int) and start < 0) or (
- isinstance(stop, int) and stop < 0
- ):
- _no_negative_indexes()
-
- res = iterable_query.slice(start, stop)
- if step is not None:
- return list(res)[None : None : item.step]
- else:
- return list(res)
- else:
- if item == -1:
- _no_negative_indexes()
- else:
- return list(iterable_query[item : item + 1])[0]
-
-
-def _is_mapped_annotation(
- raw_annotation: _AnnotationScanType,
- cls: Type[Any],
- originating_cls: Type[Any],
-) -> bool:
- try:
- annotated = de_stringify_annotation(
- cls, raw_annotation, originating_cls.__module__
- )
- except NameError:
- # in most cases, at least within our own tests, we can raise
- # here, which is more accurate as it prevents us from returning
- # false negatives. However, in the real world, try to avoid getting
- # involved with end-user annotations that have nothing to do with us.
- # see issue #8888 where we bypass using this function in the case
- # that we want to detect an unresolvable Mapped[] type.
- return False
- else:
- return is_origin_of_cls(annotated, _MappedAnnotationBase)
-
-
-class _CleanupError(Exception):
- pass
-
-
-def _cleanup_mapped_str_annotation(
- annotation: str, originating_module: str
-) -> str:
- # fix up an annotation that comes in as the form:
- # 'Mapped[List[Address]]' so that it instead looks like:
- # 'Mapped[List["Address"]]' , which will allow us to get
- # "Address" as a string
-
- # additionally, resolve symbols for these names since this is where
- # we'd have to do it
-
- inner: Optional[Match[str]]
-
- mm = re.match(r"^(.+?)\[(.+)\]$", annotation)
-
- if not mm:
- return annotation
-
- # ticket #8759. Resolve the Mapped name to a real symbol.
- # originally this just checked the name.
- try:
- obj = eval_name_only(mm.group(1), originating_module)
- except NameError as ne:
- raise _CleanupError(
- f'For annotation "{annotation}", could not resolve '
- f'container type "{mm.group(1)}". '
- "Please ensure this type is imported at the module level "
- "outside of TYPE_CHECKING blocks"
- ) from ne
-
- if obj is typing.ClassVar:
- real_symbol = "ClassVar"
- else:
- try:
- if issubclass(obj, _MappedAnnotationBase):
- real_symbol = obj.__name__
- else:
- return annotation
- except TypeError:
- # avoid isinstance(obj, type) check, just catch TypeError
- return annotation
-
- # note: if one of the codepaths above didn't define real_symbol and
- # then didn't return, real_symbol raises UnboundLocalError
- # which is actually a NameError, and the calling routines don't
- # notice this since they are catching NameError anyway. Just in case
- # this is being modified in the future, something to be aware of.
-
- stack = []
- inner = mm
- while True:
- stack.append(real_symbol if mm is inner else inner.group(1))
- g2 = inner.group(2)
- inner = re.match(r"^(.+?)\[(.+)\]$", g2)
- if inner is None:
- stack.append(g2)
- break
-
- # stacks we want to rewrite, that is, quote the last entry which
- # we think is a relationship class name:
- #
- # ['Mapped', 'List', 'Address']
- # ['Mapped', 'A']
- #
- # stacks we dont want to rewrite, which are generally MappedColumn
- # use cases:
- #
- # ['Mapped', "'Optional[Dict[str, str]]'"]
- # ['Mapped', 'dict[str, str] | None']
-
- if (
- # avoid already quoted symbols such as
- # ['Mapped', "'Optional[Dict[str, str]]'"]
- not re.match(r"""^["'].*["']$""", stack[-1])
- # avoid further generics like Dict[] such as
- # ['Mapped', 'dict[str, str] | None']
- and not re.match(r".*\[.*\]", stack[-1])
- ):
- stripchars = "\"' "
- stack[-1] = ", ".join(
- f'"{elem.strip(stripchars)}"' for elem in stack[-1].split(",")
- )
-
- annotation = "[".join(stack) + ("]" * (len(stack) - 1))
-
- return annotation
-
-
-def _extract_mapped_subtype(
- raw_annotation: Optional[_AnnotationScanType],
- cls: type,
- originating_module: str,
- key: str,
- attr_cls: Type[Any],
- required: bool,
- is_dataclass_field: bool,
- expect_mapped: bool = True,
- raiseerr: bool = True,
-) -> Optional[Tuple[Union[type, str], Optional[type]]]:
- """given an annotation, figure out if it's ``Mapped[something]`` and if
- so, return the ``something`` part.
-
- Includes error raise scenarios and other options.
-
- """
-
- if raw_annotation is None:
- if required:
- raise sa_exc.ArgumentError(
- f"Python typing annotation is required for attribute "
- f'"{cls.__name__}.{key}" when primary argument(s) for '
- f'"{attr_cls.__name__}" construct are None or not present'
- )
- return None
-
- try:
- annotated = de_stringify_annotation(
- cls,
- raw_annotation,
- originating_module,
- str_cleanup_fn=_cleanup_mapped_str_annotation,
- )
- except _CleanupError as ce:
- raise sa_exc.ArgumentError(
- f"Could not interpret annotation {raw_annotation}. "
- "Check that it uses names that are correctly imported at the "
- "module level. See chained stack trace for more hints."
- ) from ce
- except NameError as ne:
- if raiseerr and "Mapped[" in raw_annotation: # type: ignore
- raise sa_exc.ArgumentError(
- f"Could not interpret annotation {raw_annotation}. "
- "Check that it uses names that are correctly imported at the "
- "module level. See chained stack trace for more hints."
- ) from ne
-
- annotated = raw_annotation # type: ignore
-
- if is_dataclass_field:
- return annotated, None
- else:
- if not hasattr(annotated, "__origin__") or not is_origin_of_cls(
- annotated, _MappedAnnotationBase
- ):
- if expect_mapped:
- if not raiseerr:
- return None
-
- origin = getattr(annotated, "__origin__", None)
- if origin is typing.ClassVar:
- return None
-
- # check for other kind of ORM descriptor like AssociationProxy,
- # don't raise for that (issue #9957)
- elif isinstance(origin, type) and issubclass(
- origin, ORMDescriptor
- ):
- return None
-
- raise sa_exc.ArgumentError(
- f'Type annotation for "{cls.__name__}.{key}" '
- "can't be correctly interpreted for "
- "Annotated Declarative Table form. ORM annotations "
- "should normally make use of the ``Mapped[]`` generic "
- "type, or other ORM-compatible generic type, as a "
- "container for the actual type, which indicates the "
- "intent that the attribute is mapped. "
- "Class variables that are not intended to be mapped "
- "by the ORM should use ClassVar[]. "
- "To allow Annotated Declarative to disregard legacy "
- "annotations which don't use Mapped[] to pass, set "
- '"__allow_unmapped__ = True" on the class or a '
- "superclass this class.",
- code="zlpr",
- )
-
- else:
- return annotated, None
-
- if len(annotated.__args__) != 1:
- raise sa_exc.ArgumentError(
- "Expected sub-type for Mapped[] annotation"
- )
-
- return annotated.__args__[0], annotated.__origin__
-
-
-def _mapper_property_as_plain_name(prop: Type[Any]) -> str:
- if hasattr(prop, "_mapper_property_name"):
- name = prop._mapper_property_name()
- else:
- name = None
- return util.clsname_as_plain_name(prop, name)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/writeonly.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/writeonly.py
deleted file mode 100644
index 5680cc7..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/writeonly.py
+++ /dev/null
@@ -1,678 +0,0 @@
-# orm/writeonly.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
-
-"""Write-only collection API.
-
-This is an alternate mapped attribute style that only supports single-item
-collection mutation operations. To read the collection, a select()
-object must be executed each time.
-
-.. versionadded:: 2.0
-
-
-"""
-
-from __future__ import annotations
-
-from typing import Any
-from typing import Collection
-from typing import Dict
-from typing import Generic
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import NoReturn
-from typing import Optional
-from typing import overload
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from sqlalchemy.sql import bindparam
-from . import attributes
-from . import interfaces
-from . import relationships
-from . import strategies
-from .base import NEVER_SET
-from .base import object_mapper
-from .base import PassiveFlag
-from .base import RelationshipDirection
-from .. import exc
-from .. import inspect
-from .. import log
-from .. import util
-from ..sql import delete
-from ..sql import insert
-from ..sql import select
-from ..sql import update
-from ..sql.dml import Delete
-from ..sql.dml import Insert
-from ..sql.dml import Update
-from ..util.typing import Literal
-
-if TYPE_CHECKING:
- from . import QueryableAttribute
- from ._typing import _InstanceDict
- from .attributes import AttributeEventToken
- from .base import LoaderCallableStatus
- from .collections import _AdaptedCollectionProtocol
- from .collections import CollectionAdapter
- from .mapper import Mapper
- from .relationships import _RelationshipOrderByArg
- from .state import InstanceState
- from .util import AliasedClass
- from ..event import _Dispatch
- from ..sql.selectable import FromClause
- from ..sql.selectable import Select
-
-_T = TypeVar("_T", bound=Any)
-
-
-class WriteOnlyHistory(Generic[_T]):
- """Overrides AttributeHistory to receive append/remove events directly."""
-
- unchanged_items: util.OrderedIdentitySet
- added_items: util.OrderedIdentitySet
- deleted_items: util.OrderedIdentitySet
- _reconcile_collection: bool
-
- def __init__(
- self,
- attr: WriteOnlyAttributeImpl,
- state: InstanceState[_T],
- passive: PassiveFlag,
- apply_to: Optional[WriteOnlyHistory[_T]] = None,
- ) -> None:
- if apply_to:
- if passive & PassiveFlag.SQL_OK:
- raise exc.InvalidRequestError(
- f"Attribute {attr} can't load the existing state from the "
- "database for this operation; full iteration is not "
- "permitted. If this is a delete operation, configure "
- f"passive_deletes=True on the {attr} relationship in "
- "order to resolve this error."
- )
-
- self.unchanged_items = apply_to.unchanged_items
- self.added_items = apply_to.added_items
- self.deleted_items = apply_to.deleted_items
- self._reconcile_collection = apply_to._reconcile_collection
- else:
- self.deleted_items = util.OrderedIdentitySet()
- self.added_items = util.OrderedIdentitySet()
- self.unchanged_items = util.OrderedIdentitySet()
- self._reconcile_collection = False
-
- @property
- def added_plus_unchanged(self) -> List[_T]:
- return list(self.added_items.union(self.unchanged_items))
-
- @property
- def all_items(self) -> List[_T]:
- return list(
- self.added_items.union(self.unchanged_items).union(
- self.deleted_items
- )
- )
-
- def as_history(self) -> attributes.History:
- if self._reconcile_collection:
- added = self.added_items.difference(self.unchanged_items)
- deleted = self.deleted_items.intersection(self.unchanged_items)
- unchanged = self.unchanged_items.difference(deleted)
- else:
- added, unchanged, deleted = (
- self.added_items,
- self.unchanged_items,
- self.deleted_items,
- )
- return attributes.History(list(added), list(unchanged), list(deleted))
-
- def indexed(self, index: Union[int, slice]) -> Union[List[_T], _T]:
- return list(self.added_items)[index]
-
- def add_added(self, value: _T) -> None:
- self.added_items.add(value)
-
- def add_removed(self, value: _T) -> None:
- if value in self.added_items:
- self.added_items.remove(value)
- else:
- self.deleted_items.add(value)
-
-
-class WriteOnlyAttributeImpl(
- attributes.HasCollectionAdapter, attributes.AttributeImpl
-):
- uses_objects: bool = True
- default_accepts_scalar_loader: bool = False
- supports_population: bool = False
- _supports_dynamic_iteration: bool = False
- collection: bool = False
- dynamic: bool = True
- order_by: _RelationshipOrderByArg = ()
- collection_history_cls: Type[WriteOnlyHistory[Any]] = WriteOnlyHistory
-
- query_class: Type[WriteOnlyCollection[Any]]
-
- def __init__(
- self,
- class_: Union[Type[Any], AliasedClass[Any]],
- key: str,
- dispatch: _Dispatch[QueryableAttribute[Any]],
- target_mapper: Mapper[_T],
- order_by: _RelationshipOrderByArg,
- **kw: Any,
- ):
- super().__init__(class_, key, None, dispatch, **kw)
- self.target_mapper = target_mapper
- self.query_class = WriteOnlyCollection
- if order_by:
- self.order_by = tuple(order_by)
-
- def get(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- ) -> Union[util.OrderedIdentitySet, WriteOnlyCollection[Any]]:
- if not passive & PassiveFlag.SQL_OK:
- return self._get_collection_history(
- state, PassiveFlag.PASSIVE_NO_INITIALIZE
- ).added_items
- else:
- return self.query_class(self, state)
-
- @overload
- def get_collection(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- user_data: Literal[None] = ...,
- passive: Literal[PassiveFlag.PASSIVE_OFF] = ...,
- ) -> CollectionAdapter: ...
-
- @overload
- def get_collection(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- user_data: _AdaptedCollectionProtocol = ...,
- passive: PassiveFlag = ...,
- ) -> CollectionAdapter: ...
-
- @overload
- def get_collection(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- user_data: Optional[_AdaptedCollectionProtocol] = ...,
- passive: PassiveFlag = ...,
- ) -> Union[
- Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
- ]: ...
-
- def get_collection(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- user_data: Optional[_AdaptedCollectionProtocol] = None,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- ) -> Union[
- Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
- ]:
- data: Collection[Any]
- if not passive & PassiveFlag.SQL_OK:
- data = self._get_collection_history(state, passive).added_items
- else:
- history = self._get_collection_history(state, passive)
- data = history.added_plus_unchanged
- return DynamicCollectionAdapter(data) # type: ignore[return-value]
-
- @util.memoized_property
- def _append_token( # type:ignore[override]
- self,
- ) -> attributes.AttributeEventToken:
- return attributes.AttributeEventToken(self, attributes.OP_APPEND)
-
- @util.memoized_property
- def _remove_token( # type:ignore[override]
- self,
- ) -> attributes.AttributeEventToken:
- return attributes.AttributeEventToken(self, attributes.OP_REMOVE)
-
- def fire_append_event(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken],
- collection_history: Optional[WriteOnlyHistory[Any]] = None,
- ) -> None:
- if collection_history is None:
- collection_history = self._modified_event(state, dict_)
-
- collection_history.add_added(value)
-
- for fn in self.dispatch.append:
- value = fn(state, value, initiator or self._append_token)
-
- if self.trackparent and value is not None:
- self.sethasparent(attributes.instance_state(value), state, True)
-
- def fire_remove_event(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken],
- collection_history: Optional[WriteOnlyHistory[Any]] = None,
- ) -> None:
- if collection_history is None:
- collection_history = self._modified_event(state, dict_)
-
- collection_history.add_removed(value)
-
- if self.trackparent and value is not None:
- self.sethasparent(attributes.instance_state(value), state, False)
-
- for fn in self.dispatch.remove:
- fn(state, value, initiator or self._remove_token)
-
- def _modified_event(
- self, state: InstanceState[Any], dict_: _InstanceDict
- ) -> WriteOnlyHistory[Any]:
- if self.key not in state.committed_state:
- state.committed_state[self.key] = self.collection_history_cls(
- self, state, PassiveFlag.PASSIVE_NO_FETCH
- )
-
- state._modified_event(dict_, self, NEVER_SET)
-
- # this is a hack to allow the entities.ComparableEntity fixture
- # to work
- dict_[self.key] = True
- return state.committed_state[self.key] # type: ignore[no-any-return]
-
- def set(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken] = None,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- check_old: Any = None,
- pop: bool = False,
- _adapt: bool = True,
- ) -> None:
- if initiator and initiator.parent_token is self.parent_token:
- return
-
- if pop and value is None:
- return
-
- iterable = value
- new_values = list(iterable)
- if state.has_identity:
- if not self._supports_dynamic_iteration:
- raise exc.InvalidRequestError(
- f'Collection "{self}" does not support implicit '
- "iteration; collection replacement operations "
- "can't be used"
- )
- old_collection = util.IdentitySet(
- self.get(state, dict_, passive=passive)
- )
-
- collection_history = self._modified_event(state, dict_)
- if not state.has_identity:
- old_collection = collection_history.added_items
- else:
- old_collection = old_collection.union(
- collection_history.added_items
- )
-
- constants = old_collection.intersection(new_values)
- additions = util.IdentitySet(new_values).difference(constants)
- removals = old_collection.difference(constants)
-
- for member in new_values:
- if member in additions:
- self.fire_append_event(
- state,
- dict_,
- member,
- None,
- collection_history=collection_history,
- )
-
- for member in removals:
- self.fire_remove_event(
- state,
- dict_,
- member,
- None,
- collection_history=collection_history,
- )
-
- def delete(self, *args: Any, **kwargs: Any) -> NoReturn:
- raise NotImplementedError()
-
- def set_committed_value(
- self, state: InstanceState[Any], dict_: _InstanceDict, value: Any
- ) -> NoReturn:
- raise NotImplementedError(
- "Dynamic attributes don't support collection population."
- )
-
- def get_history(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
- ) -> attributes.History:
- c = self._get_collection_history(state, passive)
- return c.as_history()
-
- def get_all_pending(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PassiveFlag.PASSIVE_NO_INITIALIZE,
- ) -> List[Tuple[InstanceState[Any], Any]]:
- c = self._get_collection_history(state, passive)
- return [(attributes.instance_state(x), x) for x in c.all_items]
-
- def _get_collection_history(
- self, state: InstanceState[Any], passive: PassiveFlag
- ) -> WriteOnlyHistory[Any]:
- c: WriteOnlyHistory[Any]
- if self.key in state.committed_state:
- c = state.committed_state[self.key]
- else:
- c = self.collection_history_cls(
- self, state, PassiveFlag.PASSIVE_NO_FETCH
- )
-
- if state.has_identity and (passive & PassiveFlag.INIT_OK):
- return self.collection_history_cls(
- self, state, passive, apply_to=c
- )
- else:
- return c
-
- def append(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken],
- passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
- ) -> None:
- if initiator is not self:
- self.fire_append_event(state, dict_, value, initiator)
-
- def remove(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken],
- passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
- ) -> None:
- if initiator is not self:
- self.fire_remove_event(state, dict_, value, initiator)
-
- def pop(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- value: Any,
- initiator: Optional[AttributeEventToken],
- passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
- ) -> None:
- self.remove(state, dict_, value, initiator, passive=passive)
-
-
-@log.class_logger
-@relationships.RelationshipProperty.strategy_for(lazy="write_only")
-class WriteOnlyLoader(strategies.AbstractRelationshipLoader, log.Identified):
- impl_class = WriteOnlyAttributeImpl
-
- def init_class_attribute(self, mapper: Mapper[Any]) -> None:
- self.is_class_level = True
- if not self.uselist or self.parent_property.direction not in (
- interfaces.ONETOMANY,
- interfaces.MANYTOMANY,
- ):
- raise exc.InvalidRequestError(
- "On relationship %s, 'dynamic' loaders cannot be used with "
- "many-to-one/one-to-one relationships and/or "
- "uselist=False." % self.parent_property
- )
-
- strategies._register_attribute( # type: ignore[no-untyped-call]
- self.parent_property,
- mapper,
- useobject=True,
- impl_class=self.impl_class,
- target_mapper=self.parent_property.mapper,
- order_by=self.parent_property.order_by,
- query_class=self.parent_property.query_class,
- )
-
-
-class DynamicCollectionAdapter:
- """simplified CollectionAdapter for internal API consistency"""
-
- data: Collection[Any]
-
- def __init__(self, data: Collection[Any]):
- self.data = data
-
- def __iter__(self) -> Iterator[Any]:
- return iter(self.data)
-
- def _reset_empty(self) -> None:
- pass
-
- def __len__(self) -> int:
- return len(self.data)
-
- def __bool__(self) -> bool:
- return True
-
-
-class AbstractCollectionWriter(Generic[_T]):
- """Virtual collection which includes append/remove methods that synchronize
- into the attribute event system.
-
- """
-
- if not TYPE_CHECKING:
- __slots__ = ()
-
- instance: _T
- _from_obj: Tuple[FromClause, ...]
-
- def __init__(self, attr: WriteOnlyAttributeImpl, state: InstanceState[_T]):
- instance = state.obj()
- if TYPE_CHECKING:
- assert instance
- self.instance = instance
- self.attr = attr
-
- mapper = object_mapper(instance)
- prop = mapper._props[self.attr.key]
-
- if prop.secondary is not None:
- # this is a hack right now. The Query only knows how to
- # make subsequent joins() without a given left-hand side
- # from self._from_obj[0]. We need to ensure prop.secondary
- # is in the FROM. So we purposely put the mapper selectable
- # in _from_obj[0] to ensure a user-defined join() later on
- # doesn't fail, and secondary is then in _from_obj[1].
-
- # note also, we are using the official ORM-annotated selectable
- # from __clause_element__(), see #7868
- self._from_obj = (prop.mapper.__clause_element__(), prop.secondary)
- else:
- self._from_obj = ()
-
- self._where_criteria = (
- prop._with_parent(instance, alias_secondary=False),
- )
-
- if self.attr.order_by:
- self._order_by_clauses = self.attr.order_by
- else:
- self._order_by_clauses = ()
-
- def _add_all_impl(self, iterator: Iterable[_T]) -> None:
- for item in iterator:
- self.attr.append(
- attributes.instance_state(self.instance),
- attributes.instance_dict(self.instance),
- item,
- None,
- )
-
- def _remove_impl(self, item: _T) -> None:
- self.attr.remove(
- attributes.instance_state(self.instance),
- attributes.instance_dict(self.instance),
- item,
- None,
- )
-
-
-class WriteOnlyCollection(AbstractCollectionWriter[_T]):
- """Write-only collection which can synchronize changes into the
- attribute event system.
-
- The :class:`.WriteOnlyCollection` is used in a mapping by
- using the ``"write_only"`` lazy loading strategy with
- :func:`_orm.relationship`. For background on this configuration,
- see :ref:`write_only_relationship`.
-
- .. versionadded:: 2.0
-
- .. seealso::
-
- :ref:`write_only_relationship`
-
- """
-
- __slots__ = (
- "instance",
- "attr",
- "_where_criteria",
- "_from_obj",
- "_order_by_clauses",
- )
-
- def __iter__(self) -> NoReturn:
- raise TypeError(
- "WriteOnly collections don't support iteration in-place; "
- "to query for collection items, use the select() method to "
- "produce a SQL statement and execute it with session.scalars()."
- )
-
- def select(self) -> Select[Tuple[_T]]:
- """Produce a :class:`_sql.Select` construct that represents the
- rows within this instance-local :class:`_orm.WriteOnlyCollection`.
-
- """
- stmt = select(self.attr.target_mapper).where(*self._where_criteria)
- if self._from_obj:
- stmt = stmt.select_from(*self._from_obj)
- if self._order_by_clauses:
- stmt = stmt.order_by(*self._order_by_clauses)
- return stmt
-
- def insert(self) -> Insert:
- """For one-to-many collections, produce a :class:`_dml.Insert` which
- will insert new rows in terms of this this instance-local
- :class:`_orm.WriteOnlyCollection`.
-
- This construct is only supported for a :class:`_orm.Relationship`
- that does **not** include the :paramref:`_orm.relationship.secondary`
- parameter. For relationships that refer to a many-to-many table,
- use ordinary bulk insert techniques to produce new objects, then
- use :meth:`_orm.AbstractCollectionWriter.add_all` to associate them
- with the collection.
-
-
- """
-
- state = inspect(self.instance)
- mapper = state.mapper
- prop = mapper._props[self.attr.key]
-
- if prop.direction is not RelationshipDirection.ONETOMANY:
- raise exc.InvalidRequestError(
- "Write only bulk INSERT only supported for one-to-many "
- "collections; for many-to-many, use a separate bulk "
- "INSERT along with add_all()."
- )
-
- dict_: Dict[str, Any] = {}
-
- for l, r in prop.synchronize_pairs:
- fn = prop._get_attr_w_warn_on_none(
- mapper,
- state,
- state.dict,
- l,
- )
-
- dict_[r.key] = bindparam(None, callable_=fn)
-
- return insert(self.attr.target_mapper).values(**dict_)
-
- def update(self) -> Update:
- """Produce a :class:`_dml.Update` which will refer to rows in terms
- of this instance-local :class:`_orm.WriteOnlyCollection`.
-
- """
- return update(self.attr.target_mapper).where(*self._where_criteria)
-
- def delete(self) -> Delete:
- """Produce a :class:`_dml.Delete` which will refer to rows in terms
- of this instance-local :class:`_orm.WriteOnlyCollection`.
-
- """
- return delete(self.attr.target_mapper).where(*self._where_criteria)
-
- def add_all(self, iterator: Iterable[_T]) -> None:
- """Add an iterable of items to this :class:`_orm.WriteOnlyCollection`.
-
- The given items will be persisted to the database in terms of
- the parent instance's collection on the next flush.
-
- """
- self._add_all_impl(iterator)
-
- def add(self, item: _T) -> None:
- """Add an item to this :class:`_orm.WriteOnlyCollection`.
-
- The given item will be persisted to the database in terms of
- the parent instance's collection on the next flush.
-
- """
- self._add_all_impl([item])
-
- def remove(self, item: _T) -> None:
- """Remove an item from this :class:`_orm.WriteOnlyCollection`.
-
- The given item will be removed from the parent instance's collection on
- the next flush.
-
- """
- self._remove_impl(item)