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,915 @@
1
+ # sql/cache_key.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
+
8
+ from __future__ import annotations
9
+
10
+ from itertools import zip_longest
11
+ import typing
12
+ from typing import Any
13
+ from typing import cast
14
+ from typing import Dict
15
+ from typing import Final
16
+ from typing import Iterable
17
+ from typing import Iterator
18
+ from typing import List
19
+ from typing import Literal
20
+ from typing import MutableMapping
21
+ from typing import NamedTuple
22
+ from typing import Optional
23
+ from typing import Protocol
24
+ from typing import Sequence
25
+ from typing import Tuple
26
+ from typing import Type
27
+ from typing import Union
28
+
29
+ from . import _cache_key_cy
30
+ from ._cache_key_cy import CacheConst as CacheConst
31
+ from ._cache_key_cy import CacheTraverseTarget as CacheTraverseTarget
32
+ from .visitors import anon_map
33
+ from .visitors import HasTraversalDispatch
34
+ from .visitors import HasTraverseInternals
35
+ from .visitors import InternalTraversal
36
+ from .visitors import prefix_anon_map
37
+ from .. import util
38
+ from ..inspection import inspect
39
+ from ..util import HasMemoized
40
+
41
+ if typing.TYPE_CHECKING:
42
+ from .elements import BindParameter
43
+ from .elements import ClauseElement
44
+ from .elements import ColumnElement
45
+ from .visitors import _TraverseInternalsType
46
+ from ..engine.interfaces import _CoreSingleExecuteParams
47
+
48
+
49
+ class _CacheKeyTraversalDispatchType(Protocol):
50
+ def __call__(
51
+ s,
52
+ self: HasCacheKey,
53
+ anon_map: anon_map,
54
+ bindparams: List[BindParameter[Any]],
55
+ id_: int,
56
+ cls: Type[HasCacheKey],
57
+ ) -> Optional[Tuple[Any, ...]]: ...
58
+
59
+
60
+ NO_CACHE: Final = CacheConst.NO_CACHE
61
+
62
+ _CacheKeyTraversalType = Union[
63
+ "_TraverseInternalsType", Literal[CacheConst.NO_CACHE], Literal[None]
64
+ ]
65
+
66
+
67
+ CACHE_IN_PLACE: Final = CacheTraverseTarget.CACHE_IN_PLACE
68
+ CALL_GEN_CACHE_KEY: Final = CacheTraverseTarget.CALL_GEN_CACHE_KEY
69
+ STATIC_CACHE_KEY: Final = CacheTraverseTarget.STATIC_CACHE_KEY
70
+ PROPAGATE_ATTRS: Final = CacheTraverseTarget.PROPAGATE_ATTRS
71
+ ANON_NAME: Final = CacheTraverseTarget.ANON_NAME
72
+
73
+
74
+ class HasCacheKey(_cache_key_cy.BaseHasCacheKey):
75
+ """Mixin for objects which can produce a cache key.
76
+
77
+ This class is usually in a hierarchy that starts with the
78
+ :class:`.HasTraverseInternals` base, but this is optional. Currently,
79
+ the class should be able to work on its own without including
80
+ :class:`.HasTraverseInternals`.
81
+
82
+ .. seealso::
83
+
84
+ :class:`.CacheKey`
85
+
86
+ :ref:`sql_caching`
87
+
88
+ """
89
+
90
+ __slots__ = ()
91
+
92
+ _cache_key_traversal: _CacheKeyTraversalType = NO_CACHE
93
+
94
+ _is_has_cache_key = True
95
+
96
+ _hierarchy_supports_caching = True
97
+ """private attribute which may be set to False to prevent the
98
+ inherit_cache warning from being emitted for a hierarchy of subclasses.
99
+
100
+ Currently applies to the :class:`.ExecutableDDLElement` hierarchy which
101
+ does not implement caching.
102
+
103
+ """
104
+
105
+ inherit_cache: Optional[bool] = None
106
+ """Indicate if this :class:`.HasCacheKey` instance should make use of the
107
+ cache key generation scheme used by its immediate superclass.
108
+
109
+ The attribute defaults to ``None``, which indicates that a construct has
110
+ not yet taken into account whether or not its appropriate for it to
111
+ participate in caching; this is functionally equivalent to setting the
112
+ value to ``False``, except that a warning is also emitted.
113
+
114
+ This flag can be set to ``True`` on a particular class, if the SQL that
115
+ corresponds to the object does not change based on attributes which
116
+ are local to this class, and not its superclass.
117
+
118
+ .. seealso::
119
+
120
+ :ref:`compilerext_caching` - General guideslines for setting the
121
+ :attr:`.HasCacheKey.inherit_cache` attribute for third-party or user
122
+ defined SQL constructs.
123
+
124
+ """
125
+
126
+ __slots__ = ()
127
+
128
+ _generated_cache_key_traversal: Any
129
+
130
+ @classmethod
131
+ def _generate_cache_attrs(
132
+ cls,
133
+ ) -> Union[_CacheKeyTraversalDispatchType, Literal[CacheConst.NO_CACHE]]:
134
+ """generate cache key dispatcher for a new class.
135
+
136
+ This sets the _generated_cache_key_traversal attribute once called
137
+ so should only be called once per class.
138
+
139
+ """
140
+ inherit_cache = cls.__dict__.get("inherit_cache", None)
141
+ inherit = bool(inherit_cache)
142
+
143
+ if inherit:
144
+ _cache_key_traversal = getattr(cls, "_cache_key_traversal", None)
145
+ if _cache_key_traversal is None:
146
+ try:
147
+ assert issubclass(cls, HasTraverseInternals)
148
+ _cache_key_traversal = cls._traverse_internals
149
+ except AttributeError:
150
+ cls._generated_cache_key_traversal = NO_CACHE
151
+ return NO_CACHE
152
+
153
+ assert _cache_key_traversal is not NO_CACHE, (
154
+ f"class {cls} has _cache_key_traversal=NO_CACHE, "
155
+ "which conflicts with inherit_cache=True"
156
+ )
157
+
158
+ # TODO: wouldn't we instead get this from our superclass?
159
+ # also, our superclass may not have this yet, but in any case,
160
+ # we'd generate for the superclass that has it. this is a little
161
+ # more complicated, so for the moment this is a little less
162
+ # efficient on startup but simpler.
163
+ return cast(
164
+ _CacheKeyTraversalDispatchType,
165
+ _cache_key_traversal_visitor.generate_class_attrs(
166
+ cls,
167
+ _cache_key_traversal,
168
+ "_generated_cache_key_traversal",
169
+ ),
170
+ )
171
+ else:
172
+ _cache_key_traversal = cls.__dict__.get(
173
+ "_cache_key_traversal", None
174
+ )
175
+ if _cache_key_traversal is None:
176
+ _cache_key_traversal = cls.__dict__.get(
177
+ "_traverse_internals", None
178
+ )
179
+ if _cache_key_traversal is None:
180
+ cls._generated_cache_key_traversal = NO_CACHE
181
+ if (
182
+ inherit_cache is None
183
+ and cls._hierarchy_supports_caching
184
+ ):
185
+ util.warn(
186
+ "Class %s will not make use of SQL compilation "
187
+ "caching as it does not set the 'inherit_cache' "
188
+ "attribute to ``True``. This can have "
189
+ "significant performance implications including "
190
+ "some performance degradations in comparison to "
191
+ "prior SQLAlchemy versions. Set this attribute "
192
+ "to True if this object can make use of the cache "
193
+ "key generated by the superclass. Alternatively, "
194
+ "this attribute may be set to False which will "
195
+ "disable this warning." % (cls.__name__),
196
+ code="cprf",
197
+ )
198
+ return NO_CACHE
199
+
200
+ return cast(
201
+ _CacheKeyTraversalDispatchType,
202
+ _cache_key_traversal_visitor.generate_class_attrs(
203
+ cls,
204
+ _cache_key_traversal,
205
+ "_generated_cache_key_traversal",
206
+ ),
207
+ )
208
+
209
+ def _generate_cache_key(self) -> Optional[CacheKey]:
210
+ """return a cache key.
211
+
212
+ The cache key is a tuple which can contain any series of
213
+ objects that are hashable and also identifies
214
+ this object uniquely within the presence of a larger SQL expression
215
+ or statement, for the purposes of caching the resulting query.
216
+
217
+ The cache key should be based on the SQL compiled structure that would
218
+ ultimately be produced. That is, two structures that are composed in
219
+ exactly the same way should produce the same cache key; any difference
220
+ in the structures that would affect the SQL string or the type handlers
221
+ should result in a different cache key.
222
+
223
+ The cache key returned by this method is an instance of
224
+ :class:`.CacheKey`, which consists of a tuple representing the
225
+ cache key, as well as a list of :class:`.BindParameter` objects
226
+ which are extracted from the expression. While two expressions
227
+ that produce identical cache key tuples will themselves generate
228
+ identical SQL strings, the list of :class:`.BindParameter` objects
229
+ indicates the bound values which may have different values in
230
+ each one; these bound parameters must be consulted in order to
231
+ execute the statement with the correct parameters.
232
+
233
+ a :class:`_expression.ClauseElement` structure that does not implement
234
+ a :meth:`._gen_cache_key` method and does not implement a
235
+ :attr:`.traverse_internals` attribute will not be cacheable; when
236
+ such an element is embedded into a larger structure, this method
237
+ will return None, indicating no cache key is available.
238
+
239
+ """
240
+
241
+ bindparams: List[BindParameter[Any]] = []
242
+
243
+ _anon_map = anon_map()
244
+ key = self._gen_cache_key(_anon_map, bindparams)
245
+ if NO_CACHE in _anon_map:
246
+ return None
247
+ else:
248
+ assert key is not None
249
+ return CacheKey(
250
+ key,
251
+ bindparams,
252
+ _anon_map.get(CacheConst.PARAMS), # type: ignore[arg-type]
253
+ )
254
+
255
+
256
+ class HasCacheKeyTraverse(HasTraverseInternals, HasCacheKey):
257
+ pass
258
+
259
+
260
+ class MemoizedHasCacheKey(HasCacheKey, HasMemoized):
261
+ __slots__ = ()
262
+
263
+ @HasMemoized.memoized_instancemethod
264
+ def _generate_cache_key(self) -> Optional[CacheKey]:
265
+ return HasCacheKey._generate_cache_key(self)
266
+
267
+
268
+ class SlotsMemoizedHasCacheKey(HasCacheKey, util.MemoizedSlots):
269
+ __slots__ = ()
270
+
271
+ def _memoized_method__generate_cache_key(self) -> Optional[CacheKey]:
272
+ return HasCacheKey._generate_cache_key(self)
273
+
274
+
275
+ class CacheKey(NamedTuple):
276
+ """The key used to identify a SQL statement construct in the
277
+ SQL compilation cache.
278
+
279
+ .. seealso::
280
+
281
+ :ref:`sql_caching`
282
+
283
+ """
284
+
285
+ key: Tuple[Any, ...]
286
+ bindparams: Sequence[BindParameter[Any]]
287
+ params: _CoreSingleExecuteParams | None
288
+
289
+ # can't set __hash__ attribute because it interferes
290
+ # with namedtuple
291
+ # can't use "if not TYPE_CHECKING" because mypy rejects it
292
+ # inside of a NamedTuple
293
+ def __hash__(self) -> Optional[int]: # type: ignore[override]
294
+ """CacheKey itself is not hashable - hash the .key portion"""
295
+ return None
296
+
297
+ def to_offline_string(
298
+ self,
299
+ statement_cache: MutableMapping[Any, str],
300
+ statement: ClauseElement,
301
+ parameters: _CoreSingleExecuteParams,
302
+ ) -> str:
303
+ """Generate an "offline string" form of this :class:`.CacheKey`
304
+
305
+ The "offline string" is basically the string SQL for the
306
+ statement plus a repr of the bound parameter values in series.
307
+ Whereas the :class:`.CacheKey` object is dependent on in-memory
308
+ identities in order to work as a cache key, the "offline" version
309
+ is suitable for a cache that will work for other processes as well.
310
+
311
+ The given ``statement_cache`` is a dictionary-like object where the
312
+ string form of the statement itself will be cached. This dictionary
313
+ should be in a longer lived scope in order to reduce the time spent
314
+ stringifying statements.
315
+
316
+
317
+ """
318
+ if self.key not in statement_cache:
319
+ statement_cache[self.key] = sql_str = str(statement)
320
+ else:
321
+ sql_str = statement_cache[self.key]
322
+
323
+ if not self.bindparams:
324
+ param_tuple = tuple(parameters[key] for key in sorted(parameters))
325
+ else:
326
+ param_tuple = tuple(
327
+ parameters.get(bindparam.key, bindparam.value)
328
+ for bindparam in self.bindparams
329
+ )
330
+
331
+ return repr((sql_str, param_tuple))
332
+
333
+ def __eq__(self, other: Any) -> bool:
334
+ return other is not None and bool(self.key == other.key)
335
+
336
+ def __ne__(self, other: Any) -> bool:
337
+ return other is None or not (self.key == other.key)
338
+
339
+ @classmethod
340
+ def _diff_tuples(cls, left: CacheKey, right: CacheKey) -> str:
341
+ ck1 = CacheKey(left, [], None)
342
+ ck2 = CacheKey(right, [], None)
343
+ return ck1._diff(ck2)
344
+
345
+ def _whats_different(self, other: CacheKey) -> Iterator[str]:
346
+ k1 = self.key
347
+ k2 = other.key
348
+
349
+ stack: List[int] = []
350
+ pickup_index = 0
351
+ while True:
352
+ s1, s2 = k1, k2
353
+ for idx in stack:
354
+ s1 = s1[idx]
355
+ s2 = s2[idx]
356
+
357
+ for idx, (e1, e2) in enumerate(zip_longest(s1, s2)):
358
+ if idx < pickup_index:
359
+ continue
360
+ if e1 != e2:
361
+ if isinstance(e1, tuple) and isinstance(e2, tuple):
362
+ stack.append(idx)
363
+ break
364
+ else:
365
+ yield "key%s[%d]: %s != %s" % (
366
+ "".join("[%d]" % id_ for id_ in stack),
367
+ idx,
368
+ e1,
369
+ e2,
370
+ )
371
+ else:
372
+ stack.pop(-1)
373
+ break
374
+
375
+ def _diff(self, other: CacheKey) -> str:
376
+ return ", ".join(self._whats_different(other))
377
+
378
+ def __str__(self) -> str:
379
+ stack: List[Union[Tuple[Any, ...], HasCacheKey]] = [self.key]
380
+
381
+ output = []
382
+ sentinel = object()
383
+ indent = -1
384
+ while stack:
385
+ elem = stack.pop(0)
386
+ if elem is sentinel:
387
+ output.append((" " * (indent * 2)) + "),")
388
+ indent -= 1
389
+ elif isinstance(elem, tuple):
390
+ if not elem:
391
+ output.append((" " * ((indent + 1) * 2)) + "()")
392
+ else:
393
+ indent += 1
394
+ stack = list(elem) + [sentinel] + stack
395
+ output.append((" " * (indent * 2)) + "(")
396
+ else:
397
+ if isinstance(elem, HasCacheKey):
398
+ repr_ = "<%s object at %s>" % (
399
+ type(elem).__name__,
400
+ hex(id(elem)),
401
+ )
402
+ else:
403
+ repr_ = repr(elem)
404
+ output.append((" " * (indent * 2)) + " " + repr_ + ", ")
405
+
406
+ return "CacheKey(key=%s)" % ("\n".join(output),)
407
+
408
+ def _generate_param_dict(self) -> Dict[str, Any]:
409
+ """used for testing"""
410
+
411
+ _anon_map = prefix_anon_map()
412
+ return {b.key % _anon_map: b.effective_value for b in self.bindparams}
413
+
414
+ @util.preload_module("sqlalchemy.sql.elements")
415
+ def _apply_params_to_element(
416
+ self, original_cache_key: CacheKey, target_element: ColumnElement[Any]
417
+ ) -> ColumnElement[Any]:
418
+ if target_element._is_immutable or original_cache_key is self:
419
+ return target_element
420
+
421
+ elements = util.preloaded.sql_elements
422
+ return elements._OverrideBinds(
423
+ target_element, self.bindparams, original_cache_key.bindparams
424
+ )
425
+
426
+
427
+ def _ad_hoc_cache_key_from_args(
428
+ tokens: Tuple[Any, ...],
429
+ traverse_args: Iterable[Tuple[str, InternalTraversal]],
430
+ args: Iterable[Any],
431
+ ) -> Tuple[Any, ...]:
432
+ """a quick cache key generator used by reflection.flexi_cache."""
433
+ bindparams: List[BindParameter[Any]] = []
434
+
435
+ _anon_map = anon_map()
436
+
437
+ tup = tokens
438
+
439
+ for (attrname, sym), arg in zip(traverse_args, args):
440
+ key = sym.name
441
+ visit_key = key.replace("dp_", "visit_")
442
+
443
+ if arg is None:
444
+ tup += (attrname, None)
445
+ continue
446
+
447
+ meth = getattr(_cache_key_traversal_visitor, visit_key)
448
+ if meth is CACHE_IN_PLACE:
449
+ tup += (attrname, arg)
450
+ elif meth in (
451
+ CALL_GEN_CACHE_KEY,
452
+ STATIC_CACHE_KEY,
453
+ ANON_NAME,
454
+ PROPAGATE_ATTRS,
455
+ ):
456
+ raise NotImplementedError(
457
+ f"Haven't implemented symbol {meth} for ad-hoc key from args"
458
+ )
459
+ else:
460
+ tup += meth(attrname, arg, None, _anon_map, bindparams)
461
+ return tup
462
+
463
+
464
+ class _CacheKeyTraversal(
465
+ HasTraversalDispatch, _cache_key_cy._BaseCacheKeyTraversal
466
+ ):
467
+ # the dispatch symbols below are resolved at class setup time by
468
+ # _generate_class_attrs(), which records the handler for each attribute
469
+ # as an integer "kind" that the compiled traversal switches on directly;
470
+ # only the symbols that have no such inline handler are left as actual
471
+ # methods on this class
472
+
473
+ visit_has_cache_key = visit_clauseelement = CALL_GEN_CACHE_KEY
474
+ visit_clauseelement_list = InternalTraversal.dp_clauseelement_list
475
+ visit_annotations_key = InternalTraversal.dp_annotations_key
476
+ visit_clauseelement_tuple = InternalTraversal.dp_clauseelement_tuple
477
+ visit_memoized_select_entities = (
478
+ InternalTraversal.dp_memoized_select_entities
479
+ )
480
+
481
+ visit_string = visit_boolean = visit_operator = visit_plain_obj = (
482
+ CACHE_IN_PLACE
483
+ )
484
+ visit_statement_hint_list = CACHE_IN_PLACE
485
+ visit_type = STATIC_CACHE_KEY
486
+ visit_anon_name = ANON_NAME
487
+
488
+ visit_propagate_attrs = PROPAGATE_ATTRS
489
+
490
+ def visit_compile_state_funcs(
491
+ self,
492
+ attrname: str,
493
+ obj: Any,
494
+ parent: Any,
495
+ anon_map: anon_map,
496
+ bindparams: List[BindParameter[Any]],
497
+ ) -> Tuple[Any, ...]:
498
+ return tuple([(fn.__code__, c_key) for fn, c_key in obj])
499
+
500
+ def visit_inspectable(
501
+ self,
502
+ attrname: str,
503
+ obj: Any,
504
+ parent: Any,
505
+ anon_map: anon_map,
506
+ bindparams: List[BindParameter[Any]],
507
+ ) -> Tuple[Any, ...]:
508
+ return (attrname, inspect(obj)._gen_cache_key(anon_map, bindparams))
509
+
510
+ def visit_string_list(
511
+ self,
512
+ attrname: str,
513
+ obj: Any,
514
+ parent: Any,
515
+ anon_map: anon_map,
516
+ bindparams: List[BindParameter[Any]],
517
+ ) -> Tuple[Any, ...]:
518
+ return tuple(obj)
519
+
520
+ def visit_multi(
521
+ self,
522
+ attrname: str,
523
+ obj: Any,
524
+ parent: Any,
525
+ anon_map: anon_map,
526
+ bindparams: List[BindParameter[Any]],
527
+ ) -> Tuple[Any, ...]:
528
+ return (
529
+ attrname,
530
+ (
531
+ obj._gen_cache_key(anon_map, bindparams)
532
+ if isinstance(obj, HasCacheKey)
533
+ else obj
534
+ ),
535
+ )
536
+
537
+ def visit_multi_list(
538
+ self,
539
+ attrname: str,
540
+ obj: Any,
541
+ parent: Any,
542
+ anon_map: anon_map,
543
+ bindparams: List[BindParameter[Any]],
544
+ ) -> Tuple[Any, ...]:
545
+ return (
546
+ attrname,
547
+ tuple(
548
+ [
549
+ (
550
+ elem._gen_cache_key(anon_map, bindparams)
551
+ if isinstance(elem, HasCacheKey)
552
+ else elem
553
+ )
554
+ for elem in obj
555
+ ]
556
+ ),
557
+ )
558
+
559
+ def visit_has_cache_key_tuples(
560
+ self,
561
+ attrname: str,
562
+ obj: Any,
563
+ parent: Any,
564
+ anon_map: anon_map,
565
+ bindparams: List[BindParameter[Any]],
566
+ ) -> Tuple[Any, ...]:
567
+ return (
568
+ attrname,
569
+ tuple(
570
+ [
571
+ tuple(
572
+ [
573
+ elem._gen_cache_key(anon_map, bindparams)
574
+ for elem in tup_elem
575
+ ]
576
+ )
577
+ for tup_elem in obj
578
+ ]
579
+ ),
580
+ )
581
+
582
+ def visit_has_cache_key_list(
583
+ self,
584
+ attrname: str,
585
+ obj: Any,
586
+ parent: Any,
587
+ anon_map: anon_map,
588
+ bindparams: List[BindParameter[Any]],
589
+ ) -> Tuple[Any, ...]:
590
+ return (
591
+ attrname,
592
+ tuple([elem._gen_cache_key(anon_map, bindparams) for elem in obj]),
593
+ )
594
+
595
+ def visit_executable_options(
596
+ self,
597
+ attrname: str,
598
+ obj: Any,
599
+ parent: Any,
600
+ anon_map: anon_map,
601
+ bindparams: List[BindParameter[Any]],
602
+ ) -> Tuple[Any, ...]:
603
+ return (
604
+ attrname,
605
+ tuple(
606
+ [
607
+ elem._gen_cache_key(anon_map, bindparams)
608
+ for elem in obj
609
+ if elem._is_has_cache_key
610
+ ]
611
+ ),
612
+ )
613
+
614
+ visit_clauseelement_tuples = visit_has_cache_key_tuples
615
+
616
+ def visit_fromclause_ordered_set(
617
+ self,
618
+ attrname: str,
619
+ obj: Any,
620
+ parent: Any,
621
+ anon_map: anon_map,
622
+ bindparams: List[BindParameter[Any]],
623
+ ) -> Tuple[Any, ...]:
624
+ return (
625
+ attrname,
626
+ tuple([elem._gen_cache_key(anon_map, bindparams) for elem in obj]),
627
+ )
628
+
629
+ def visit_clauseelement_unordered_set(
630
+ self,
631
+ attrname: str,
632
+ obj: Any,
633
+ parent: Any,
634
+ anon_map: anon_map,
635
+ bindparams: List[BindParameter[Any]],
636
+ ) -> Tuple[Any, ...]:
637
+ cache_keys = [
638
+ elem._gen_cache_key(anon_map, bindparams) for elem in obj
639
+ ]
640
+ return (
641
+ attrname,
642
+ tuple(
643
+ sorted(cache_keys)
644
+ ), # cache keys all start with (id_, class)
645
+ )
646
+
647
+ def visit_named_ddl_element(
648
+ self,
649
+ attrname: str,
650
+ obj: Any,
651
+ parent: Any,
652
+ anon_map: anon_map,
653
+ bindparams: List[BindParameter[Any]],
654
+ ) -> Tuple[Any, ...]:
655
+ return (attrname, obj.name)
656
+
657
+ def visit_prefix_sequence(
658
+ self,
659
+ attrname: str,
660
+ obj: Any,
661
+ parent: Any,
662
+ anon_map: anon_map,
663
+ bindparams: List[BindParameter[Any]],
664
+ ) -> Tuple[Any, ...]:
665
+ return (
666
+ attrname,
667
+ tuple(
668
+ [
669
+ (clause._gen_cache_key(anon_map, bindparams), strval)
670
+ for clause, strval in obj
671
+ ]
672
+ ),
673
+ )
674
+
675
+ def visit_setup_join_tuple(
676
+ self,
677
+ attrname: str,
678
+ obj: Any,
679
+ parent: Any,
680
+ anon_map: anon_map,
681
+ bindparams: List[BindParameter[Any]],
682
+ ) -> Tuple[Any, ...]:
683
+ return tuple(
684
+ [
685
+ (
686
+ target._gen_cache_key(anon_map, bindparams),
687
+ (
688
+ onclause._gen_cache_key(anon_map, bindparams)
689
+ if onclause is not None
690
+ else None
691
+ ),
692
+ (
693
+ from_._gen_cache_key(anon_map, bindparams)
694
+ if from_ is not None
695
+ else None
696
+ ),
697
+ tuple([(key, flags[key]) for key in sorted(flags)]),
698
+ )
699
+ for (target, onclause, from_, flags) in obj
700
+ ]
701
+ )
702
+
703
+ def visit_table_hint_list(
704
+ self,
705
+ attrname: str,
706
+ obj: Any,
707
+ parent: Any,
708
+ anon_map: anon_map,
709
+ bindparams: List[BindParameter[Any]],
710
+ ) -> Tuple[Any, ...]:
711
+ return (
712
+ attrname,
713
+ tuple(
714
+ [
715
+ (
716
+ clause._gen_cache_key(anon_map, bindparams),
717
+ dialect_name,
718
+ text,
719
+ )
720
+ for (clause, dialect_name), text in obj.items()
721
+ ]
722
+ ),
723
+ )
724
+
725
+ def visit_plain_dict(
726
+ self,
727
+ attrname: str,
728
+ obj: Any,
729
+ parent: Any,
730
+ anon_map: anon_map,
731
+ bindparams: List[BindParameter[Any]],
732
+ ) -> Tuple[Any, ...]:
733
+ return (attrname, tuple([(key, obj[key]) for key in sorted(obj)]))
734
+
735
+ def visit_dialect_options(
736
+ self,
737
+ attrname: str,
738
+ obj: Any,
739
+ parent: Any,
740
+ anon_map: anon_map,
741
+ bindparams: List[BindParameter[Any]],
742
+ ) -> Tuple[Any, ...]:
743
+ return (
744
+ attrname,
745
+ tuple(
746
+ [
747
+ (
748
+ dialect_name,
749
+ tuple(
750
+ [
751
+ (key, obj[dialect_name][key])
752
+ for key in sorted(obj[dialect_name])
753
+ ]
754
+ ),
755
+ )
756
+ for dialect_name in sorted(obj)
757
+ ]
758
+ ),
759
+ )
760
+
761
+ def visit_string_clauseelement_dict(
762
+ self,
763
+ attrname: str,
764
+ obj: Any,
765
+ parent: Any,
766
+ anon_map: anon_map,
767
+ bindparams: List[BindParameter[Any]],
768
+ ) -> Tuple[Any, ...]:
769
+ return (
770
+ attrname,
771
+ tuple(
772
+ [
773
+ (key, obj[key]._gen_cache_key(anon_map, bindparams))
774
+ for key in sorted(obj)
775
+ ]
776
+ ),
777
+ )
778
+
779
+ def visit_string_multi_dict(
780
+ self,
781
+ attrname: str,
782
+ obj: Any,
783
+ parent: Any,
784
+ anon_map: anon_map,
785
+ bindparams: List[BindParameter[Any]],
786
+ ) -> Tuple[Any, ...]:
787
+ return (
788
+ attrname,
789
+ tuple(
790
+ [
791
+ (
792
+ key,
793
+ (
794
+ value._gen_cache_key(anon_map, bindparams)
795
+ if isinstance(value, HasCacheKey)
796
+ else value
797
+ ),
798
+ )
799
+ for key, value in [(key, obj[key]) for key in sorted(obj)]
800
+ ]
801
+ ),
802
+ )
803
+
804
+ def visit_fromclause_canonical_column_collection(
805
+ self,
806
+ attrname: str,
807
+ obj: Any,
808
+ parent: Any,
809
+ anon_map: anon_map,
810
+ bindparams: List[BindParameter[Any]],
811
+ ) -> Tuple[Any, ...]:
812
+ # inlining into the internals of ColumnCollection
813
+ return (
814
+ attrname,
815
+ tuple(
816
+ [
817
+ col._gen_cache_key(anon_map, bindparams)
818
+ for k, col, _ in obj._collection
819
+ ]
820
+ ),
821
+ )
822
+
823
+ def visit_unknown_structure(
824
+ self,
825
+ attrname: str,
826
+ obj: Any,
827
+ parent: Any,
828
+ anon_map: anon_map,
829
+ bindparams: List[BindParameter[Any]],
830
+ ) -> Tuple[Any, ...]:
831
+ anon_map[NO_CACHE] = True
832
+ return ()
833
+
834
+ def visit_dml_ordered_values(
835
+ self,
836
+ attrname: str,
837
+ obj: Any,
838
+ parent: Any,
839
+ anon_map: anon_map,
840
+ bindparams: List[BindParameter[Any]],
841
+ ) -> Tuple[Any, ...]:
842
+ return (
843
+ attrname,
844
+ tuple(
845
+ [
846
+ (
847
+ (
848
+ key._gen_cache_key(anon_map, bindparams)
849
+ if hasattr(key, "__clause_element__")
850
+ else key
851
+ ),
852
+ value._gen_cache_key(anon_map, bindparams),
853
+ )
854
+ for key, value in obj
855
+ ]
856
+ ),
857
+ )
858
+
859
+ def visit_dml_values(
860
+ self,
861
+ attrname: str,
862
+ obj: Any,
863
+ parent: Any,
864
+ anon_map: anon_map,
865
+ bindparams: List[BindParameter[Any]],
866
+ ) -> Tuple[Any, ...]:
867
+ # in py37 we can assume two dictionaries created in the same
868
+ # insert ordering will retain that sorting
869
+ return (
870
+ attrname,
871
+ tuple(
872
+ [
873
+ (
874
+ (
875
+ k._gen_cache_key(anon_map, bindparams)
876
+ if hasattr(k, "__clause_element__")
877
+ else k
878
+ ),
879
+ obj[k]._gen_cache_key(anon_map, bindparams),
880
+ )
881
+ for k in obj
882
+ ]
883
+ ),
884
+ )
885
+
886
+ def visit_dml_multi_values(
887
+ self,
888
+ attrname: str,
889
+ obj: Any,
890
+ parent: Any,
891
+ anon_map: anon_map,
892
+ bindparams: List[BindParameter[Any]],
893
+ ) -> Tuple[Any, ...]:
894
+ # multivalues are simply not cacheable right now
895
+ anon_map[NO_CACHE] = True
896
+ return ()
897
+
898
+ def visit_params(
899
+ self,
900
+ attrname: str,
901
+ obj: Any,
902
+ parent: Any,
903
+ anon_map: anon_map,
904
+ bindparams: List[BindParameter[Any]],
905
+ ) -> Tuple[Any, ...]:
906
+ if obj:
907
+ if CacheConst.PARAMS in anon_map:
908
+ to_set = anon_map[CacheConst.PARAMS] | obj
909
+ else:
910
+ to_set = obj
911
+ anon_map[CacheConst.PARAMS] = to_set
912
+ return ()
913
+
914
+
915
+ _cache_key_traversal_visitor = _CacheKeyTraversal()