SQLAlchemy 2.1.0b2__cp313-cp313t-win_arm64.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 (270) hide show
  1. sqlalchemy/__init__.py +298 -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 +250 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +30 -0
  8. sqlalchemy/dialects/mssql/__init__.py +89 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4166 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +220 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +698 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +226 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +214 -0
  21. sqlalchemy/dialects/mysql/base.py +3877 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +106 -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 +330 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +296 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +312 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +157 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +156 -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 +3977 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1601 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +158 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +909 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +368 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +171 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +229 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1323 -0
  50. sqlalchemy/dialects/postgresql/base.py +5789 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +593 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +423 -0
  55. sqlalchemy/dialects/postgresql/json.py +408 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +521 -0
  57. sqlalchemy/dialects/postgresql/operators.py +130 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +670 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +344 -0
  60. sqlalchemy/dialects/postgresql/provision.py +184 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +799 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +321 -0
  68. sqlalchemy/dialects/sqlite/base.py +3063 -0
  69. sqlalchemy/dialects/sqlite/dml.py +279 -0
  70. sqlalchemy/dialects/sqlite/json.py +100 -0
  71. sqlalchemy/dialects/sqlite/provision.py +229 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +161 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +754 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp313t-win_arm64.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp313t-win_arm64.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +633 -0
  80. sqlalchemy/engine/_row_cy.cp313t-win_arm64.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp313t-win_arm64.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3354 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2421 -0
  88. sqlalchemy/engine/default.py +2402 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3495 -0
  91. sqlalchemy/engine/mock.py +134 -0
  92. sqlalchemy/engine/processors.py +82 -0
  93. sqlalchemy/engine/reflection.py +2100 -0
  94. sqlalchemy/engine/result.py +1966 -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 +156 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +674 -0
  102. sqlalchemy/event/base.py +472 -0
  103. sqlalchemy/event/legacy.py +258 -0
  104. sqlalchemy/event/registry.py +390 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +922 -0
  107. sqlalchemy/ext/__init__.py +11 -0
  108. sqlalchemy/ext/associationproxy.py +2072 -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 +2007 -0
  116. sqlalchemy/ext/automap.py +1701 -0
  117. sqlalchemy/ext/baked.py +559 -0
  118. sqlalchemy/ext/compiler.py +600 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +560 -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 +439 -0
  127. sqlalchemy/ext/serializer.py +185 -0
  128. sqlalchemy/future/__init__.py +16 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +174 -0
  131. sqlalchemy/log.py +283 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +179 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +976 -0
  137. sqlalchemy/orm/bulk_persistence.py +2152 -0
  138. sqlalchemy/orm/clsregistry.py +582 -0
  139. sqlalchemy/orm/collections.py +1568 -0
  140. sqlalchemy/orm/context.py +3471 -0
  141. sqlalchemy/orm/decl_api.py +2280 -0
  142. sqlalchemy/orm/decl_base.py +2309 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +307 -0
  146. sqlalchemy/orm/evaluator.py +379 -0
  147. sqlalchemy/orm/events.py +3386 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +746 -0
  151. sqlalchemy/orm/interfaces.py +1589 -0
  152. sqlalchemy/orm/loading.py +1684 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4411 -0
  155. sqlalchemy/orm/path_registry.py +829 -0
  156. sqlalchemy/orm/persistence.py +1789 -0
  157. sqlalchemy/orm/properties.py +973 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3570 -0
  160. sqlalchemy/orm/scoping.py +2232 -0
  161. sqlalchemy/orm/session.py +5403 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3492 -0
  165. sqlalchemy/orm/strategy_options.py +2562 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +798 -0
  168. sqlalchemy/orm/util.py +2438 -0
  169. sqlalchemy/orm/writeonly.py +694 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1522 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +582 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +74 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +397 -0
  178. sqlalchemy/sql/_dml_constructors.py +132 -0
  179. sqlalchemy/sql/_elements_constructors.py +2164 -0
  180. sqlalchemy/sql/_orm_types.py +20 -0
  181. sqlalchemy/sql/_selectable_constructors.py +840 -0
  182. sqlalchemy/sql/_typing.py +487 -0
  183. sqlalchemy/sql/_util_cy.cp313t-win_arm64.pyd +0 -0
  184. sqlalchemy/sql/_util_cy.py +127 -0
  185. sqlalchemy/sql/annotation.py +590 -0
  186. sqlalchemy/sql/base.py +2699 -0
  187. sqlalchemy/sql/cache_key.py +1066 -0
  188. sqlalchemy/sql/coercions.py +1373 -0
  189. sqlalchemy/sql/compiler.py +8327 -0
  190. sqlalchemy/sql/crud.py +1815 -0
  191. sqlalchemy/sql/ddl.py +1928 -0
  192. sqlalchemy/sql/default_comparator.py +654 -0
  193. sqlalchemy/sql/dml.py +1977 -0
  194. sqlalchemy/sql/elements.py +6033 -0
  195. sqlalchemy/sql/events.py +458 -0
  196. sqlalchemy/sql/expression.py +172 -0
  197. sqlalchemy/sql/functions.py +2305 -0
  198. sqlalchemy/sql/lambdas.py +1443 -0
  199. sqlalchemy/sql/naming.py +209 -0
  200. sqlalchemy/sql/operators.py +2897 -0
  201. sqlalchemy/sql/roles.py +332 -0
  202. sqlalchemy/sql/schema.py +6703 -0
  203. sqlalchemy/sql/selectable.py +7553 -0
  204. sqlalchemy/sql/sqltypes.py +4093 -0
  205. sqlalchemy/sql/traversals.py +1042 -0
  206. sqlalchemy/sql/type_api.py +2446 -0
  207. sqlalchemy/sql/util.py +1495 -0
  208. sqlalchemy/sql/visitors.py +1157 -0
  209. sqlalchemy/testing/__init__.py +96 -0
  210. sqlalchemy/testing/assertions.py +1007 -0
  211. sqlalchemy/testing/assertsql.py +519 -0
  212. sqlalchemy/testing/asyncio.py +128 -0
  213. sqlalchemy/testing/config.py +440 -0
  214. sqlalchemy/testing/engines.py +483 -0
  215. sqlalchemy/testing/entities.py +117 -0
  216. sqlalchemy/testing/exclusions.py +476 -0
  217. sqlalchemy/testing/fixtures/__init__.py +30 -0
  218. sqlalchemy/testing/fixtures/base.py +384 -0
  219. sqlalchemy/testing/fixtures/mypy.py +247 -0
  220. sqlalchemy/testing/fixtures/orm.py +227 -0
  221. sqlalchemy/testing/fixtures/sql.py +538 -0
  222. sqlalchemy/testing/pickleable.py +155 -0
  223. sqlalchemy/testing/plugin/__init__.py +6 -0
  224. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  225. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  226. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  227. sqlalchemy/testing/profiling.py +329 -0
  228. sqlalchemy/testing/provision.py +613 -0
  229. sqlalchemy/testing/requirements.py +1978 -0
  230. sqlalchemy/testing/schema.py +198 -0
  231. sqlalchemy/testing/suite/__init__.py +19 -0
  232. sqlalchemy/testing/suite/test_cte.py +237 -0
  233. sqlalchemy/testing/suite/test_ddl.py +420 -0
  234. sqlalchemy/testing/suite/test_dialect.py +776 -0
  235. sqlalchemy/testing/suite/test_insert.py +630 -0
  236. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  237. sqlalchemy/testing/suite/test_results.py +660 -0
  238. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  239. sqlalchemy/testing/suite/test_select.py +2112 -0
  240. sqlalchemy/testing/suite/test_sequence.py +317 -0
  241. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  242. sqlalchemy/testing/suite/test_types.py +2271 -0
  243. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  244. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  245. sqlalchemy/testing/util.py +535 -0
  246. sqlalchemy/testing/warnings.py +52 -0
  247. sqlalchemy/types.py +76 -0
  248. sqlalchemy/util/__init__.py +158 -0
  249. sqlalchemy/util/_collections.py +688 -0
  250. sqlalchemy/util/_collections_cy.cp313t-win_arm64.pyd +0 -0
  251. sqlalchemy/util/_collections_cy.pxd +8 -0
  252. sqlalchemy/util/_collections_cy.py +516 -0
  253. sqlalchemy/util/_has_cython.py +46 -0
  254. sqlalchemy/util/_immutabledict_cy.cp313t-win_arm64.pyd +0 -0
  255. sqlalchemy/util/_immutabledict_cy.py +240 -0
  256. sqlalchemy/util/compat.py +299 -0
  257. sqlalchemy/util/concurrency.py +322 -0
  258. sqlalchemy/util/cython.py +79 -0
  259. sqlalchemy/util/deprecations.py +401 -0
  260. sqlalchemy/util/langhelpers.py +2320 -0
  261. sqlalchemy/util/preloaded.py +152 -0
  262. sqlalchemy/util/queue.py +304 -0
  263. sqlalchemy/util/tool_support.py +201 -0
  264. sqlalchemy/util/topological.py +120 -0
  265. sqlalchemy/util/typing.py +711 -0
  266. sqlalchemy-2.1.0b2.dist-info/METADATA +269 -0
  267. sqlalchemy-2.1.0b2.dist-info/RECORD +270 -0
  268. sqlalchemy-2.1.0b2.dist-info/WHEEL +5 -0
  269. sqlalchemy-2.1.0b2.dist-info/licenses/LICENSE +19 -0
  270. sqlalchemy-2.1.0b2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1066 @@
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
+ import enum
11
+ from itertools import zip_longest
12
+ import typing
13
+ from typing import Any
14
+ from typing import Callable
15
+ from typing import Dict
16
+ from typing import Final
17
+ from typing import Iterable
18
+ from typing import Iterator
19
+ from typing import List
20
+ from typing import Literal
21
+ from typing import MutableMapping
22
+ from typing import NamedTuple
23
+ from typing import Optional
24
+ from typing import Protocol
25
+ from typing import Sequence
26
+ from typing import Tuple
27
+ from typing import Union
28
+
29
+ from .visitors import anon_map
30
+ from .visitors import HasTraversalDispatch
31
+ from .visitors import HasTraverseInternals
32
+ from .visitors import InternalTraversal
33
+ from .visitors import prefix_anon_map
34
+ from .. import util
35
+ from ..inspection import inspect
36
+ from ..util import HasMemoized
37
+
38
+ if typing.TYPE_CHECKING:
39
+ from .elements import BindParameter
40
+ from .elements import ClauseElement
41
+ from .elements import ColumnElement
42
+ from .visitors import _TraverseInternalsType
43
+ from ..engine.interfaces import _CoreSingleExecuteParams
44
+
45
+
46
+ class _CacheKeyTraversalDispatchType(Protocol):
47
+ def __call__(
48
+ s, self: HasCacheKey, visitor: _CacheKeyTraversal
49
+ ) -> _CacheKeyTraversalDispatchTypeReturn: ...
50
+
51
+
52
+ class CacheConst(enum.Enum):
53
+ NO_CACHE = 0
54
+ PARAMS = 1
55
+
56
+
57
+ NO_CACHE: Final = CacheConst.NO_CACHE
58
+
59
+
60
+ _CacheKeyTraversalType = Union[
61
+ "_TraverseInternalsType", Literal[CacheConst.NO_CACHE], Literal[None]
62
+ ]
63
+
64
+
65
+ class CacheTraverseTarget(enum.Enum):
66
+ CACHE_IN_PLACE = 0
67
+ CALL_GEN_CACHE_KEY = 1
68
+ STATIC_CACHE_KEY = 2
69
+ PROPAGATE_ATTRS = 3
70
+ ANON_NAME = 4
71
+
72
+
73
+ (
74
+ CACHE_IN_PLACE,
75
+ CALL_GEN_CACHE_KEY,
76
+ STATIC_CACHE_KEY,
77
+ PROPAGATE_ATTRS,
78
+ ANON_NAME,
79
+ ) = tuple(CacheTraverseTarget)
80
+
81
+ _CacheKeyTraversalDispatchTypeReturn = Sequence[
82
+ Tuple[
83
+ str,
84
+ Any,
85
+ Union[
86
+ Callable[..., Tuple[Any, ...]],
87
+ CacheTraverseTarget,
88
+ InternalTraversal,
89
+ ],
90
+ ]
91
+ ]
92
+
93
+
94
+ class HasCacheKey:
95
+ """Mixin for objects which can produce a cache key.
96
+
97
+ This class is usually in a hierarchy that starts with the
98
+ :class:`.HasTraverseInternals` base, but this is optional. Currently,
99
+ the class should be able to work on its own without including
100
+ :class:`.HasTraverseInternals`.
101
+
102
+ .. seealso::
103
+
104
+ :class:`.CacheKey`
105
+
106
+ :ref:`sql_caching`
107
+
108
+ """
109
+
110
+ __slots__ = ()
111
+
112
+ _cache_key_traversal: _CacheKeyTraversalType = NO_CACHE
113
+
114
+ _is_has_cache_key = True
115
+
116
+ _hierarchy_supports_caching = True
117
+ """private attribute which may be set to False to prevent the
118
+ inherit_cache warning from being emitted for a hierarchy of subclasses.
119
+
120
+ Currently applies to the :class:`.ExecutableDDLElement` hierarchy which
121
+ does not implement caching.
122
+
123
+ """
124
+
125
+ inherit_cache: Optional[bool] = None
126
+ """Indicate if this :class:`.HasCacheKey` instance should make use of the
127
+ cache key generation scheme used by its immediate superclass.
128
+
129
+ The attribute defaults to ``None``, which indicates that a construct has
130
+ not yet taken into account whether or not its appropriate for it to
131
+ participate in caching; this is functionally equivalent to setting the
132
+ value to ``False``, except that a warning is also emitted.
133
+
134
+ This flag can be set to ``True`` on a particular class, if the SQL that
135
+ corresponds to the object does not change based on attributes which
136
+ are local to this class, and not its superclass.
137
+
138
+ .. seealso::
139
+
140
+ :ref:`compilerext_caching` - General guideslines for setting the
141
+ :attr:`.HasCacheKey.inherit_cache` attribute for third-party or user
142
+ defined SQL constructs.
143
+
144
+ """
145
+
146
+ __slots__ = ()
147
+
148
+ _generated_cache_key_traversal: Any
149
+
150
+ @classmethod
151
+ def _generate_cache_attrs(
152
+ cls,
153
+ ) -> Union[_CacheKeyTraversalDispatchType, Literal[CacheConst.NO_CACHE]]:
154
+ """generate cache key dispatcher for a new class.
155
+
156
+ This sets the _generated_cache_key_traversal attribute once called
157
+ so should only be called once per class.
158
+
159
+ """
160
+ inherit_cache = cls.__dict__.get("inherit_cache", None)
161
+ inherit = bool(inherit_cache)
162
+
163
+ if inherit:
164
+ _cache_key_traversal = getattr(cls, "_cache_key_traversal", None)
165
+ if _cache_key_traversal is None:
166
+ try:
167
+ assert issubclass(cls, HasTraverseInternals)
168
+ _cache_key_traversal = cls._traverse_internals
169
+ except AttributeError:
170
+ cls._generated_cache_key_traversal = NO_CACHE
171
+ return NO_CACHE
172
+
173
+ assert _cache_key_traversal is not NO_CACHE, (
174
+ f"class {cls} has _cache_key_traversal=NO_CACHE, "
175
+ "which conflicts with inherit_cache=True"
176
+ )
177
+
178
+ # TODO: wouldn't we instead get this from our superclass?
179
+ # also, our superclass may not have this yet, but in any case,
180
+ # we'd generate for the superclass that has it. this is a little
181
+ # more complicated, so for the moment this is a little less
182
+ # efficient on startup but simpler.
183
+ return _cache_key_traversal_visitor.generate_dispatch(
184
+ cls,
185
+ _cache_key_traversal,
186
+ "_generated_cache_key_traversal",
187
+ )
188
+ else:
189
+ _cache_key_traversal = cls.__dict__.get(
190
+ "_cache_key_traversal", None
191
+ )
192
+ if _cache_key_traversal is None:
193
+ _cache_key_traversal = cls.__dict__.get(
194
+ "_traverse_internals", None
195
+ )
196
+ if _cache_key_traversal is None:
197
+ cls._generated_cache_key_traversal = NO_CACHE
198
+ if (
199
+ inherit_cache is None
200
+ and cls._hierarchy_supports_caching
201
+ ):
202
+ util.warn(
203
+ "Class %s will not make use of SQL compilation "
204
+ "caching as it does not set the 'inherit_cache' "
205
+ "attribute to ``True``. This can have "
206
+ "significant performance implications including "
207
+ "some performance degradations in comparison to "
208
+ "prior SQLAlchemy versions. Set this attribute "
209
+ "to True if this object can make use of the cache "
210
+ "key generated by the superclass. Alternatively, "
211
+ "this attribute may be set to False which will "
212
+ "disable this warning." % (cls.__name__),
213
+ code="cprf",
214
+ )
215
+ return NO_CACHE
216
+
217
+ return _cache_key_traversal_visitor.generate_dispatch(
218
+ cls,
219
+ _cache_key_traversal,
220
+ "_generated_cache_key_traversal",
221
+ )
222
+
223
+ @util.preload_module("sqlalchemy.sql.elements")
224
+ def _gen_cache_key(
225
+ self, anon_map: anon_map, bindparams: List[BindParameter[Any]]
226
+ ) -> Optional[Tuple[Any, ...]]:
227
+ """return an optional cache key.
228
+
229
+ The cache key is a tuple which can contain any series of
230
+ objects that are hashable and also identifies
231
+ this object uniquely within the presence of a larger SQL expression
232
+ or statement, for the purposes of caching the resulting query.
233
+
234
+ The cache key should be based on the SQL compiled structure that would
235
+ ultimately be produced. That is, two structures that are composed in
236
+ exactly the same way should produce the same cache key; any difference
237
+ in the structures that would affect the SQL string or the type handlers
238
+ should result in a different cache key.
239
+
240
+ If a structure cannot produce a useful cache key, the NO_CACHE
241
+ symbol should be added to the anon_map and the method should
242
+ return None.
243
+
244
+ """
245
+
246
+ cls = self.__class__
247
+
248
+ id_, found = anon_map.get_anon(self)
249
+ if found:
250
+ return (id_, cls)
251
+
252
+ dispatcher: Union[
253
+ Literal[CacheConst.NO_CACHE],
254
+ _CacheKeyTraversalDispatchType,
255
+ ]
256
+
257
+ try:
258
+ dispatcher = cls.__dict__["_generated_cache_key_traversal"]
259
+ except KeyError:
260
+ # traversals.py -> _preconfigure_traversals()
261
+ # may be used to run these ahead of time, but
262
+ # is not enabled right now.
263
+ # this block will generate any remaining dispatchers.
264
+ dispatcher = cls._generate_cache_attrs()
265
+
266
+ if dispatcher is NO_CACHE:
267
+ anon_map[NO_CACHE] = True
268
+ return None
269
+
270
+ result: Tuple[Any, ...] = (id_, cls)
271
+
272
+ # inline of _cache_key_traversal_visitor.run_generated_dispatch()
273
+
274
+ for attrname, obj, meth in dispatcher(
275
+ self, _cache_key_traversal_visitor
276
+ ):
277
+ if obj is not None:
278
+ # TODO: see if C code can help here as Python lacks an
279
+ # efficient switch construct
280
+
281
+ if meth is STATIC_CACHE_KEY:
282
+ sck = obj._static_cache_key
283
+ if sck is NO_CACHE:
284
+ anon_map[NO_CACHE] = True
285
+ return None
286
+ result += (attrname, sck)
287
+ elif meth is ANON_NAME:
288
+ elements = util.preloaded.sql_elements
289
+ if isinstance(obj, elements._anonymous_label):
290
+ obj = obj.apply_map(anon_map) # type: ignore
291
+ result += (attrname, obj)
292
+ elif meth is CALL_GEN_CACHE_KEY:
293
+ result += (
294
+ attrname,
295
+ obj._gen_cache_key(anon_map, bindparams),
296
+ )
297
+
298
+ # remaining cache functions are against
299
+ # Python tuples, dicts, lists, etc. so we can skip
300
+ # if they are empty
301
+ elif obj:
302
+ if meth is CACHE_IN_PLACE:
303
+ result += (attrname, obj)
304
+ elif meth is PROPAGATE_ATTRS:
305
+ result += (
306
+ attrname,
307
+ obj["compile_state_plugin"],
308
+ (
309
+ obj["plugin_subject"]._gen_cache_key(
310
+ anon_map, bindparams
311
+ )
312
+ if obj["plugin_subject"]
313
+ else None
314
+ ),
315
+ )
316
+ elif meth is InternalTraversal.dp_annotations_key:
317
+ # obj is here is the _annotations dict. Table uses
318
+ # a memoized version of it. however in other cases,
319
+ # we generate it given anon_map as we may be from a
320
+ # Join, Aliased, etc.
321
+ # see #8790
322
+
323
+ if self._gen_static_annotations_cache_key: # type: ignore # noqa: E501
324
+ result += self._annotations_cache_key # type: ignore # noqa: E501
325
+ else:
326
+ result += self._gen_annotations_cache_key(anon_map) # type: ignore # noqa: E501
327
+
328
+ elif (
329
+ meth is InternalTraversal.dp_clauseelement_list
330
+ or meth is InternalTraversal.dp_clauseelement_tuple
331
+ or meth
332
+ is InternalTraversal.dp_memoized_select_entities
333
+ ):
334
+ result += (
335
+ attrname,
336
+ tuple(
337
+ [
338
+ elem._gen_cache_key(anon_map, bindparams)
339
+ for elem in obj
340
+ ]
341
+ ),
342
+ )
343
+ else:
344
+ result += meth( # type: ignore
345
+ attrname, obj, self, anon_map, bindparams
346
+ )
347
+ return result
348
+
349
+ def _generate_cache_key(self) -> Optional[CacheKey]:
350
+ """return a cache key.
351
+
352
+ The cache key is a tuple which can contain any series of
353
+ objects that are hashable and also identifies
354
+ this object uniquely within the presence of a larger SQL expression
355
+ or statement, for the purposes of caching the resulting query.
356
+
357
+ The cache key should be based on the SQL compiled structure that would
358
+ ultimately be produced. That is, two structures that are composed in
359
+ exactly the same way should produce the same cache key; any difference
360
+ in the structures that would affect the SQL string or the type handlers
361
+ should result in a different cache key.
362
+
363
+ The cache key returned by this method is an instance of
364
+ :class:`.CacheKey`, which consists of a tuple representing the
365
+ cache key, as well as a list of :class:`.BindParameter` objects
366
+ which are extracted from the expression. While two expressions
367
+ that produce identical cache key tuples will themselves generate
368
+ identical SQL strings, the list of :class:`.BindParameter` objects
369
+ indicates the bound values which may have different values in
370
+ each one; these bound parameters must be consulted in order to
371
+ execute the statement with the correct parameters.
372
+
373
+ a :class:`_expression.ClauseElement` structure that does not implement
374
+ a :meth:`._gen_cache_key` method and does not implement a
375
+ :attr:`.traverse_internals` attribute will not be cacheable; when
376
+ such an element is embedded into a larger structure, this method
377
+ will return None, indicating no cache key is available.
378
+
379
+ """
380
+
381
+ bindparams: List[BindParameter[Any]] = []
382
+
383
+ _anon_map = anon_map()
384
+ key = self._gen_cache_key(_anon_map, bindparams)
385
+ if NO_CACHE in _anon_map:
386
+ return None
387
+ else:
388
+ assert key is not None
389
+ return CacheKey(
390
+ key,
391
+ bindparams,
392
+ _anon_map.get(CacheConst.PARAMS), # type: ignore[arg-type]
393
+ )
394
+
395
+
396
+ class HasCacheKeyTraverse(HasTraverseInternals, HasCacheKey):
397
+ pass
398
+
399
+
400
+ class MemoizedHasCacheKey(HasCacheKey, HasMemoized):
401
+ __slots__ = ()
402
+
403
+ @HasMemoized.memoized_instancemethod
404
+ def _generate_cache_key(self) -> Optional[CacheKey]:
405
+ return HasCacheKey._generate_cache_key(self)
406
+
407
+
408
+ class SlotsMemoizedHasCacheKey(HasCacheKey, util.MemoizedSlots):
409
+ __slots__ = ()
410
+
411
+ def _memoized_method__generate_cache_key(self) -> Optional[CacheKey]:
412
+ return HasCacheKey._generate_cache_key(self)
413
+
414
+
415
+ class CacheKey(NamedTuple):
416
+ """The key used to identify a SQL statement construct in the
417
+ SQL compilation cache.
418
+
419
+ .. seealso::
420
+
421
+ :ref:`sql_caching`
422
+
423
+ """
424
+
425
+ key: Tuple[Any, ...]
426
+ bindparams: Sequence[BindParameter[Any]]
427
+ params: _CoreSingleExecuteParams | None
428
+
429
+ # can't set __hash__ attribute because it interferes
430
+ # with namedtuple
431
+ # can't use "if not TYPE_CHECKING" because mypy rejects it
432
+ # inside of a NamedTuple
433
+ def __hash__(self) -> Optional[int]: # type: ignore
434
+ """CacheKey itself is not hashable - hash the .key portion"""
435
+ return None
436
+
437
+ def to_offline_string(
438
+ self,
439
+ statement_cache: MutableMapping[Any, str],
440
+ statement: ClauseElement,
441
+ parameters: _CoreSingleExecuteParams,
442
+ ) -> str:
443
+ """Generate an "offline string" form of this :class:`.CacheKey`
444
+
445
+ The "offline string" is basically the string SQL for the
446
+ statement plus a repr of the bound parameter values in series.
447
+ Whereas the :class:`.CacheKey` object is dependent on in-memory
448
+ identities in order to work as a cache key, the "offline" version
449
+ is suitable for a cache that will work for other processes as well.
450
+
451
+ The given ``statement_cache`` is a dictionary-like object where the
452
+ string form of the statement itself will be cached. This dictionary
453
+ should be in a longer lived scope in order to reduce the time spent
454
+ stringifying statements.
455
+
456
+
457
+ """
458
+ if self.key not in statement_cache:
459
+ statement_cache[self.key] = sql_str = str(statement)
460
+ else:
461
+ sql_str = statement_cache[self.key]
462
+
463
+ if not self.bindparams:
464
+ param_tuple = tuple(parameters[key] for key in sorted(parameters))
465
+ else:
466
+ param_tuple = tuple(
467
+ parameters.get(bindparam.key, bindparam.value)
468
+ for bindparam in self.bindparams
469
+ )
470
+
471
+ return repr((sql_str, param_tuple))
472
+
473
+ def __eq__(self, other: Any) -> bool:
474
+ return other is not None and bool(self.key == other.key)
475
+
476
+ def __ne__(self, other: Any) -> bool:
477
+ return other is None or not (self.key == other.key)
478
+
479
+ @classmethod
480
+ def _diff_tuples(cls, left: CacheKey, right: CacheKey) -> str:
481
+ ck1 = CacheKey(left, [], None)
482
+ ck2 = CacheKey(right, [], None)
483
+ return ck1._diff(ck2)
484
+
485
+ def _whats_different(self, other: CacheKey) -> Iterator[str]:
486
+ k1 = self.key
487
+ k2 = other.key
488
+
489
+ stack: List[int] = []
490
+ pickup_index = 0
491
+ while True:
492
+ s1, s2 = k1, k2
493
+ for idx in stack:
494
+ s1 = s1[idx]
495
+ s2 = s2[idx]
496
+
497
+ for idx, (e1, e2) in enumerate(zip_longest(s1, s2)):
498
+ if idx < pickup_index:
499
+ continue
500
+ if e1 != e2:
501
+ if isinstance(e1, tuple) and isinstance(e2, tuple):
502
+ stack.append(idx)
503
+ break
504
+ else:
505
+ yield "key%s[%d]: %s != %s" % (
506
+ "".join("[%d]" % id_ for id_ in stack),
507
+ idx,
508
+ e1,
509
+ e2,
510
+ )
511
+ else:
512
+ stack.pop(-1)
513
+ break
514
+
515
+ def _diff(self, other: CacheKey) -> str:
516
+ return ", ".join(self._whats_different(other))
517
+
518
+ def __str__(self) -> str:
519
+ stack: List[Union[Tuple[Any, ...], HasCacheKey]] = [self.key]
520
+
521
+ output = []
522
+ sentinel = object()
523
+ indent = -1
524
+ while stack:
525
+ elem = stack.pop(0)
526
+ if elem is sentinel:
527
+ output.append((" " * (indent * 2)) + "),")
528
+ indent -= 1
529
+ elif isinstance(elem, tuple):
530
+ if not elem:
531
+ output.append((" " * ((indent + 1) * 2)) + "()")
532
+ else:
533
+ indent += 1
534
+ stack = list(elem) + [sentinel] + stack
535
+ output.append((" " * (indent * 2)) + "(")
536
+ else:
537
+ if isinstance(elem, HasCacheKey):
538
+ repr_ = "<%s object at %s>" % (
539
+ type(elem).__name__,
540
+ hex(id(elem)),
541
+ )
542
+ else:
543
+ repr_ = repr(elem)
544
+ output.append((" " * (indent * 2)) + " " + repr_ + ", ")
545
+
546
+ return "CacheKey(key=%s)" % ("\n".join(output),)
547
+
548
+ def _generate_param_dict(self) -> Dict[str, Any]:
549
+ """used for testing"""
550
+
551
+ _anon_map = prefix_anon_map()
552
+ return {b.key % _anon_map: b.effective_value for b in self.bindparams}
553
+
554
+ @util.preload_module("sqlalchemy.sql.elements")
555
+ def _apply_params_to_element(
556
+ self, original_cache_key: CacheKey, target_element: ColumnElement[Any]
557
+ ) -> ColumnElement[Any]:
558
+ if target_element._is_immutable or original_cache_key is self:
559
+ return target_element
560
+
561
+ elements = util.preloaded.sql_elements
562
+ return elements._OverrideBinds(
563
+ target_element, self.bindparams, original_cache_key.bindparams
564
+ )
565
+
566
+
567
+ def _ad_hoc_cache_key_from_args(
568
+ tokens: Tuple[Any, ...],
569
+ traverse_args: Iterable[Tuple[str, InternalTraversal]],
570
+ args: Iterable[Any],
571
+ ) -> Tuple[Any, ...]:
572
+ """a quick cache key generator used by reflection.flexi_cache."""
573
+ bindparams: List[BindParameter[Any]] = []
574
+
575
+ _anon_map = anon_map()
576
+
577
+ tup = tokens
578
+
579
+ for (attrname, sym), arg in zip(traverse_args, args):
580
+ key = sym.name
581
+ visit_key = key.replace("dp_", "visit_")
582
+
583
+ if arg is None:
584
+ tup += (attrname, None)
585
+ continue
586
+
587
+ meth = getattr(_cache_key_traversal_visitor, visit_key)
588
+ if meth is CACHE_IN_PLACE:
589
+ tup += (attrname, arg)
590
+ elif meth in (
591
+ CALL_GEN_CACHE_KEY,
592
+ STATIC_CACHE_KEY,
593
+ ANON_NAME,
594
+ PROPAGATE_ATTRS,
595
+ ):
596
+ raise NotImplementedError(
597
+ f"Haven't implemented symbol {meth} for ad-hoc key from args"
598
+ )
599
+ else:
600
+ tup += meth(attrname, arg, None, _anon_map, bindparams)
601
+ return tup
602
+
603
+
604
+ class _CacheKeyTraversal(HasTraversalDispatch):
605
+ # very common elements are inlined into the main _get_cache_key() method
606
+ # to produce a dramatic savings in Python function call overhead
607
+
608
+ visit_has_cache_key = visit_clauseelement = CALL_GEN_CACHE_KEY
609
+ visit_clauseelement_list = InternalTraversal.dp_clauseelement_list
610
+ visit_annotations_key = InternalTraversal.dp_annotations_key
611
+ visit_clauseelement_tuple = InternalTraversal.dp_clauseelement_tuple
612
+ visit_memoized_select_entities = (
613
+ InternalTraversal.dp_memoized_select_entities
614
+ )
615
+
616
+ visit_string = visit_boolean = visit_operator = visit_plain_obj = (
617
+ CACHE_IN_PLACE
618
+ )
619
+ visit_statement_hint_list = CACHE_IN_PLACE
620
+ visit_type = STATIC_CACHE_KEY
621
+ visit_anon_name = ANON_NAME
622
+
623
+ visit_propagate_attrs = PROPAGATE_ATTRS
624
+
625
+ def visit_compile_state_funcs(
626
+ self,
627
+ attrname: str,
628
+ obj: Any,
629
+ parent: Any,
630
+ anon_map: anon_map,
631
+ bindparams: List[BindParameter[Any]],
632
+ ) -> Tuple[Any, ...]:
633
+ return tuple((fn.__code__, c_key) for fn, c_key in obj)
634
+
635
+ def visit_inspectable(
636
+ self,
637
+ attrname: str,
638
+ obj: Any,
639
+ parent: Any,
640
+ anon_map: anon_map,
641
+ bindparams: List[BindParameter[Any]],
642
+ ) -> Tuple[Any, ...]:
643
+ return (attrname, inspect(obj)._gen_cache_key(anon_map, bindparams))
644
+
645
+ def visit_string_list(
646
+ self,
647
+ attrname: str,
648
+ obj: Any,
649
+ parent: Any,
650
+ anon_map: anon_map,
651
+ bindparams: List[BindParameter[Any]],
652
+ ) -> Tuple[Any, ...]:
653
+ return tuple(obj)
654
+
655
+ def visit_multi(
656
+ self,
657
+ attrname: str,
658
+ obj: Any,
659
+ parent: Any,
660
+ anon_map: anon_map,
661
+ bindparams: List[BindParameter[Any]],
662
+ ) -> Tuple[Any, ...]:
663
+ return (
664
+ attrname,
665
+ (
666
+ obj._gen_cache_key(anon_map, bindparams)
667
+ if isinstance(obj, HasCacheKey)
668
+ else obj
669
+ ),
670
+ )
671
+
672
+ def visit_multi_list(
673
+ self,
674
+ attrname: str,
675
+ obj: Any,
676
+ parent: Any,
677
+ anon_map: anon_map,
678
+ bindparams: List[BindParameter[Any]],
679
+ ) -> Tuple[Any, ...]:
680
+ return (
681
+ attrname,
682
+ tuple(
683
+ (
684
+ elem._gen_cache_key(anon_map, bindparams)
685
+ if isinstance(elem, HasCacheKey)
686
+ else elem
687
+ )
688
+ for elem in obj
689
+ ),
690
+ )
691
+
692
+ def visit_has_cache_key_tuples(
693
+ self,
694
+ attrname: str,
695
+ obj: Any,
696
+ parent: Any,
697
+ anon_map: anon_map,
698
+ bindparams: List[BindParameter[Any]],
699
+ ) -> Tuple[Any, ...]:
700
+ if not obj:
701
+ return ()
702
+ return (
703
+ attrname,
704
+ tuple(
705
+ tuple(
706
+ elem._gen_cache_key(anon_map, bindparams)
707
+ for elem in tup_elem
708
+ )
709
+ for tup_elem in obj
710
+ ),
711
+ )
712
+
713
+ def visit_has_cache_key_list(
714
+ self,
715
+ attrname: str,
716
+ obj: Any,
717
+ parent: Any,
718
+ anon_map: anon_map,
719
+ bindparams: List[BindParameter[Any]],
720
+ ) -> Tuple[Any, ...]:
721
+ if not obj:
722
+ return ()
723
+ return (
724
+ attrname,
725
+ tuple(elem._gen_cache_key(anon_map, bindparams) for elem in obj),
726
+ )
727
+
728
+ def visit_executable_options(
729
+ self,
730
+ attrname: str,
731
+ obj: Any,
732
+ parent: Any,
733
+ anon_map: anon_map,
734
+ bindparams: List[BindParameter[Any]],
735
+ ) -> Tuple[Any, ...]:
736
+ if not obj:
737
+ return ()
738
+ return (
739
+ attrname,
740
+ tuple(
741
+ elem._gen_cache_key(anon_map, bindparams)
742
+ for elem in obj
743
+ if elem._is_has_cache_key
744
+ ),
745
+ )
746
+
747
+ def visit_inspectable_list(
748
+ self,
749
+ attrname: str,
750
+ obj: Any,
751
+ parent: Any,
752
+ anon_map: anon_map,
753
+ bindparams: List[BindParameter[Any]],
754
+ ) -> Tuple[Any, ...]:
755
+ return self.visit_has_cache_key_list(
756
+ attrname, [inspect(o) for o in obj], parent, anon_map, bindparams
757
+ )
758
+
759
+ def visit_clauseelement_tuples(
760
+ self,
761
+ attrname: str,
762
+ obj: Any,
763
+ parent: Any,
764
+ anon_map: anon_map,
765
+ bindparams: List[BindParameter[Any]],
766
+ ) -> Tuple[Any, ...]:
767
+ return self.visit_has_cache_key_tuples(
768
+ attrname, obj, parent, anon_map, bindparams
769
+ )
770
+
771
+ def visit_fromclause_ordered_set(
772
+ self,
773
+ attrname: str,
774
+ obj: Any,
775
+ parent: Any,
776
+ anon_map: anon_map,
777
+ bindparams: List[BindParameter[Any]],
778
+ ) -> Tuple[Any, ...]:
779
+ if not obj:
780
+ return ()
781
+ return (
782
+ attrname,
783
+ tuple([elem._gen_cache_key(anon_map, bindparams) for elem in obj]),
784
+ )
785
+
786
+ def visit_clauseelement_unordered_set(
787
+ self,
788
+ attrname: str,
789
+ obj: Any,
790
+ parent: Any,
791
+ anon_map: anon_map,
792
+ bindparams: List[BindParameter[Any]],
793
+ ) -> Tuple[Any, ...]:
794
+ if not obj:
795
+ return ()
796
+ cache_keys = [
797
+ elem._gen_cache_key(anon_map, bindparams) for elem in obj
798
+ ]
799
+ return (
800
+ attrname,
801
+ tuple(
802
+ sorted(cache_keys)
803
+ ), # cache keys all start with (id_, class)
804
+ )
805
+
806
+ def visit_named_ddl_element(
807
+ self,
808
+ attrname: str,
809
+ obj: Any,
810
+ parent: Any,
811
+ anon_map: anon_map,
812
+ bindparams: List[BindParameter[Any]],
813
+ ) -> Tuple[Any, ...]:
814
+ return (attrname, obj.name)
815
+
816
+ def visit_prefix_sequence(
817
+ self,
818
+ attrname: str,
819
+ obj: Any,
820
+ parent: Any,
821
+ anon_map: anon_map,
822
+ bindparams: List[BindParameter[Any]],
823
+ ) -> Tuple[Any, ...]:
824
+ if not obj:
825
+ return ()
826
+
827
+ return (
828
+ attrname,
829
+ tuple(
830
+ [
831
+ (clause._gen_cache_key(anon_map, bindparams), strval)
832
+ for clause, strval in obj
833
+ ]
834
+ ),
835
+ )
836
+
837
+ def visit_setup_join_tuple(
838
+ self,
839
+ attrname: str,
840
+ obj: Any,
841
+ parent: Any,
842
+ anon_map: anon_map,
843
+ bindparams: List[BindParameter[Any]],
844
+ ) -> Tuple[Any, ...]:
845
+ return tuple(
846
+ (
847
+ target._gen_cache_key(anon_map, bindparams),
848
+ (
849
+ onclause._gen_cache_key(anon_map, bindparams)
850
+ if onclause is not None
851
+ else None
852
+ ),
853
+ (
854
+ from_._gen_cache_key(anon_map, bindparams)
855
+ if from_ is not None
856
+ else None
857
+ ),
858
+ tuple([(key, flags[key]) for key in sorted(flags)]),
859
+ )
860
+ for (target, onclause, from_, flags) in obj
861
+ )
862
+
863
+ def visit_table_hint_list(
864
+ self,
865
+ attrname: str,
866
+ obj: Any,
867
+ parent: Any,
868
+ anon_map: anon_map,
869
+ bindparams: List[BindParameter[Any]],
870
+ ) -> Tuple[Any, ...]:
871
+ if not obj:
872
+ return ()
873
+
874
+ return (
875
+ attrname,
876
+ tuple(
877
+ [
878
+ (
879
+ clause._gen_cache_key(anon_map, bindparams),
880
+ dialect_name,
881
+ text,
882
+ )
883
+ for (clause, dialect_name), text in obj.items()
884
+ ]
885
+ ),
886
+ )
887
+
888
+ def visit_plain_dict(
889
+ self,
890
+ attrname: str,
891
+ obj: Any,
892
+ parent: Any,
893
+ anon_map: anon_map,
894
+ bindparams: List[BindParameter[Any]],
895
+ ) -> Tuple[Any, ...]:
896
+ return (attrname, tuple([(key, obj[key]) for key in sorted(obj)]))
897
+
898
+ def visit_dialect_options(
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
+ return (
907
+ attrname,
908
+ tuple(
909
+ (
910
+ dialect_name,
911
+ tuple(
912
+ [
913
+ (key, obj[dialect_name][key])
914
+ for key in sorted(obj[dialect_name])
915
+ ]
916
+ ),
917
+ )
918
+ for dialect_name in sorted(obj)
919
+ ),
920
+ )
921
+
922
+ def visit_string_clauseelement_dict(
923
+ self,
924
+ attrname: str,
925
+ obj: Any,
926
+ parent: Any,
927
+ anon_map: anon_map,
928
+ bindparams: List[BindParameter[Any]],
929
+ ) -> Tuple[Any, ...]:
930
+ return (
931
+ attrname,
932
+ tuple(
933
+ (key, obj[key]._gen_cache_key(anon_map, bindparams))
934
+ for key in sorted(obj)
935
+ ),
936
+ )
937
+
938
+ def visit_string_multi_dict(
939
+ self,
940
+ attrname: str,
941
+ obj: Any,
942
+ parent: Any,
943
+ anon_map: anon_map,
944
+ bindparams: List[BindParameter[Any]],
945
+ ) -> Tuple[Any, ...]:
946
+ return (
947
+ attrname,
948
+ tuple(
949
+ (
950
+ key,
951
+ (
952
+ value._gen_cache_key(anon_map, bindparams)
953
+ if isinstance(value, HasCacheKey)
954
+ else value
955
+ ),
956
+ )
957
+ for key, value in [(key, obj[key]) for key in sorted(obj)]
958
+ ),
959
+ )
960
+
961
+ def visit_fromclause_canonical_column_collection(
962
+ self,
963
+ attrname: str,
964
+ obj: Any,
965
+ parent: Any,
966
+ anon_map: anon_map,
967
+ bindparams: List[BindParameter[Any]],
968
+ ) -> Tuple[Any, ...]:
969
+ # inlining into the internals of ColumnCollection
970
+ return (
971
+ attrname,
972
+ tuple(
973
+ col._gen_cache_key(anon_map, bindparams)
974
+ for k, col, _ in obj._collection
975
+ ),
976
+ )
977
+
978
+ def visit_unknown_structure(
979
+ self,
980
+ attrname: str,
981
+ obj: Any,
982
+ parent: Any,
983
+ anon_map: anon_map,
984
+ bindparams: List[BindParameter[Any]],
985
+ ) -> Tuple[Any, ...]:
986
+ anon_map[NO_CACHE] = True
987
+ return ()
988
+
989
+ def visit_dml_ordered_values(
990
+ self,
991
+ attrname: str,
992
+ obj: Any,
993
+ parent: Any,
994
+ anon_map: anon_map,
995
+ bindparams: List[BindParameter[Any]],
996
+ ) -> Tuple[Any, ...]:
997
+ return (
998
+ attrname,
999
+ tuple(
1000
+ (
1001
+ (
1002
+ key._gen_cache_key(anon_map, bindparams)
1003
+ if hasattr(key, "__clause_element__")
1004
+ else key
1005
+ ),
1006
+ value._gen_cache_key(anon_map, bindparams),
1007
+ )
1008
+ for key, value in obj
1009
+ ),
1010
+ )
1011
+
1012
+ def visit_dml_values(
1013
+ self,
1014
+ attrname: str,
1015
+ obj: Any,
1016
+ parent: Any,
1017
+ anon_map: anon_map,
1018
+ bindparams: List[BindParameter[Any]],
1019
+ ) -> Tuple[Any, ...]:
1020
+ # in py37 we can assume two dictionaries created in the same
1021
+ # insert ordering will retain that sorting
1022
+ return (
1023
+ attrname,
1024
+ tuple(
1025
+ (
1026
+ (
1027
+ k._gen_cache_key(anon_map, bindparams)
1028
+ if hasattr(k, "__clause_element__")
1029
+ else k
1030
+ ),
1031
+ obj[k]._gen_cache_key(anon_map, bindparams),
1032
+ )
1033
+ for k in obj
1034
+ ),
1035
+ )
1036
+
1037
+ def visit_dml_multi_values(
1038
+ self,
1039
+ attrname: str,
1040
+ obj: Any,
1041
+ parent: Any,
1042
+ anon_map: anon_map,
1043
+ bindparams: List[BindParameter[Any]],
1044
+ ) -> Tuple[Any, ...]:
1045
+ # multivalues are simply not cacheable right now
1046
+ anon_map[NO_CACHE] = True
1047
+ return ()
1048
+
1049
+ def visit_params(
1050
+ self,
1051
+ attrname: str,
1052
+ obj: Any,
1053
+ parent: Any,
1054
+ anon_map: anon_map,
1055
+ bindparams: List[BindParameter[Any]],
1056
+ ) -> Tuple[Any, ...]:
1057
+ if obj:
1058
+ if CacheConst.PARAMS in anon_map:
1059
+ to_set = anon_map[CacheConst.PARAMS] | obj
1060
+ else:
1061
+ to_set = obj
1062
+ anon_map[CacheConst.PARAMS] = to_set
1063
+ return ()
1064
+
1065
+
1066
+ _cache_key_traversal_visitor = _CacheKeyTraversal()