SQLAlchemy 2.1.0rc1__cp315-cp315-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. sqlalchemy/__init__.py +299 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +171 -0
  4. sqlalchemy/connectors/asyncio.py +476 -0
  5. sqlalchemy/connectors/pyodbc.py +248 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +29 -0
  8. sqlalchemy/dialects/mssql/__init__.py +88 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4833 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +345 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +242 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +130 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +697 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +260 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +241 -0
  21. sqlalchemy/dialects/mysql/base.py +3896 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +107 -0
  23. sqlalchemy/dialects/mysql/dml.py +279 -0
  24. sqlalchemy/dialects/mysql/enumerated.py +277 -0
  25. sqlalchemy/dialects/mysql/expression.py +146 -0
  26. sqlalchemy/dialects/mysql/json.py +92 -0
  27. sqlalchemy/dialects/mysql/mariadb.py +67 -0
  28. sqlalchemy/dialects/mysql/mariadbconnector.py +314 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +291 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +318 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +188 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  34. sqlalchemy/dialects/mysql/reflection.py +724 -0
  35. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  36. sqlalchemy/dialects/mysql/types.py +845 -0
  37. sqlalchemy/dialects/oracle/__init__.py +85 -0
  38. sqlalchemy/dialects/oracle/base.py +3847 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1736 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +157 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +898 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +366 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +170 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +232 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1318 -0
  50. sqlalchemy/dialects/postgresql/base.py +5935 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +599 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +422 -0
  55. sqlalchemy/dialects/postgresql/json.py +411 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +535 -0
  57. sqlalchemy/dialects/postgresql/operators.py +129 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +655 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +345 -0
  60. sqlalchemy/dialects/postgresql/provision.py +202 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +800 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +62 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +59 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +375 -0
  68. sqlalchemy/dialects/sqlite/base.py +3103 -0
  69. sqlalchemy/dialects/sqlite/dml.py +314 -0
  70. sqlalchemy/dialects/sqlite/json.py +134 -0
  71. sqlalchemy/dialects/sqlite/provision.py +237 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +166 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +959 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp315-win_amd64.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp315-win_amd64.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +711 -0
  80. sqlalchemy/engine/_row_cy.cp315-win_amd64.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp315-win_amd64.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3357 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2425 -0
  88. sqlalchemy/engine/default.py +2627 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3636 -0
  91. sqlalchemy/engine/mock.py +133 -0
  92. sqlalchemy/engine/processors.py +83 -0
  93. sqlalchemy/engine/reflection.py +2141 -0
  94. sqlalchemy/engine/result.py +2012 -0
  95. sqlalchemy/engine/row.py +397 -0
  96. sqlalchemy/engine/strategies.py +16 -0
  97. sqlalchemy/engine/url.py +922 -0
  98. sqlalchemy/engine/util.py +164 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +675 -0
  102. sqlalchemy/event/base.py +473 -0
  103. sqlalchemy/event/legacy.py +259 -0
  104. sqlalchemy/event/registry.py +391 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +939 -0
  107. sqlalchemy/ext/__init__.py +10 -0
  108. sqlalchemy/ext/associationproxy.py +2073 -0
  109. sqlalchemy/ext/asyncio/__init__.py +29 -0
  110. sqlalchemy/ext/asyncio/base.py +281 -0
  111. sqlalchemy/ext/asyncio/engine.py +1487 -0
  112. sqlalchemy/ext/asyncio/exc.py +21 -0
  113. sqlalchemy/ext/asyncio/result.py +994 -0
  114. sqlalchemy/ext/asyncio/scoping.py +1679 -0
  115. sqlalchemy/ext/asyncio/session.py +2006 -0
  116. sqlalchemy/ext/automap.py +1702 -0
  117. sqlalchemy/ext/baked.py +558 -0
  118. sqlalchemy/ext/compiler.py +601 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +561 -0
  121. sqlalchemy/ext/horizontal_shard.py +481 -0
  122. sqlalchemy/ext/hybrid.py +1877 -0
  123. sqlalchemy/ext/indexable.py +364 -0
  124. sqlalchemy/ext/instrumentation.py +450 -0
  125. sqlalchemy/ext/mutable.py +1081 -0
  126. sqlalchemy/ext/orderinglist.py +440 -0
  127. sqlalchemy/ext/serializer.py +184 -0
  128. sqlalchemy/future/__init__.py +17 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +188 -0
  131. sqlalchemy/log.py +279 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +180 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +991 -0
  137. sqlalchemy/orm/bulk_persistence.py +2168 -0
  138. sqlalchemy/orm/clsregistry.py +630 -0
  139. sqlalchemy/orm/collections.py +1569 -0
  140. sqlalchemy/orm/context.py +3475 -0
  141. sqlalchemy/orm/decl_api.py +2283 -0
  142. sqlalchemy/orm/decl_base.py +2320 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +306 -0
  146. sqlalchemy/orm/evaluator.py +378 -0
  147. sqlalchemy/orm/events.py +3387 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +749 -0
  151. sqlalchemy/orm/interfaces.py +1595 -0
  152. sqlalchemy/orm/loading.py +1712 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4465 -0
  155. sqlalchemy/orm/path_registry.py +907 -0
  156. sqlalchemy/orm/persistence.py +1790 -0
  157. sqlalchemy/orm/properties.py +972 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3608 -0
  160. sqlalchemy/orm/scoping.py +2233 -0
  161. sqlalchemy/orm/session.py +5468 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3552 -0
  165. sqlalchemy/orm/strategy_options.py +2648 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +797 -0
  168. sqlalchemy/orm/util.py +2461 -0
  169. sqlalchemy/orm/writeonly.py +701 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1540 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +583 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +75 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +402 -0
  178. sqlalchemy/sql/_cache_key_cy.cp315-win_amd64.pyd +0 -0
  179. sqlalchemy/sql/_cache_key_cy.py +363 -0
  180. sqlalchemy/sql/_dml_constructors.py +132 -0
  181. sqlalchemy/sql/_elements_constructors.py +2190 -0
  182. sqlalchemy/sql/_orm_types.py +19 -0
  183. sqlalchemy/sql/_selectable_constructors.py +840 -0
  184. sqlalchemy/sql/_typing.py +500 -0
  185. sqlalchemy/sql/_util_cy.cp315-win_amd64.pyd +0 -0
  186. sqlalchemy/sql/_util_cy.pxd +11 -0
  187. sqlalchemy/sql/_util_cy.py +127 -0
  188. sqlalchemy/sql/annotation.py +590 -0
  189. sqlalchemy/sql/base.py +2702 -0
  190. sqlalchemy/sql/cache_key.py +915 -0
  191. sqlalchemy/sql/coercions.py +1373 -0
  192. sqlalchemy/sql/compiler.py +8453 -0
  193. sqlalchemy/sql/crud.py +1816 -0
  194. sqlalchemy/sql/ddl.py +1962 -0
  195. sqlalchemy/sql/default_comparator.py +660 -0
  196. sqlalchemy/sql/dml.py +2018 -0
  197. sqlalchemy/sql/elements.py +6057 -0
  198. sqlalchemy/sql/events.py +458 -0
  199. sqlalchemy/sql/expression.py +171 -0
  200. sqlalchemy/sql/functions.py +2380 -0
  201. sqlalchemy/sql/lambdas.py +1442 -0
  202. sqlalchemy/sql/naming.py +204 -0
  203. sqlalchemy/sql/operators.py +2909 -0
  204. sqlalchemy/sql/roles.py +332 -0
  205. sqlalchemy/sql/schema.py +7075 -0
  206. sqlalchemy/sql/selectable.py +7634 -0
  207. sqlalchemy/sql/sqltypes.py +4130 -0
  208. sqlalchemy/sql/traversals.py +1041 -0
  209. sqlalchemy/sql/type_api.py +2450 -0
  210. sqlalchemy/sql/util.py +1496 -0
  211. sqlalchemy/sql/visitors.py +1153 -0
  212. sqlalchemy/testing/__init__.py +97 -0
  213. sqlalchemy/testing/assertions.py +1007 -0
  214. sqlalchemy/testing/assertsql.py +519 -0
  215. sqlalchemy/testing/asyncio.py +128 -0
  216. sqlalchemy/testing/cancellation.py +237 -0
  217. sqlalchemy/testing/config.py +440 -0
  218. sqlalchemy/testing/engines.py +482 -0
  219. sqlalchemy/testing/entities.py +117 -0
  220. sqlalchemy/testing/exclusions.py +501 -0
  221. sqlalchemy/testing/fixtures/__init__.py +30 -0
  222. sqlalchemy/testing/fixtures/base.py +426 -0
  223. sqlalchemy/testing/fixtures/mypy.py +247 -0
  224. sqlalchemy/testing/fixtures/orm.py +227 -0
  225. sqlalchemy/testing/fixtures/sql.py +538 -0
  226. sqlalchemy/testing/pickleable.py +155 -0
  227. sqlalchemy/testing/plugin/__init__.py +6 -0
  228. sqlalchemy/testing/plugin/bootstrap.py +50 -0
  229. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  230. sqlalchemy/testing/plugin/pytestplugin.py +896 -0
  231. sqlalchemy/testing/profiles_file.py +350 -0
  232. sqlalchemy/testing/profiling.py +294 -0
  233. sqlalchemy/testing/provision.py +633 -0
  234. sqlalchemy/testing/requirements.py +1971 -0
  235. sqlalchemy/testing/schema.py +198 -0
  236. sqlalchemy/testing/suite/__init__.py +19 -0
  237. sqlalchemy/testing/suite/test_cte.py +237 -0
  238. sqlalchemy/testing/suite/test_ddl.py +420 -0
  239. sqlalchemy/testing/suite/test_dialect.py +776 -0
  240. sqlalchemy/testing/suite/test_insert.py +630 -0
  241. sqlalchemy/testing/suite/test_reflection.py +3815 -0
  242. sqlalchemy/testing/suite/test_results.py +660 -0
  243. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  244. sqlalchemy/testing/suite/test_select.py +2112 -0
  245. sqlalchemy/testing/suite/test_sequence.py +317 -0
  246. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  247. sqlalchemy/testing/suite/test_types.py +2271 -0
  248. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  249. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  250. sqlalchemy/testing/util.py +575 -0
  251. sqlalchemy/testing/warnings.py +52 -0
  252. sqlalchemy/types.py +75 -0
  253. sqlalchemy/util/__init__.py +165 -0
  254. sqlalchemy/util/_collections.py +688 -0
  255. sqlalchemy/util/_collections_cy.cp315-win_amd64.pyd +0 -0
  256. sqlalchemy/util/_collections_cy.pxd +8 -0
  257. sqlalchemy/util/_collections_cy.py +516 -0
  258. sqlalchemy/util/_has_cython.py +48 -0
  259. sqlalchemy/util/_immutabledict_cy.cp315-win_amd64.pyd +0 -0
  260. sqlalchemy/util/_immutabledict_cy.py +240 -0
  261. sqlalchemy/util/compat.py +298 -0
  262. sqlalchemy/util/concurrency.py +272 -0
  263. sqlalchemy/util/cython.py +95 -0
  264. sqlalchemy/util/deprecations.py +401 -0
  265. sqlalchemy/util/langhelpers.py +2797 -0
  266. sqlalchemy/util/preloaded.py +153 -0
  267. sqlalchemy/util/queue.py +304 -0
  268. sqlalchemy/util/tool_support.py +202 -0
  269. sqlalchemy/util/topological.py +120 -0
  270. sqlalchemy/util/typing.py +709 -0
  271. sqlalchemy-2.1.0rc1.dist-info/METADATA +270 -0
  272. sqlalchemy-2.1.0rc1.dist-info/RECORD +276 -0
  273. sqlalchemy-2.1.0rc1.dist-info/WHEEL +5 -0
  274. sqlalchemy-2.1.0rc1.dist-info/licenses/AUTHORS +30 -0
  275. sqlalchemy-2.1.0rc1.dist-info/licenses/LICENSE +19 -0
  276. sqlalchemy-2.1.0rc1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1712 @@
1
+ # orm/loading.py
2
+ # Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of SQLAlchemy and is released under
6
+ # the MIT License: https://www.opensource.org/licenses/mit-license.php
7
+ # mypy: ignore-errors
8
+
9
+
10
+ """private module containing functions used to convert database
11
+ rows into object instances and associated state.
12
+
13
+ the functions here are called primarily by Query, Mapper,
14
+ as well as some of the attribute loading strategies.
15
+
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from typing import Any
21
+ from typing import Dict
22
+ from typing import Iterable
23
+ from typing import List
24
+ from typing import Mapping
25
+ from typing import Optional
26
+ from typing import Sequence
27
+ from typing import Tuple
28
+ from typing import TYPE_CHECKING
29
+ from typing import TypeVar
30
+ from typing import Union
31
+
32
+ from . import attributes
33
+ from . import exc as orm_exc
34
+ from . import path_registry
35
+ from .base import _DEFER_FOR_STATE
36
+ from .base import _RAISE_FOR_STATE
37
+ from .base import _SET_DEFERRED_EXPIRED
38
+ from .base import PassiveFlag
39
+ from .context import _ORMCompileState
40
+ from .context import FromStatement
41
+ from .context import QueryContext
42
+ from .strategies import _SelectInLoader
43
+ from .util import _none_set
44
+ from .util import state_str
45
+ from .. import exc as sa_exc
46
+ from .. import util
47
+ from ..engine import result_tuple
48
+ from ..engine.result import ChunkedIteratorResult
49
+ from ..engine.result import FrozenResult
50
+ from ..engine.result import SimpleResultMetaData
51
+ from ..sql import select
52
+ from ..sql import util as sql_util
53
+ from ..sql.selectable import ForUpdateArg
54
+ from ..sql.selectable import SelectState
55
+ from ..util import EMPTY_DICT
56
+ from ..util.typing import TupleAny
57
+ from ..util.typing import Unpack
58
+
59
+ if TYPE_CHECKING:
60
+ from ._typing import _IdentityKeyType
61
+ from .base import LoaderCallableStatus
62
+ from .interfaces import ORMOption
63
+ from .mapper import Mapper
64
+ from .query import Query
65
+ from .session import Session
66
+ from .state import InstanceState
67
+ from ..engine.cursor import CursorResult
68
+ from ..engine.interfaces import _ExecuteOptions
69
+ from ..engine.result import Result
70
+ from ..sql import Select
71
+
72
+ _T = TypeVar("_T", bound=Any)
73
+ _O = TypeVar("_O", bound=object)
74
+ _new_runid = util.counter()
75
+
76
+
77
+ _PopulatorDict = Dict[str, List[Tuple[str, Any]]]
78
+
79
+
80
+ def instances(
81
+ cursor: CursorResult[Unpack[TupleAny]], context: QueryContext
82
+ ) -> Result[Unpack[TupleAny]]:
83
+ """Return a :class:`.Result` given an ORM query context.
84
+
85
+ :param cursor: a :class:`.CursorResult`, generated by a statement
86
+ which came from :class:`.ORMCompileState`
87
+
88
+ :param context: a :class:`.QueryContext` object
89
+
90
+ :return: a :class:`.Result` object representing ORM results
91
+
92
+ .. versionchanged:: 1.4 The instances() function now uses
93
+ :class:`.Result` objects and has an all new interface.
94
+
95
+ """
96
+
97
+ context.runid = _new_runid()
98
+
99
+ if context.top_level_context:
100
+ is_top_level = False
101
+ context.post_load_paths = context.top_level_context.post_load_paths
102
+ else:
103
+ is_top_level = True
104
+ context.post_load_paths = {}
105
+
106
+ compile_state = context.compile_state
107
+ filtered = compile_state._has_mapper_entities
108
+ single_entity = (
109
+ not context.load_options._only_return_tuples
110
+ and len(compile_state._entities) == 1
111
+ and compile_state._entities[0].supports_single_entity
112
+ )
113
+
114
+ try:
115
+ process, labels, extra = list(
116
+ zip(
117
+ *[
118
+ query_entity.row_processor(context, cursor)
119
+ for query_entity in context.compile_state._entities
120
+ ]
121
+ )
122
+ )
123
+
124
+ if context.yield_per and (
125
+ context.loaders_require_buffering
126
+ or context.loaders_require_uniquing
127
+ ):
128
+ raise sa_exc.InvalidRequestError(
129
+ "Can't use yield_per with eager loaders that require uniquing "
130
+ "or row buffering, e.g. joinedload() against collections "
131
+ "or subqueryload(). Consider the selectinload() strategy "
132
+ "for better flexibility in loading objects."
133
+ )
134
+
135
+ except Exception:
136
+ with util.safe_reraise():
137
+ cursor.close()
138
+
139
+ def _not_hashable(datatype, *, legacy=False, uncertain=False):
140
+ if not legacy:
141
+
142
+ def go(obj):
143
+ if uncertain:
144
+ try:
145
+ return hash(obj)
146
+ except:
147
+ pass
148
+
149
+ raise sa_exc.InvalidRequestError(
150
+ "Can't apply uniqueness to row tuple containing value of "
151
+ f"""type {datatype!r}; {
152
+ 'the values returned appear to be'
153
+ if uncertain
154
+ else 'this datatype produces'
155
+ } non-hashable values"""
156
+ )
157
+
158
+ return go
159
+ elif not uncertain:
160
+ return id
161
+ else:
162
+ _use_id = False
163
+
164
+ def go(obj):
165
+ nonlocal _use_id
166
+
167
+ if not _use_id:
168
+ try:
169
+ return hash(obj)
170
+ except:
171
+ pass
172
+
173
+ # in #10459, we considered using a warning here, however
174
+ # as legacy query uses result.unique() in all cases, this
175
+ # would lead to too many warning cases.
176
+ _use_id = True
177
+
178
+ return id(obj)
179
+
180
+ return go
181
+
182
+ _uniquing_is_active = False
183
+
184
+ def _create_unique_filters(result):
185
+ nonlocal _uniquing_is_active
186
+
187
+ if result._yield_per:
188
+ raise sa_exc.InvalidRequestError(
189
+ "Can't use the ORM yield_per feature "
190
+ "in conjunction with unique()"
191
+ )
192
+
193
+ _uniquing_is_active = True
194
+ return [
195
+ (
196
+ _not_hashable(
197
+ ent.column.type, # type: ignore
198
+ legacy=context.load_options._legacy_uniquing,
199
+ uncertain=ent._null_column_type,
200
+ )
201
+ if (
202
+ not ent.use_id_for_hash
203
+ and (ent._non_hashable_value or ent._null_column_type)
204
+ )
205
+ else id if ent.use_id_for_hash else None
206
+ )
207
+ for ent in context.compile_state._entities
208
+ ]
209
+
210
+ row_metadata = SimpleResultMetaData(
211
+ labels, extra, _create_unique_filters=_create_unique_filters
212
+ )
213
+
214
+ def chunks(size): # type: ignore
215
+ while True:
216
+ yield_per = size
217
+
218
+ context.partials = {}
219
+
220
+ if yield_per:
221
+ if _uniquing_is_active:
222
+ raise sa_exc.InvalidRequestError(
223
+ "Can't use the ORM yield_per feature "
224
+ "in conjunction with unique()"
225
+ )
226
+ fetch = cursor.fetchmany(yield_per)
227
+
228
+ if not fetch:
229
+ break
230
+ else:
231
+ fetch = cursor._raw_all_tuples()
232
+
233
+ if single_entity:
234
+ proc = process[0]
235
+ rows = [proc(row) for row in fetch]
236
+ else:
237
+ rows = [
238
+ tuple([proc(row) for proc in process]) for row in fetch
239
+ ]
240
+
241
+ # if we are the originating load from a query, meaning we
242
+ # aren't being called as a result of a nested "post load",
243
+ # iterate through all the collected post loaders and fire them
244
+ # off. Previously this used to work recursively, however that
245
+ # prevented deeply nested structures from being loadable
246
+ if is_top_level:
247
+ if yield_per:
248
+ # if using yield per, memoize the state of the
249
+ # collection so that it can be restored
250
+ top_level_post_loads = list(
251
+ context.post_load_paths.items()
252
+ )
253
+
254
+ while context.post_load_paths:
255
+ post_loads = list(context.post_load_paths.items())
256
+ context.post_load_paths.clear()
257
+ for path, post_load in post_loads:
258
+ post_load.invoke(context, path)
259
+
260
+ if yield_per:
261
+ context.post_load_paths.clear()
262
+ context.post_load_paths.update(top_level_post_loads)
263
+
264
+ yield rows
265
+
266
+ if not yield_per:
267
+ break
268
+
269
+ if context.execution_options.get("prebuffer_rows", False):
270
+ # this is a bit of a hack at the moment.
271
+ # I would rather have some option in the result to pre-buffer
272
+ # internally.
273
+ _prebuffered = list(chunks(None))
274
+
275
+ def chunks(size):
276
+ return iter(_prebuffered)
277
+
278
+ result = ChunkedIteratorResult(
279
+ row_metadata,
280
+ chunks,
281
+ source_supports_scalars=single_entity,
282
+ raw=cursor,
283
+ dynamic_yield_per=cursor.context._is_server_side,
284
+ context=context,
285
+ )
286
+
287
+ # filtered and single_entity are used to indicate to legacy Query that the
288
+ # query has ORM entities, so legacy deduping and scalars should be called
289
+ # on the result.
290
+ result._attributes = result._attributes.union(
291
+ dict(filtered=filtered, is_single_entity=single_entity)
292
+ )
293
+
294
+ # multi_row_eager_loaders OTOH is specific to joinedload.
295
+ if context.requires_uniquing:
296
+
297
+ def require_unique(obj):
298
+ raise sa_exc.InvalidRequestError(
299
+ "The unique() method must be invoked on this Result, "
300
+ "as it contains results that include joined eager loads "
301
+ "against collections"
302
+ )
303
+
304
+ result._unique_filter_state = (set(), require_unique)
305
+
306
+ if context.yield_per:
307
+ result.yield_per(context.yield_per)
308
+
309
+ return result
310
+
311
+
312
+ @util.preload_module("sqlalchemy.orm.context")
313
+ def merge_frozen_result(session, statement, frozen_result, load=True):
314
+ """Merge a :class:`_engine.FrozenResult` back into a :class:`_orm.Session`,
315
+ returning a new :class:`_engine.Result` object with :term:`persistent`
316
+ objects.
317
+
318
+ See the section :ref:`do_orm_execute_re_executing` for an example.
319
+
320
+ .. seealso::
321
+
322
+ :ref:`do_orm_execute_re_executing`
323
+
324
+ :meth:`_engine.Result.freeze`
325
+
326
+ :class:`_engine.FrozenResult`
327
+
328
+ """
329
+ querycontext = util.preloaded.orm_context
330
+
331
+ if load:
332
+ # flush current contents if we expect to load data
333
+ session._autoflush()
334
+
335
+ ctx = querycontext._ORMSelectCompileState._create_entities_collection(
336
+ statement, legacy=False
337
+ )
338
+
339
+ with session.no_autoflush:
340
+ mapped_entities = [
341
+ i
342
+ for i, e in enumerate(ctx._entities)
343
+ if isinstance(e, querycontext._MapperEntity)
344
+ ]
345
+ keys = [ent._label_name for ent in ctx._entities]
346
+
347
+ keyed_tuple = result_tuple(
348
+ keys, [ent._extra_entities for ent in ctx._entities]
349
+ )
350
+
351
+ result = []
352
+ for newrow in frozen_result._rewrite_rows():
353
+ for i in mapped_entities:
354
+ if newrow[i] is not None:
355
+ newrow[i] = session._merge(
356
+ attributes.instance_state(newrow[i]),
357
+ attributes.instance_dict(newrow[i]),
358
+ load=load,
359
+ _recursive={},
360
+ _resolve_conflict_map={},
361
+ )
362
+
363
+ result.append(keyed_tuple(newrow))
364
+
365
+ return frozen_result.with_new_rows(result)
366
+
367
+
368
+ @util.became_legacy_20(
369
+ ":func:`_orm.merge_result`",
370
+ alternative="The function as well as the method on :class:`_orm.Query` "
371
+ "is superseded by the :func:`_orm.merge_frozen_result` function.",
372
+ )
373
+ @util.preload_module("sqlalchemy.orm.context")
374
+ def merge_result(
375
+ query: Query[Any],
376
+ iterator: Union[FrozenResult, Iterable[Sequence[Any]], Iterable[object]],
377
+ load: bool = True,
378
+ ) -> Union[FrozenResult, Iterable[Any]]:
379
+ """Merge a result into the given :class:`.Query` object's Session.
380
+
381
+ See :meth:`_orm.Query.merge_result` for top-level documentation on this
382
+ function.
383
+
384
+ """
385
+
386
+ querycontext = util.preloaded.orm_context
387
+
388
+ session = query.session
389
+ if load:
390
+ # flush current contents if we expect to load data
391
+ session._autoflush()
392
+
393
+ # TODO: need test coverage and documentation for the FrozenResult
394
+ # use case.
395
+ if isinstance(iterator, FrozenResult):
396
+ frozen_result = iterator
397
+ iterator = iter(frozen_result.data)
398
+ else:
399
+ frozen_result = None
400
+
401
+ ctx = querycontext._ORMSelectCompileState._create_entities_collection(
402
+ query, legacy=True
403
+ )
404
+
405
+ autoflush = session.autoflush
406
+ try:
407
+ session.autoflush = False
408
+ single_entity = not frozen_result and len(ctx._entities) == 1
409
+
410
+ if single_entity:
411
+ if isinstance(ctx._entities[0], querycontext._MapperEntity):
412
+ result = [
413
+ session._merge(
414
+ attributes.instance_state(instance),
415
+ attributes.instance_dict(instance),
416
+ load=load,
417
+ _recursive={},
418
+ _resolve_conflict_map={},
419
+ )
420
+ for instance in iterator
421
+ ]
422
+ else:
423
+ result = list(iterator)
424
+ else:
425
+ mapped_entities = [
426
+ i
427
+ for i, e in enumerate(ctx._entities)
428
+ if isinstance(e, querycontext._MapperEntity)
429
+ ]
430
+ result = []
431
+ keys = [ent._label_name for ent in ctx._entities]
432
+
433
+ keyed_tuple = result_tuple(
434
+ keys, [ent._extra_entities for ent in ctx._entities]
435
+ )
436
+
437
+ for row in iterator:
438
+ newrow = list(row)
439
+ for i in mapped_entities:
440
+ if newrow[i] is not None:
441
+ newrow[i] = session._merge(
442
+ attributes.instance_state(newrow[i]),
443
+ attributes.instance_dict(newrow[i]),
444
+ load=load,
445
+ _recursive={},
446
+ _resolve_conflict_map={},
447
+ )
448
+ result.append(keyed_tuple(newrow))
449
+
450
+ if frozen_result:
451
+ return frozen_result.with_new_rows(result)
452
+ else:
453
+ return iter(result)
454
+ finally:
455
+ session.autoflush = autoflush
456
+
457
+
458
+ def get_from_identity(
459
+ session: Session,
460
+ mapper: Mapper[_O],
461
+ key: _IdentityKeyType[_O],
462
+ passive: PassiveFlag,
463
+ ) -> Union[LoaderCallableStatus, Optional[_O]]:
464
+ """Look up the given key in the given session's identity map,
465
+ check the object for expired state if found.
466
+
467
+ """
468
+ instance = session.identity_map.get(key)
469
+ if instance is not None:
470
+ state = attributes.instance_state(instance)
471
+
472
+ if mapper.inherits and not state.mapper.isa(mapper):
473
+ return attributes.PASSIVE_CLASS_MISMATCH
474
+
475
+ # expired - ensure it still exists
476
+ if state.expired:
477
+ if not passive & attributes.SQL_OK:
478
+ # TODO: no coverage here
479
+ return attributes.PASSIVE_NO_RESULT
480
+ elif not passive & attributes.RELATED_OBJECT_OK:
481
+ # this mode is used within a flush and the instance's
482
+ # expired state will be checked soon enough, if necessary.
483
+ # also used by immediateloader for a mutually-dependent
484
+ # o2m->m2m load, :ticket:`6301`
485
+ return instance
486
+ try:
487
+ state._load_expired(state, passive)
488
+ except orm_exc.ObjectDeletedError:
489
+ session._remove_newly_deleted([state])
490
+ return None
491
+ return instance
492
+ else:
493
+ return None
494
+
495
+
496
+ def _load_on_ident(
497
+ session: Session,
498
+ statement: Union[Select, FromStatement],
499
+ key: Optional[_IdentityKeyType],
500
+ *,
501
+ load_options: Optional[Sequence[ORMOption]] = None,
502
+ refresh_state: Optional[InstanceState[Any]] = None,
503
+ with_for_update: Optional[ForUpdateArg] = None,
504
+ only_load_props: Optional[Iterable[str]] = None,
505
+ no_autoflush: bool = False,
506
+ bind_arguments: Mapping[str, Any] = util.EMPTY_DICT,
507
+ execution_options: _ExecuteOptions = util.EMPTY_DICT,
508
+ require_pk_cols: bool = False,
509
+ is_user_refresh: bool = False,
510
+ ):
511
+ """Load the given identity key from the database."""
512
+ if key is not None:
513
+ ident = key[1]
514
+ identity_token = key[2]
515
+ else:
516
+ ident = identity_token = None
517
+
518
+ return _load_on_pk_identity(
519
+ session,
520
+ statement,
521
+ ident,
522
+ load_options=load_options,
523
+ refresh_state=refresh_state,
524
+ with_for_update=with_for_update,
525
+ only_load_props=only_load_props,
526
+ identity_token=identity_token,
527
+ no_autoflush=no_autoflush,
528
+ bind_arguments=bind_arguments,
529
+ execution_options=execution_options,
530
+ require_pk_cols=require_pk_cols,
531
+ is_user_refresh=is_user_refresh,
532
+ )
533
+
534
+
535
+ def _load_on_pk_identity(
536
+ session: Session,
537
+ statement: Union[Select, FromStatement],
538
+ primary_key_identity: Optional[Tuple[Any, ...]],
539
+ *,
540
+ load_options: Optional[Sequence[ORMOption]] = None,
541
+ refresh_state: Optional[InstanceState[Any]] = None,
542
+ with_for_update: Optional[ForUpdateArg] = None,
543
+ only_load_props: Optional[Iterable[str]] = None,
544
+ identity_token: Optional[Any] = None,
545
+ no_autoflush: bool = False,
546
+ bind_arguments: Mapping[str, Any] = util.EMPTY_DICT,
547
+ execution_options: _ExecuteOptions = util.EMPTY_DICT,
548
+ require_pk_cols: bool = False,
549
+ is_user_refresh: bool = False,
550
+ ):
551
+ """Load the given primary key identity from the database."""
552
+
553
+ query = statement
554
+ q = query._clone()
555
+
556
+ assert not q._is_lambda_element
557
+
558
+ if load_options is None:
559
+ load_options = QueryContext.default_load_options
560
+
561
+ if (
562
+ statement._compile_options
563
+ is SelectState.default_select_compile_options
564
+ ):
565
+ compile_options = _ORMCompileState.default_compile_options
566
+ else:
567
+ compile_options = statement._compile_options
568
+
569
+ if primary_key_identity is not None:
570
+ mapper = query._propagate_attrs["plugin_subject"]
571
+
572
+ _get_clause, _get_params = mapper._get_clause
573
+
574
+ # None present in ident - turn those comparisons
575
+ # into "IS NULL"
576
+ if None in primary_key_identity:
577
+ nones = {
578
+ _get_params[col].key
579
+ for col, value in zip(mapper.primary_key, primary_key_identity)
580
+ if value is None
581
+ }
582
+
583
+ _get_clause = sql_util.adapt_criterion_to_null(_get_clause, nones)
584
+
585
+ if len(nones) == len(primary_key_identity):
586
+ util.warn(
587
+ "fully NULL primary key identity cannot load any "
588
+ "object. This condition may raise an error in a future "
589
+ "release."
590
+ )
591
+
592
+ q._where_criteria = (_get_clause,)
593
+
594
+ params = {
595
+ _get_params[primary_key].key: id_val
596
+ for id_val, primary_key in zip(
597
+ primary_key_identity, mapper.primary_key
598
+ )
599
+ }
600
+ else:
601
+ params = None
602
+
603
+ if with_for_update is not None:
604
+ version_check = True
605
+ q._for_update_arg = with_for_update
606
+ elif query._for_update_arg is not None:
607
+ version_check = True
608
+ q._for_update_arg = query._for_update_arg
609
+ else:
610
+ version_check = False
611
+
612
+ if require_pk_cols and only_load_props:
613
+ if not refresh_state:
614
+ raise sa_exc.ArgumentError(
615
+ "refresh_state is required when require_pk_cols is present"
616
+ )
617
+
618
+ refresh_state_prokeys = refresh_state.mapper._primary_key_propkeys
619
+ has_changes = {
620
+ key
621
+ for key in refresh_state_prokeys.difference(only_load_props)
622
+ if refresh_state.attrs[key].history.has_changes()
623
+ }
624
+ if has_changes:
625
+ # raise if pending pk changes are present.
626
+ # technically, this could be limited to the case where we have
627
+ # relationships in the only_load_props collection to be refreshed
628
+ # also (and only ones that have a secondary eager loader, at that).
629
+ # however, the error is in place across the board so that behavior
630
+ # here is easier to predict. The use case it prevents is one
631
+ # of mutating PK attrs, leaving them unflushed,
632
+ # calling session.refresh(), and expecting those attrs to remain
633
+ # still unflushed. It seems likely someone doing all those
634
+ # things would be better off having the PK attributes flushed
635
+ # to the database before tinkering like that (session.refresh() is
636
+ # tinkering).
637
+ raise sa_exc.InvalidRequestError(
638
+ f"Please flush pending primary key changes on "
639
+ "attributes "
640
+ f"{has_changes} for mapper {refresh_state.mapper} before "
641
+ "proceeding with a refresh"
642
+ )
643
+
644
+ # overall, the ORM has no internal flow right now for "dont load the
645
+ # primary row of an object at all, but fire off
646
+ # selectinload/subqueryload/immediateload for some relationships".
647
+ # It would probably be a pretty big effort to add such a flow. So
648
+ # here, the case for #8703 is introduced; user asks to refresh some
649
+ # relationship attributes only which are
650
+ # selectinload/subqueryload/immediateload/ etc. (not joinedload).
651
+ # ORM complains there's no columns in the primary row to load.
652
+ # So here, we just add the PK cols if that
653
+ # case is detected, so that there is a SELECT emitted for the primary
654
+ # row.
655
+ #
656
+ # Let's just state right up front, for this one little case,
657
+ # the ORM here is adding a whole extra SELECT just to satisfy
658
+ # limitations in the internal flow. This is really not a thing
659
+ # SQLAlchemy finds itself doing like, ever, obviously, we are
660
+ # constantly working to *remove* SELECTs we don't need. We
661
+ # rationalize this for now based on 1. session.refresh() is not
662
+ # commonly used 2. session.refresh() with only relationship attrs is
663
+ # even less commonly used 3. the SELECT in question is very low
664
+ # latency.
665
+ #
666
+ # to add the flow to not include the SELECT, the quickest way
667
+ # might be to just manufacture a single-row result set to send off to
668
+ # instances(), but we'd have to weave that into context.py and all
669
+ # that. For 2.0.0, we have enough big changes to navigate for now.
670
+ #
671
+ mp = refresh_state.mapper._props
672
+ for p in only_load_props:
673
+ if mp[p]._is_relationship:
674
+ only_load_props = refresh_state_prokeys.union(only_load_props)
675
+ break
676
+
677
+ if refresh_state and refresh_state.load_options:
678
+ compile_options += {"_current_path": refresh_state.load_path.parent}
679
+ q = q.options(*refresh_state.load_options)
680
+
681
+ new_compile_options, load_options = _set_get_options(
682
+ compile_options,
683
+ load_options,
684
+ version_check=version_check,
685
+ only_load_props=only_load_props,
686
+ refresh_state=refresh_state,
687
+ identity_token=identity_token,
688
+ is_user_refresh=is_user_refresh,
689
+ )
690
+
691
+ q._compile_options = new_compile_options
692
+ q._order_by = None
693
+
694
+ if no_autoflush:
695
+ load_options += {"_autoflush": False}
696
+
697
+ execution_options = util.EMPTY_DICT.merge_with(
698
+ execution_options,
699
+ util.immutabledict(_sa_orm_load_options=load_options),
700
+ )
701
+ result = (
702
+ session.execute(
703
+ q,
704
+ params=params,
705
+ execution_options=execution_options,
706
+ bind_arguments=bind_arguments,
707
+ )
708
+ .unique()
709
+ .scalars()
710
+ )
711
+
712
+ try:
713
+ return result.one()
714
+ except orm_exc.NoResultFound:
715
+ return None
716
+
717
+
718
+ def _set_get_options(
719
+ compile_opt,
720
+ load_opt,
721
+ populate_existing=None,
722
+ version_check=None,
723
+ only_load_props=None,
724
+ refresh_state=None,
725
+ identity_token=None,
726
+ is_user_refresh=None,
727
+ ):
728
+ compile_options = {}
729
+ load_options = {}
730
+ if version_check:
731
+ load_options["_version_check"] = version_check
732
+ if populate_existing:
733
+ load_options["_populate_existing"] = populate_existing
734
+ if refresh_state:
735
+ load_options["_refresh_state"] = refresh_state
736
+ compile_options["_for_refresh_state"] = True
737
+ if only_load_props:
738
+ compile_options["_only_load_props"] = frozenset(only_load_props)
739
+ if identity_token:
740
+ load_options["_identity_token"] = identity_token
741
+
742
+ if is_user_refresh:
743
+ load_options["_is_user_refresh"] = is_user_refresh
744
+ if load_options:
745
+ load_opt += load_options
746
+ if compile_options:
747
+ compile_opt += compile_options
748
+
749
+ return compile_opt, load_opt
750
+
751
+
752
+ def _setup_entity_query(
753
+ compile_state,
754
+ mapper,
755
+ query_entity,
756
+ path,
757
+ adapter,
758
+ column_collection,
759
+ with_polymorphic=None,
760
+ only_load_props=None,
761
+ polymorphic_discriminator=None,
762
+ **kw,
763
+ ):
764
+ if with_polymorphic:
765
+ poly_properties = mapper._iterate_polymorphic_properties(
766
+ with_polymorphic
767
+ )
768
+ else:
769
+ poly_properties = mapper._polymorphic_properties
770
+
771
+ quick_populators = {}
772
+
773
+ path.set(compile_state.attributes, "memoized_setups", quick_populators)
774
+
775
+ # for the lead entities in the path, e.g. not eager loads, and
776
+ # assuming a user-passed aliased class, e.g. not a from_self() or any
777
+ # implicit aliasing, don't add columns to the SELECT that aren't
778
+ # in the thing that's aliased.
779
+ check_for_adapt = adapter and len(path) == 1 and path[-1].is_aliased_class
780
+
781
+ for value in poly_properties:
782
+ if only_load_props and value.key not in only_load_props:
783
+ continue
784
+ value.setup(
785
+ compile_state,
786
+ query_entity,
787
+ path,
788
+ adapter,
789
+ only_load_props=only_load_props,
790
+ column_collection=column_collection,
791
+ memoized_populators=quick_populators,
792
+ check_for_adapt=check_for_adapt,
793
+ **kw,
794
+ )
795
+
796
+ if (
797
+ polymorphic_discriminator is not None
798
+ and polymorphic_discriminator is not mapper.polymorphic_on
799
+ ):
800
+ if adapter:
801
+ pd = adapter.columns[polymorphic_discriminator]
802
+ else:
803
+ pd = polymorphic_discriminator
804
+ column_collection.append(pd)
805
+
806
+
807
+ def _warn_for_runid_changed(state):
808
+ util.warn(
809
+ "Loading context for %s has changed within a load/refresh "
810
+ "handler, suggesting a row refresh operation took place. If this "
811
+ "event handler is expected to be "
812
+ "emitting row refresh operations within an existing load or refresh "
813
+ "operation, set restore_load_context=True when establishing the "
814
+ "listener to ensure the context remains unchanged when the event "
815
+ "handler completes." % (state_str(state),)
816
+ )
817
+
818
+
819
+ def _instance_processor(
820
+ query_entity,
821
+ mapper,
822
+ context,
823
+ result,
824
+ path,
825
+ adapter,
826
+ only_load_props=None,
827
+ refresh_state=None,
828
+ polymorphic_discriminator=None,
829
+ _polymorphic_from=None,
830
+ ):
831
+ """Produce a mapper level row processor callable
832
+ which processes rows into mapped instances."""
833
+
834
+ # note that this method, most of which exists in a closure
835
+ # called _instance(), resists being broken out, as
836
+ # attempts to do so tend to add significant function
837
+ # call overhead. _instance() is the most
838
+ # performance-critical section in the whole ORM.
839
+
840
+ identity_class = mapper._identity_class
841
+ compile_state = context.compile_state
842
+
843
+ # look for "row getter" functions that have been assigned along
844
+ # with the compile state that were cached from a previous load.
845
+ # these are operator.itemgetter() objects that each will extract a
846
+ # particular column from each row.
847
+
848
+ getter_key = ("getters", mapper)
849
+ getters = path.get(compile_state.attributes, getter_key, None)
850
+
851
+ if getters is None:
852
+ # no getters, so go through a list of attributes we are loading for,
853
+ # and the ones that are column based will have already put information
854
+ # for us in another collection "memoized_setups", which represents the
855
+ # output of the LoaderStrategy.setup_query() method. We can just as
856
+ # easily call LoaderStrategy.create_row_processor for each, but by
857
+ # getting it all at once from setup_query we save another method call
858
+ # per attribute.
859
+ props = mapper._prop_set
860
+ if only_load_props is not None:
861
+ props = props.intersection(
862
+ mapper._props[k] for k in only_load_props
863
+ )
864
+
865
+ quick_populators = path.get(
866
+ context.attributes, "memoized_setups", EMPTY_DICT
867
+ )
868
+
869
+ todo = []
870
+ cached_populators = {
871
+ "new": [],
872
+ "quick": [],
873
+ "deferred": [],
874
+ "expire": [],
875
+ "existing": [],
876
+ "eager": [],
877
+ }
878
+
879
+ if refresh_state is None:
880
+ # we can also get the "primary key" tuple getter function
881
+ pk_cols = mapper.primary_key
882
+
883
+ if adapter:
884
+ pk_cols = [adapter.columns[c] for c in pk_cols]
885
+ primary_key_getter = result._tuple_getter(pk_cols)
886
+ else:
887
+ primary_key_getter = None
888
+
889
+ getters = {
890
+ "cached_populators": cached_populators,
891
+ "todo": todo,
892
+ "primary_key_getter": primary_key_getter,
893
+ }
894
+ for prop in props:
895
+ if prop in quick_populators:
896
+ # this is an inlined path just for column-based attributes.
897
+ col = quick_populators[prop]
898
+ if col is _DEFER_FOR_STATE:
899
+ cached_populators["new"].append(
900
+ (prop.key, prop._deferred_column_loader)
901
+ )
902
+ elif col is _SET_DEFERRED_EXPIRED:
903
+ # note that in this path, we are no longer
904
+ # searching in the result to see if the column might
905
+ # be present in some unexpected way.
906
+ cached_populators["expire"].append((prop.key, False))
907
+ elif col is _RAISE_FOR_STATE:
908
+ cached_populators["new"].append(
909
+ (prop.key, prop._raise_column_loader)
910
+ )
911
+ else:
912
+ getter = None
913
+ if adapter:
914
+ # this logic had been removed for all 1.4 releases
915
+ # up until 1.4.18; the adapter here is particularly
916
+ # the compound eager adapter which isn't accommodated
917
+ # in the quick_populators right now. The "fallback"
918
+ # logic below instead took over in many more cases
919
+ # until issue #6596 was identified.
920
+
921
+ # note there is still an issue where this codepath
922
+ # produces no "getter" for cases where a joined-inh
923
+ # mapping includes a labeled column property, meaning
924
+ # KeyError is caught internally and we fall back to
925
+ # _getter(col), which works anyway. The adapter
926
+ # here for joined inh without any aliasing might not
927
+ # be useful. Tests which see this include
928
+ # test.orm.inheritance.test_basic ->
929
+ # EagerTargetingTest.test_adapt_stringency
930
+ # OptimizedLoadTest.test_column_expression_joined
931
+ # PolymorphicOnNotLocalTest.test_polymorphic_on_column_prop # noqa: E501
932
+ #
933
+
934
+ adapted_col = adapter.columns[col]
935
+ if adapted_col is not None:
936
+ getter = result._getter(adapted_col, False)
937
+ if not getter:
938
+ getter = result._getter(col, False)
939
+ if getter:
940
+ cached_populators["quick"].append((prop.key, getter))
941
+ else:
942
+ # fall back to the ColumnProperty itself, which
943
+ # will iterate through all of its columns
944
+ # to see if one fits
945
+ prop.create_row_processor(
946
+ context,
947
+ query_entity,
948
+ path,
949
+ mapper,
950
+ result,
951
+ adapter,
952
+ cached_populators,
953
+ )
954
+ else:
955
+ # loader strategies like subqueryload, selectinload,
956
+ # joinedload, basically relationships, these need to interact
957
+ # with the context each time to work correctly.
958
+ todo.append(prop)
959
+
960
+ path.set(compile_state.attributes, getter_key, getters)
961
+
962
+ cached_populators = getters["cached_populators"]
963
+
964
+ populators = {key: list(value) for key, value in cached_populators.items()}
965
+ for prop in getters["todo"]:
966
+ prop.create_row_processor(
967
+ context, query_entity, path, mapper, result, adapter, populators
968
+ )
969
+
970
+ propagated_loader_options = context.propagated_loader_options
971
+ load_path = (
972
+ context.compile_state.current_path + path
973
+ if context.compile_state.current_path.path
974
+ else path
975
+ )
976
+
977
+ session_identity_map = context.session.identity_map
978
+
979
+ populate_existing = context.populate_existing or mapper.always_refresh
980
+ load_evt = bool(mapper.class_manager.dispatch.load)
981
+ refresh_evt = bool(mapper.class_manager.dispatch.refresh)
982
+ persistent_evt = bool(context.session.dispatch.loaded_as_persistent)
983
+ if persistent_evt:
984
+ loaded_as_persistent = context.session.dispatch.loaded_as_persistent
985
+ instance_state = attributes.instance_state
986
+ instance_dict = attributes.instance_dict
987
+ session_id = context.session.hash_key
988
+ runid = context.runid
989
+ identity_token = context.identity_token
990
+
991
+ version_check = context.version_check
992
+ if version_check:
993
+ version_id_col = mapper.version_id_col
994
+ if version_id_col is not None:
995
+ if adapter:
996
+ version_id_col = adapter.columns[version_id_col]
997
+ version_id_getter = result._getter(version_id_col)
998
+ else:
999
+ version_id_getter = None
1000
+
1001
+ if not refresh_state and _polymorphic_from is not None:
1002
+ key = ("loader", path.path)
1003
+
1004
+ if key in context.attributes and context.attributes[key].strategy == (
1005
+ ("selectinload_polymorphic", True),
1006
+ ):
1007
+ option_entities = context.attributes[key].local_opts["entities"]
1008
+ else:
1009
+ option_entities = None
1010
+ selectin_load_via = mapper._should_selectin_load(
1011
+ option_entities,
1012
+ _polymorphic_from,
1013
+ )
1014
+
1015
+ if selectin_load_via and selectin_load_via is not _polymorphic_from:
1016
+ # only_load_props goes w/ refresh_state only, and in a refresh
1017
+ # we are a single row query for the exact entity; polymorphic
1018
+ # loading does not apply
1019
+ assert only_load_props is None
1020
+
1021
+ if selectin_load_via.is_mapper:
1022
+ _load_supers = []
1023
+ _endmost_mapper = selectin_load_via
1024
+ while (
1025
+ _endmost_mapper
1026
+ and _endmost_mapper is not _polymorphic_from
1027
+ ):
1028
+ _load_supers.append(_endmost_mapper)
1029
+ _endmost_mapper = _endmost_mapper.inherits
1030
+ else:
1031
+ _load_supers = [selectin_load_via]
1032
+
1033
+ for _selectinload_entity in _load_supers:
1034
+ if _PostLoad.path_exists(
1035
+ context, load_path, _selectinload_entity
1036
+ ):
1037
+ continue
1038
+ callable_ = _load_subclass_via_in(
1039
+ context,
1040
+ path,
1041
+ _selectinload_entity,
1042
+ _polymorphic_from,
1043
+ option_entities,
1044
+ )
1045
+ _PostLoad.callable_for_path(
1046
+ context,
1047
+ load_path,
1048
+ _selectinload_entity.mapper,
1049
+ _selectinload_entity,
1050
+ callable_,
1051
+ _selectinload_entity,
1052
+ )
1053
+
1054
+ post_load = _PostLoad.for_context(context, load_path, only_load_props)
1055
+
1056
+ if refresh_state:
1057
+ refresh_identity_key = refresh_state.key
1058
+ if refresh_identity_key is None:
1059
+ # super-rare condition; a refresh is being called
1060
+ # on a non-instance-key instance; this is meant to only
1061
+ # occur within a flush()
1062
+ refresh_identity_key = mapper._identity_key_from_state(
1063
+ refresh_state
1064
+ )
1065
+ else:
1066
+ refresh_identity_key = None
1067
+
1068
+ primary_key_getter = getters["primary_key_getter"]
1069
+
1070
+ if mapper.allow_partial_pks:
1071
+ is_not_primary_key = _none_set.issuperset
1072
+ else:
1073
+ is_not_primary_key = _none_set.intersection
1074
+
1075
+ def _instance(row):
1076
+ # determine the state that we'll be populating
1077
+ if refresh_identity_key:
1078
+ # fixed state that we're refreshing
1079
+ state = refresh_state
1080
+ instance = state.obj()
1081
+ dict_ = instance_dict(instance)
1082
+ isnew = state.runid != runid
1083
+ currentload = True
1084
+ loaded_instance = False
1085
+ else:
1086
+ # look at the row, see if that identity is in the
1087
+ # session, or we have to create a new one
1088
+ identitykey = (
1089
+ identity_class,
1090
+ primary_key_getter(row),
1091
+ identity_token,
1092
+ )
1093
+
1094
+ instance = session_identity_map.get(identitykey)
1095
+
1096
+ if instance is not None:
1097
+ # existing instance
1098
+ state = instance_state(instance)
1099
+ dict_ = instance_dict(instance)
1100
+
1101
+ isnew = state.runid != runid
1102
+ currentload = not isnew
1103
+ loaded_instance = False
1104
+
1105
+ if version_check and version_id_getter and not currentload:
1106
+ _validate_version_id(
1107
+ mapper, state, dict_, row, version_id_getter
1108
+ )
1109
+
1110
+ else:
1111
+ # create a new instance
1112
+
1113
+ # check for non-NULL values in the primary key columns,
1114
+ # else no entity is returned for the row
1115
+ if is_not_primary_key(identitykey[1]):
1116
+ return None
1117
+
1118
+ isnew = True
1119
+ currentload = True
1120
+ loaded_instance = True
1121
+
1122
+ instance = mapper.class_manager.new_instance()
1123
+
1124
+ dict_ = instance_dict(instance)
1125
+ state = instance_state(instance)
1126
+ state.key = identitykey
1127
+ state.identity_token = identity_token
1128
+
1129
+ # attach instance to session.
1130
+ state.session_id = session_id
1131
+ session_identity_map._add_unpresent(state, identitykey)
1132
+
1133
+ effective_populate_existing = populate_existing
1134
+ if refresh_state is state:
1135
+ effective_populate_existing = True
1136
+
1137
+ # populate. this looks at whether this state is new
1138
+ # for this load or was existing, and whether or not this
1139
+ # row is the first row with this identity.
1140
+ if currentload or effective_populate_existing:
1141
+ # full population routines. Objects here are either
1142
+ # just created, or we are doing a populate_existing
1143
+
1144
+ # be conservative about setting load_path when populate_existing
1145
+ # is in effect; want to maintain options from the original
1146
+ # load. see test_expire->test_refresh_maintains_deferred_options
1147
+ if isnew and (
1148
+ propagated_loader_options or not effective_populate_existing
1149
+ ):
1150
+ state.load_options = propagated_loader_options
1151
+ state.load_path = load_path
1152
+
1153
+ _populate_full(
1154
+ context,
1155
+ row,
1156
+ state,
1157
+ dict_,
1158
+ isnew,
1159
+ load_path,
1160
+ loaded_instance,
1161
+ effective_populate_existing,
1162
+ populators,
1163
+ )
1164
+
1165
+ if isnew:
1166
+ # state.runid should be equal to context.runid / runid
1167
+ # here, however for event checks we are being more conservative
1168
+ # and checking against existing run id
1169
+ # assert state.runid == runid
1170
+
1171
+ existing_runid = state.runid
1172
+
1173
+ if loaded_instance:
1174
+ if load_evt:
1175
+ state.manager.dispatch.load(state, context)
1176
+ if state.runid != existing_runid:
1177
+ _warn_for_runid_changed(state)
1178
+ if persistent_evt:
1179
+ loaded_as_persistent(context.session, state)
1180
+ if state.runid != existing_runid:
1181
+ _warn_for_runid_changed(state)
1182
+ elif refresh_evt:
1183
+ state.manager.dispatch.refresh(
1184
+ state, context, only_load_props
1185
+ )
1186
+ if state.runid != runid:
1187
+ _warn_for_runid_changed(state)
1188
+
1189
+ if effective_populate_existing or state.modified:
1190
+ if refresh_state and only_load_props:
1191
+ state._commit(dict_, only_load_props)
1192
+ else:
1193
+ state._commit_all(dict_, session_identity_map)
1194
+
1195
+ if post_load:
1196
+ post_load.add_state(state, True)
1197
+
1198
+ else:
1199
+ # partial population routines, for objects that were already
1200
+ # in the Session, but a row matches them; apply eager loaders
1201
+ # on existing objects, etc.
1202
+ unloaded = state.unloaded
1203
+ isnew = state not in context.partials
1204
+
1205
+ if not isnew or unloaded or populators["eager"]:
1206
+ # state is having a partial set of its attributes
1207
+ # refreshed. Populate those attributes,
1208
+ # and add to the "context.partials" collection.
1209
+
1210
+ to_load = _populate_partial(
1211
+ context,
1212
+ row,
1213
+ state,
1214
+ dict_,
1215
+ isnew,
1216
+ load_path,
1217
+ unloaded,
1218
+ populators,
1219
+ )
1220
+
1221
+ if isnew:
1222
+ if refresh_evt:
1223
+ existing_runid = state.runid
1224
+ state.manager.dispatch.refresh(state, context, to_load)
1225
+ if state.runid != existing_runid:
1226
+ _warn_for_runid_changed(state)
1227
+
1228
+ state._commit(dict_, to_load)
1229
+
1230
+ if post_load and context.invoke_all_eagers:
1231
+ post_load.add_state(state, False)
1232
+
1233
+ return instance
1234
+
1235
+ if mapper.polymorphic_map and not _polymorphic_from and not refresh_state:
1236
+ # if we are doing polymorphic, dispatch to a different _instance()
1237
+ # method specific to the subclass mapper
1238
+ def ensure_no_pk(row):
1239
+ identitykey = (
1240
+ identity_class,
1241
+ primary_key_getter(row),
1242
+ identity_token,
1243
+ )
1244
+ if not is_not_primary_key(identitykey[1]):
1245
+ return identitykey
1246
+ else:
1247
+ return None
1248
+
1249
+ _instance = _decorate_polymorphic_switch(
1250
+ _instance,
1251
+ context,
1252
+ query_entity,
1253
+ mapper,
1254
+ result,
1255
+ path,
1256
+ polymorphic_discriminator,
1257
+ adapter,
1258
+ ensure_no_pk,
1259
+ )
1260
+
1261
+ return _instance
1262
+
1263
+
1264
+ def _load_subclass_via_in(
1265
+ context, path, entity, polymorphic_from, option_entities
1266
+ ):
1267
+ mapper = entity.mapper
1268
+
1269
+ # TODO: polymorphic_from seems to be a Mapper in all cases.
1270
+ # this is likely not needed, but as we dont have typing in loading.py
1271
+ # yet, err on the safe side
1272
+ polymorphic_from_mapper = polymorphic_from.mapper
1273
+ not_against_basemost = polymorphic_from_mapper.inherits is not None
1274
+
1275
+ zero_idx = len(mapper.base_mapper.primary_key) == 1
1276
+
1277
+ if entity.is_aliased_class or not_against_basemost:
1278
+ q, enable_opt, disable_opt = mapper._subclass_load_via_in(
1279
+ entity, polymorphic_from
1280
+ )
1281
+ else:
1282
+ q, enable_opt, disable_opt = mapper._subclass_load_via_in_mapper
1283
+
1284
+ def do_load(context, path, states, load_only, effective_entity):
1285
+ if not option_entities:
1286
+ # filter out states for those that would have selectinloaded
1287
+ # from another loader
1288
+ # TODO: we are currently ignoring the case where the
1289
+ # "selectin_polymorphic" option is used, as this is much more
1290
+ # complex / specific / very uncommon API use
1291
+ states = [
1292
+ (s, v)
1293
+ for s, v in states
1294
+ if s.mapper._would_selectin_load_only_from_given_mapper(mapper)
1295
+ ]
1296
+
1297
+ if not states:
1298
+ return
1299
+
1300
+ orig_query = context.query
1301
+
1302
+ if path.parent:
1303
+ enable_opt_lcl = enable_opt._prepend_path(path)
1304
+ disable_opt_lcl = disable_opt._prepend_path(path)
1305
+ else:
1306
+ enable_opt_lcl = enable_opt
1307
+ disable_opt_lcl = disable_opt
1308
+ options = (
1309
+ (enable_opt_lcl,) + orig_query._with_options + (disable_opt_lcl,)
1310
+ )
1311
+
1312
+ q2 = q.options(*options)
1313
+
1314
+ q2._compile_options = context.compile_state.default_compile_options
1315
+ q2._compile_options += {"_current_path": path.parent}
1316
+
1317
+ if context.populate_existing:
1318
+ q2 = q2.execution_options(populate_existing=True)
1319
+
1320
+ while states:
1321
+ chunk = states[0 : _SelectInLoader._chunksize]
1322
+ states = states[_SelectInLoader._chunksize :]
1323
+ context.session.execute(
1324
+ q2,
1325
+ dict(
1326
+ primary_keys=[
1327
+ state.key[1][0] if zero_idx else state.key[1]
1328
+ for state, load_attrs in chunk
1329
+ ]
1330
+ ),
1331
+ ).unique().scalars().all()
1332
+
1333
+ return do_load
1334
+
1335
+
1336
+ def _populate_full(
1337
+ context,
1338
+ row,
1339
+ state,
1340
+ dict_,
1341
+ isnew,
1342
+ load_path,
1343
+ loaded_instance,
1344
+ populate_existing,
1345
+ populators,
1346
+ ):
1347
+ if isnew:
1348
+ # first time we are seeing a row with this identity.
1349
+ state.runid = context.runid
1350
+
1351
+ for key, getter in populators["quick"]:
1352
+ dict_[key] = getter(row)
1353
+ if populate_existing:
1354
+ for key, set_callable in populators["expire"]:
1355
+ dict_.pop(key, None)
1356
+ if set_callable:
1357
+ state.expired_attributes.add(key)
1358
+ else:
1359
+ for key, set_callable in populators["expire"]:
1360
+ if set_callable:
1361
+ state.expired_attributes.add(key)
1362
+
1363
+ for key, populator in populators["new"]:
1364
+ populator(state, dict_, row)
1365
+
1366
+ elif load_path != state.load_path:
1367
+ # new load path, e.g. object is present in more than one
1368
+ # column position in a series of rows.
1369
+ #
1370
+ # the shallowest path wins. state.load_path is paired with
1371
+ # state.load_options and the two are replayed together when the
1372
+ # object is later refreshed or unexpired; taking whichever path
1373
+ # happened to be processed last made that replay depend on column
1374
+ # order within the row, row order within the result, and which
1375
+ # eager loader style was in use. the shallowest path is both
1376
+ # deterministic and the most conservative choice, as a deeper
1377
+ # path matches loader options that were registered for some other
1378
+ # occurrence of this entity. See #13507.
1379
+ #
1380
+ # only move the path if the current load is the one that stamped
1381
+ # it; the condition here mirrors the one in _instance_processor()
1382
+ # that assigns load_path / load_options together. when
1383
+ # populate_existing is in effect with no propagated options, the
1384
+ # path in place belongs to a previous load and is left alone.
1385
+ if len(load_path) < len(state.load_path) and (
1386
+ context.propagated_loader_options or not populate_existing
1387
+ ):
1388
+ state.load_path = load_path
1389
+
1390
+ # if we have data, and the data isn't in the dict, OK, let's put
1391
+ # it in.
1392
+ for key, getter in populators["quick"]:
1393
+ if key not in dict_:
1394
+ dict_[key] = getter(row)
1395
+
1396
+ # otherwise treat like an "already seen" row
1397
+ for key, populator in populators["existing"]:
1398
+ populator(state, dict_, row)
1399
+ # TODO: allow "existing" populator to know this is
1400
+ # a new path for the state:
1401
+ # populator(state, dict_, row, new_path=True)
1402
+
1403
+ else:
1404
+ # have already seen rows with this identity in this same path.
1405
+ for key, populator in populators["existing"]:
1406
+ populator(state, dict_, row)
1407
+
1408
+ # TODO: same path
1409
+ # populator(state, dict_, row, new_path=False)
1410
+
1411
+
1412
+ def _populate_partial(
1413
+ context, row, state, dict_, isnew, load_path, unloaded, populators
1414
+ ):
1415
+ if not isnew:
1416
+ if unloaded:
1417
+ # extra pass, see #8166
1418
+ for key, getter in populators["quick"]:
1419
+ if key in unloaded:
1420
+ dict_[key] = getter(row)
1421
+
1422
+ to_load = context.partials[state]
1423
+ for key, populator in populators["existing"]:
1424
+ if key in to_load:
1425
+ populator(state, dict_, row)
1426
+ else:
1427
+ to_load = unloaded
1428
+ context.partials[state] = to_load
1429
+
1430
+ for key, getter in populators["quick"]:
1431
+ if key in to_load:
1432
+ dict_[key] = getter(row)
1433
+ for key, set_callable in populators["expire"]:
1434
+ if key in to_load:
1435
+ dict_.pop(key, None)
1436
+ if set_callable:
1437
+ state.expired_attributes.add(key)
1438
+ for key, populator in populators["new"]:
1439
+ if key in to_load:
1440
+ populator(state, dict_, row)
1441
+
1442
+ for key, populator in populators["eager"]:
1443
+ if key not in unloaded:
1444
+ populator(state, dict_, row)
1445
+
1446
+ return to_load
1447
+
1448
+
1449
+ def _validate_version_id(mapper, state, dict_, row, getter):
1450
+ if mapper._get_state_attr_by_column(
1451
+ state, dict_, mapper.version_id_col
1452
+ ) != getter(row):
1453
+ raise orm_exc.StaleDataError(
1454
+ "Instance '%s' has version id '%s' which "
1455
+ "does not match database-loaded version id '%s'."
1456
+ % (
1457
+ state_str(state),
1458
+ mapper._get_state_attr_by_column(
1459
+ state, dict_, mapper.version_id_col
1460
+ ),
1461
+ getter(row),
1462
+ )
1463
+ )
1464
+
1465
+
1466
+ def _decorate_polymorphic_switch(
1467
+ instance_fn,
1468
+ context,
1469
+ query_entity,
1470
+ mapper,
1471
+ result,
1472
+ path,
1473
+ polymorphic_discriminator,
1474
+ adapter,
1475
+ ensure_no_pk,
1476
+ ):
1477
+ if polymorphic_discriminator is not None:
1478
+ polymorphic_on = polymorphic_discriminator
1479
+ else:
1480
+ polymorphic_on = mapper.polymorphic_on
1481
+ if polymorphic_on is None:
1482
+ return instance_fn
1483
+
1484
+ if adapter:
1485
+ polymorphic_on = adapter.columns[polymorphic_on]
1486
+
1487
+ def configure_subclass_mapper(discriminator):
1488
+ try:
1489
+ sub_mapper = mapper.polymorphic_map[discriminator]
1490
+ except KeyError:
1491
+ raise AssertionError(
1492
+ "No such polymorphic_identity %r is defined" % discriminator
1493
+ )
1494
+ else:
1495
+ if sub_mapper is mapper:
1496
+ return None
1497
+ elif not sub_mapper.isa(mapper):
1498
+ return False
1499
+
1500
+ return _instance_processor(
1501
+ query_entity,
1502
+ sub_mapper,
1503
+ context,
1504
+ result,
1505
+ path,
1506
+ adapter,
1507
+ _polymorphic_from=mapper,
1508
+ )
1509
+
1510
+ polymorphic_instances = util.PopulateDict(configure_subclass_mapper)
1511
+
1512
+ getter = result._getter(polymorphic_on)
1513
+
1514
+ def polymorphic_instance(row):
1515
+ discriminator = getter(row)
1516
+ if discriminator is not None:
1517
+ _instance = polymorphic_instances[discriminator]
1518
+ if _instance:
1519
+ return _instance(row)
1520
+ elif _instance is False:
1521
+ identitykey = ensure_no_pk(row)
1522
+
1523
+ if identitykey:
1524
+ raise sa_exc.InvalidRequestError(
1525
+ "Row with identity key %s can't be loaded into an "
1526
+ "object; the polymorphic discriminator column '%s' "
1527
+ "refers to %s, which is not a sub-mapper of "
1528
+ "the requested %s"
1529
+ % (
1530
+ identitykey,
1531
+ polymorphic_on,
1532
+ mapper.polymorphic_map[discriminator],
1533
+ mapper,
1534
+ )
1535
+ )
1536
+ else:
1537
+ return None
1538
+ else:
1539
+ return instance_fn(row)
1540
+ else:
1541
+ identitykey = ensure_no_pk(row)
1542
+
1543
+ if identitykey:
1544
+ raise sa_exc.InvalidRequestError(
1545
+ "Row with identity key %s can't be loaded into an "
1546
+ "object; the polymorphic discriminator column '%s' is "
1547
+ "NULL" % (identitykey, polymorphic_on)
1548
+ )
1549
+ else:
1550
+ return None
1551
+
1552
+ return polymorphic_instance
1553
+
1554
+
1555
+ class _PostLoad:
1556
+ """Track loaders and states for "post load" operations."""
1557
+
1558
+ __slots__ = "loaders", "states", "load_keys"
1559
+
1560
+ def __init__(self):
1561
+ self.loaders = {}
1562
+ self.states = util.OrderedDict()
1563
+ self.load_keys = None
1564
+
1565
+ def add_state(self, state, overwrite):
1566
+ # the states for a polymorphic load here are all shared
1567
+ # within a single PostLoad object among multiple subtypes.
1568
+ # Filtering of callables on a per-subclass basis needs to be done at
1569
+ # the invocation level
1570
+ self.states[state] = overwrite
1571
+
1572
+ def invoke(self, context, path):
1573
+ if not self.states:
1574
+ return
1575
+ path = path_registry.PathRegistry.coerce(path)
1576
+ for (
1577
+ effective_context,
1578
+ token,
1579
+ limit_to_mapper,
1580
+ loader,
1581
+ arg,
1582
+ kw,
1583
+ ) in self.loaders.values():
1584
+ states = [
1585
+ (state, overwrite)
1586
+ for state, overwrite in self.states.items()
1587
+ if state.manager.mapper.isa(limit_to_mapper)
1588
+ ]
1589
+ if states:
1590
+ loader(
1591
+ effective_context, path, states, self.load_keys, *arg, **kw
1592
+ )
1593
+ self.states.clear()
1594
+
1595
+ @classmethod
1596
+ def for_context(cls, context, path, only_load_props):
1597
+ pl = context.post_load_paths.get(path.path)
1598
+ if pl is not None and only_load_props:
1599
+ pl.load_keys = only_load_props
1600
+ return pl
1601
+
1602
+ @classmethod
1603
+ def path_exists(self, context, path, key):
1604
+ return (
1605
+ path.path in context.post_load_paths
1606
+ and key in context.post_load_paths[path.path].loaders
1607
+ )
1608
+
1609
+ @classmethod
1610
+ def callable_for_path(
1611
+ cls, context, path, limit_to_mapper, token, loader_callable, *arg, **kw
1612
+ ):
1613
+ if path.path in context.post_load_paths:
1614
+ pl = context.post_load_paths[path.path]
1615
+ else:
1616
+ pl = context.post_load_paths[path.path] = _PostLoad()
1617
+ pl.loaders[token] = (
1618
+ context,
1619
+ token,
1620
+ limit_to_mapper,
1621
+ loader_callable,
1622
+ arg,
1623
+ kw,
1624
+ )
1625
+
1626
+
1627
+ def _load_scalar_attributes(mapper, state, attribute_names, passive):
1628
+ """initiate a column-based attribute refresh operation."""
1629
+
1630
+ # assert mapper is _state_mapper(state)
1631
+ session = state.session
1632
+ if not session:
1633
+ raise orm_exc.DetachedInstanceError(
1634
+ "Instance %s is not bound to a Session; "
1635
+ "attribute refresh operation cannot proceed" % (state_str(state))
1636
+ )
1637
+
1638
+ no_autoflush = bool(passive & attributes.NO_AUTOFLUSH)
1639
+
1640
+ # in the case of inheritance, particularly concrete and abstract
1641
+ # concrete inheritance, the class manager might have some keys
1642
+ # of attributes on the superclass that we didn't actually map.
1643
+ # These could be mapped as "concrete, don't load" or could be completely
1644
+ # excluded from the mapping and we know nothing about them. Filter them
1645
+ # here to prevent them from coming through.
1646
+ if attribute_names:
1647
+ attribute_names = attribute_names.intersection(mapper.attrs.keys())
1648
+
1649
+ if mapper.inherits and not mapper.concrete:
1650
+ # load based on committed attributes in the object, formed into
1651
+ # a truncated SELECT that only includes relevant tables. does not
1652
+ # currently use state.key
1653
+ statement = mapper._optimized_get_statement(state, attribute_names)
1654
+ if statement is not None:
1655
+ # undefer() isn't needed here because statement has the
1656
+ # columns needed already, this implicitly undefers that column
1657
+ stmt = FromStatement(mapper, statement)
1658
+
1659
+ return _load_on_ident(
1660
+ session,
1661
+ stmt,
1662
+ None,
1663
+ only_load_props=attribute_names,
1664
+ refresh_state=state,
1665
+ no_autoflush=no_autoflush,
1666
+ )
1667
+
1668
+ # normal load, use state.key as the identity to SELECT
1669
+ has_key = bool(state.key)
1670
+
1671
+ if has_key:
1672
+ identity_key = state.key
1673
+ else:
1674
+ # this codepath is rare - only valid when inside a flush, and the
1675
+ # object is becoming persistent but hasn't yet been assigned
1676
+ # an identity_key.
1677
+ # check here to ensure we have the attrs we need.
1678
+ pk_attrs = [
1679
+ mapper._columntoproperty[col].key for col in mapper.primary_key
1680
+ ]
1681
+ if state.expired_attributes.intersection(pk_attrs):
1682
+ raise sa_exc.InvalidRequestError(
1683
+ "Instance %s cannot be refreshed - it's not "
1684
+ " persistent and does not "
1685
+ "contain a full primary key." % state_str(state)
1686
+ )
1687
+ identity_key = mapper._identity_key_from_state(state)
1688
+
1689
+ if (
1690
+ _none_set.issubset(identity_key) and not mapper.allow_partial_pks
1691
+ ) or _none_set.issuperset(identity_key):
1692
+ util.warn_limited(
1693
+ "Instance %s to be refreshed doesn't "
1694
+ "contain a full primary key - can't be refreshed "
1695
+ "(and shouldn't be expired, either).",
1696
+ state_str(state),
1697
+ )
1698
+ return
1699
+
1700
+ result = _load_on_ident(
1701
+ session,
1702
+ select(mapper),
1703
+ identity_key,
1704
+ refresh_state=state,
1705
+ only_load_props=attribute_names,
1706
+ no_autoflush=no_autoflush,
1707
+ )
1708
+
1709
+ # if instance is pending, a refresh operation
1710
+ # may not complete (even if PK attributes are assigned)
1711
+ if has_key and result is None:
1712
+ raise orm_exc.ObjectDeletedError(state)