SQLAlchemy 2.0.47__cp313-cp313t-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 (274) hide show
  1. sqlalchemy/__init__.py +283 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +184 -0
  4. sqlalchemy/connectors/asyncio.py +429 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/cyextension/__init__.py +6 -0
  7. sqlalchemy/cyextension/collections.cp313t-win_amd64.pyd +0 -0
  8. sqlalchemy/cyextension/collections.pyx +409 -0
  9. sqlalchemy/cyextension/immutabledict.cp313t-win_amd64.pyd +0 -0
  10. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  11. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  12. sqlalchemy/cyextension/processors.cp313t-win_amd64.pyd +0 -0
  13. sqlalchemy/cyextension/processors.pyx +68 -0
  14. sqlalchemy/cyextension/resultproxy.cp313t-win_amd64.pyd +0 -0
  15. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  16. sqlalchemy/cyextension/util.cp313t-win_amd64.pyd +0 -0
  17. sqlalchemy/cyextension/util.pyx +90 -0
  18. sqlalchemy/dialects/__init__.py +62 -0
  19. sqlalchemy/dialects/_typing.py +30 -0
  20. sqlalchemy/dialects/mssql/__init__.py +88 -0
  21. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  22. sqlalchemy/dialects/mssql/base.py +4093 -0
  23. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  24. sqlalchemy/dialects/mssql/json.py +129 -0
  25. sqlalchemy/dialects/mssql/provision.py +185 -0
  26. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  27. sqlalchemy/dialects/mssql/pyodbc.py +760 -0
  28. sqlalchemy/dialects/mysql/__init__.py +104 -0
  29. sqlalchemy/dialects/mysql/aiomysql.py +250 -0
  30. sqlalchemy/dialects/mysql/asyncmy.py +231 -0
  31. sqlalchemy/dialects/mysql/base.py +3949 -0
  32. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  33. sqlalchemy/dialects/mysql/dml.py +225 -0
  34. sqlalchemy/dialects/mysql/enumerated.py +282 -0
  35. sqlalchemy/dialects/mysql/expression.py +146 -0
  36. sqlalchemy/dialects/mysql/json.py +91 -0
  37. sqlalchemy/dialects/mysql/mariadb.py +72 -0
  38. sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
  39. sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
  40. sqlalchemy/dialects/mysql/mysqldb.py +314 -0
  41. sqlalchemy/dialects/mysql/provision.py +153 -0
  42. sqlalchemy/dialects/mysql/pymysql.py +158 -0
  43. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  44. sqlalchemy/dialects/mysql/reflection.py +727 -0
  45. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  46. sqlalchemy/dialects/mysql/types.py +835 -0
  47. sqlalchemy/dialects/oracle/__init__.py +81 -0
  48. sqlalchemy/dialects/oracle/base.py +3802 -0
  49. sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
  50. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  51. sqlalchemy/dialects/oracle/oracledb.py +941 -0
  52. sqlalchemy/dialects/oracle/provision.py +297 -0
  53. sqlalchemy/dialects/oracle/types.py +316 -0
  54. sqlalchemy/dialects/oracle/vector.py +365 -0
  55. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  56. sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
  57. sqlalchemy/dialects/postgresql/array.py +519 -0
  58. sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
  59. sqlalchemy/dialects/postgresql/base.py +5378 -0
  60. sqlalchemy/dialects/postgresql/dml.py +339 -0
  61. sqlalchemy/dialects/postgresql/ext.py +540 -0
  62. sqlalchemy/dialects/postgresql/hstore.py +406 -0
  63. sqlalchemy/dialects/postgresql/json.py +404 -0
  64. sqlalchemy/dialects/postgresql/named_types.py +524 -0
  65. sqlalchemy/dialects/postgresql/operators.py +129 -0
  66. sqlalchemy/dialects/postgresql/pg8000.py +669 -0
  67. sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
  68. sqlalchemy/dialects/postgresql/provision.py +183 -0
  69. sqlalchemy/dialects/postgresql/psycopg.py +862 -0
  70. sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
  71. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  72. sqlalchemy/dialects/postgresql/ranges.py +1031 -0
  73. sqlalchemy/dialects/postgresql/types.py +313 -0
  74. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  75. sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
  76. sqlalchemy/dialects/sqlite/base.py +3056 -0
  77. sqlalchemy/dialects/sqlite/dml.py +263 -0
  78. sqlalchemy/dialects/sqlite/json.py +92 -0
  79. sqlalchemy/dialects/sqlite/provision.py +229 -0
  80. sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
  81. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  82. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  83. sqlalchemy/engine/__init__.py +62 -0
  84. sqlalchemy/engine/_py_processors.py +136 -0
  85. sqlalchemy/engine/_py_row.py +128 -0
  86. sqlalchemy/engine/_py_util.py +74 -0
  87. sqlalchemy/engine/base.py +3390 -0
  88. sqlalchemy/engine/characteristics.py +155 -0
  89. sqlalchemy/engine/create.py +893 -0
  90. sqlalchemy/engine/cursor.py +2298 -0
  91. sqlalchemy/engine/default.py +2394 -0
  92. sqlalchemy/engine/events.py +965 -0
  93. sqlalchemy/engine/interfaces.py +3471 -0
  94. sqlalchemy/engine/mock.py +134 -0
  95. sqlalchemy/engine/processors.py +61 -0
  96. sqlalchemy/engine/reflection.py +2102 -0
  97. sqlalchemy/engine/result.py +2399 -0
  98. sqlalchemy/engine/row.py +400 -0
  99. sqlalchemy/engine/strategies.py +16 -0
  100. sqlalchemy/engine/url.py +924 -0
  101. sqlalchemy/engine/util.py +167 -0
  102. sqlalchemy/event/__init__.py +26 -0
  103. sqlalchemy/event/api.py +220 -0
  104. sqlalchemy/event/attr.py +676 -0
  105. sqlalchemy/event/base.py +472 -0
  106. sqlalchemy/event/legacy.py +258 -0
  107. sqlalchemy/event/registry.py +390 -0
  108. sqlalchemy/events.py +17 -0
  109. sqlalchemy/exc.py +832 -0
  110. sqlalchemy/ext/__init__.py +11 -0
  111. sqlalchemy/ext/associationproxy.py +2027 -0
  112. sqlalchemy/ext/asyncio/__init__.py +25 -0
  113. sqlalchemy/ext/asyncio/base.py +281 -0
  114. sqlalchemy/ext/asyncio/engine.py +1471 -0
  115. sqlalchemy/ext/asyncio/exc.py +21 -0
  116. sqlalchemy/ext/asyncio/result.py +965 -0
  117. sqlalchemy/ext/asyncio/scoping.py +1599 -0
  118. sqlalchemy/ext/asyncio/session.py +1947 -0
  119. sqlalchemy/ext/automap.py +1701 -0
  120. sqlalchemy/ext/baked.py +570 -0
  121. sqlalchemy/ext/compiler.py +600 -0
  122. sqlalchemy/ext/declarative/__init__.py +65 -0
  123. sqlalchemy/ext/declarative/extensions.py +564 -0
  124. sqlalchemy/ext/horizontal_shard.py +478 -0
  125. sqlalchemy/ext/hybrid.py +1535 -0
  126. sqlalchemy/ext/indexable.py +364 -0
  127. sqlalchemy/ext/instrumentation.py +450 -0
  128. sqlalchemy/ext/mutable.py +1085 -0
  129. sqlalchemy/ext/mypy/__init__.py +6 -0
  130. sqlalchemy/ext/mypy/apply.py +324 -0
  131. sqlalchemy/ext/mypy/decl_class.py +515 -0
  132. sqlalchemy/ext/mypy/infer.py +590 -0
  133. sqlalchemy/ext/mypy/names.py +335 -0
  134. sqlalchemy/ext/mypy/plugin.py +303 -0
  135. sqlalchemy/ext/mypy/util.py +357 -0
  136. sqlalchemy/ext/orderinglist.py +439 -0
  137. sqlalchemy/ext/serializer.py +185 -0
  138. sqlalchemy/future/__init__.py +16 -0
  139. sqlalchemy/future/engine.py +15 -0
  140. sqlalchemy/inspection.py +174 -0
  141. sqlalchemy/log.py +288 -0
  142. sqlalchemy/orm/__init__.py +171 -0
  143. sqlalchemy/orm/_orm_constructors.py +2661 -0
  144. sqlalchemy/orm/_typing.py +179 -0
  145. sqlalchemy/orm/attributes.py +2845 -0
  146. sqlalchemy/orm/base.py +971 -0
  147. sqlalchemy/orm/bulk_persistence.py +2135 -0
  148. sqlalchemy/orm/clsregistry.py +571 -0
  149. sqlalchemy/orm/collections.py +1627 -0
  150. sqlalchemy/orm/context.py +3334 -0
  151. sqlalchemy/orm/decl_api.py +2004 -0
  152. sqlalchemy/orm/decl_base.py +2192 -0
  153. sqlalchemy/orm/dependency.py +1302 -0
  154. sqlalchemy/orm/descriptor_props.py +1092 -0
  155. sqlalchemy/orm/dynamic.py +300 -0
  156. sqlalchemy/orm/evaluator.py +379 -0
  157. sqlalchemy/orm/events.py +3252 -0
  158. sqlalchemy/orm/exc.py +237 -0
  159. sqlalchemy/orm/identity.py +302 -0
  160. sqlalchemy/orm/instrumentation.py +754 -0
  161. sqlalchemy/orm/interfaces.py +1496 -0
  162. sqlalchemy/orm/loading.py +1686 -0
  163. sqlalchemy/orm/mapped_collection.py +557 -0
  164. sqlalchemy/orm/mapper.py +4444 -0
  165. sqlalchemy/orm/path_registry.py +809 -0
  166. sqlalchemy/orm/persistence.py +1788 -0
  167. sqlalchemy/orm/properties.py +935 -0
  168. sqlalchemy/orm/query.py +3459 -0
  169. sqlalchemy/orm/relationships.py +3508 -0
  170. sqlalchemy/orm/scoping.py +2148 -0
  171. sqlalchemy/orm/session.py +5280 -0
  172. sqlalchemy/orm/state.py +1168 -0
  173. sqlalchemy/orm/state_changes.py +196 -0
  174. sqlalchemy/orm/strategies.py +3470 -0
  175. sqlalchemy/orm/strategy_options.py +2568 -0
  176. sqlalchemy/orm/sync.py +164 -0
  177. sqlalchemy/orm/unitofwork.py +796 -0
  178. sqlalchemy/orm/util.py +2403 -0
  179. sqlalchemy/orm/writeonly.py +674 -0
  180. sqlalchemy/pool/__init__.py +44 -0
  181. sqlalchemy/pool/base.py +1524 -0
  182. sqlalchemy/pool/events.py +375 -0
  183. sqlalchemy/pool/impl.py +588 -0
  184. sqlalchemy/py.typed +0 -0
  185. sqlalchemy/schema.py +69 -0
  186. sqlalchemy/sql/__init__.py +145 -0
  187. sqlalchemy/sql/_dml_constructors.py +132 -0
  188. sqlalchemy/sql/_elements_constructors.py +1872 -0
  189. sqlalchemy/sql/_orm_types.py +20 -0
  190. sqlalchemy/sql/_py_util.py +75 -0
  191. sqlalchemy/sql/_selectable_constructors.py +763 -0
  192. sqlalchemy/sql/_typing.py +482 -0
  193. sqlalchemy/sql/annotation.py +587 -0
  194. sqlalchemy/sql/base.py +2293 -0
  195. sqlalchemy/sql/cache_key.py +1057 -0
  196. sqlalchemy/sql/coercions.py +1404 -0
  197. sqlalchemy/sql/compiler.py +8081 -0
  198. sqlalchemy/sql/crud.py +1752 -0
  199. sqlalchemy/sql/ddl.py +1444 -0
  200. sqlalchemy/sql/default_comparator.py +551 -0
  201. sqlalchemy/sql/dml.py +1850 -0
  202. sqlalchemy/sql/elements.py +5589 -0
  203. sqlalchemy/sql/events.py +458 -0
  204. sqlalchemy/sql/expression.py +159 -0
  205. sqlalchemy/sql/functions.py +2158 -0
  206. sqlalchemy/sql/lambdas.py +1442 -0
  207. sqlalchemy/sql/naming.py +209 -0
  208. sqlalchemy/sql/operators.py +2623 -0
  209. sqlalchemy/sql/roles.py +323 -0
  210. sqlalchemy/sql/schema.py +6222 -0
  211. sqlalchemy/sql/selectable.py +7265 -0
  212. sqlalchemy/sql/sqltypes.py +3930 -0
  213. sqlalchemy/sql/traversals.py +1024 -0
  214. sqlalchemy/sql/type_api.py +2368 -0
  215. sqlalchemy/sql/util.py +1485 -0
  216. sqlalchemy/sql/visitors.py +1164 -0
  217. sqlalchemy/testing/__init__.py +96 -0
  218. sqlalchemy/testing/assertions.py +994 -0
  219. sqlalchemy/testing/assertsql.py +520 -0
  220. sqlalchemy/testing/asyncio.py +135 -0
  221. sqlalchemy/testing/config.py +434 -0
  222. sqlalchemy/testing/engines.py +483 -0
  223. sqlalchemy/testing/entities.py +117 -0
  224. sqlalchemy/testing/exclusions.py +476 -0
  225. sqlalchemy/testing/fixtures/__init__.py +28 -0
  226. sqlalchemy/testing/fixtures/base.py +384 -0
  227. sqlalchemy/testing/fixtures/mypy.py +332 -0
  228. sqlalchemy/testing/fixtures/orm.py +227 -0
  229. sqlalchemy/testing/fixtures/sql.py +482 -0
  230. sqlalchemy/testing/pickleable.py +155 -0
  231. sqlalchemy/testing/plugin/__init__.py +6 -0
  232. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  233. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  234. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  235. sqlalchemy/testing/profiling.py +329 -0
  236. sqlalchemy/testing/provision.py +603 -0
  237. sqlalchemy/testing/requirements.py +1945 -0
  238. sqlalchemy/testing/schema.py +198 -0
  239. sqlalchemy/testing/suite/__init__.py +19 -0
  240. sqlalchemy/testing/suite/test_cte.py +237 -0
  241. sqlalchemy/testing/suite/test_ddl.py +389 -0
  242. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  243. sqlalchemy/testing/suite/test_dialect.py +776 -0
  244. sqlalchemy/testing/suite/test_insert.py +630 -0
  245. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  246. sqlalchemy/testing/suite/test_results.py +504 -0
  247. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  248. sqlalchemy/testing/suite/test_select.py +2010 -0
  249. sqlalchemy/testing/suite/test_sequence.py +317 -0
  250. sqlalchemy/testing/suite/test_types.py +2147 -0
  251. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  252. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  253. sqlalchemy/testing/util.py +535 -0
  254. sqlalchemy/testing/warnings.py +52 -0
  255. sqlalchemy/types.py +74 -0
  256. sqlalchemy/util/__init__.py +162 -0
  257. sqlalchemy/util/_collections.py +712 -0
  258. sqlalchemy/util/_concurrency_py3k.py +288 -0
  259. sqlalchemy/util/_has_cy.py +40 -0
  260. sqlalchemy/util/_py_collections.py +541 -0
  261. sqlalchemy/util/compat.py +421 -0
  262. sqlalchemy/util/concurrency.py +110 -0
  263. sqlalchemy/util/deprecations.py +401 -0
  264. sqlalchemy/util/langhelpers.py +2203 -0
  265. sqlalchemy/util/preloaded.py +150 -0
  266. sqlalchemy/util/queue.py +322 -0
  267. sqlalchemy/util/tool_support.py +201 -0
  268. sqlalchemy/util/topological.py +120 -0
  269. sqlalchemy/util/typing.py +734 -0
  270. sqlalchemy-2.0.47.dist-info/METADATA +243 -0
  271. sqlalchemy-2.0.47.dist-info/RECORD +274 -0
  272. sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
  273. sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
  274. sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1442 @@
1
+ # sql/lambdas.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: allow-untyped-defs, allow-untyped-calls
8
+
9
+ from __future__ import annotations
10
+
11
+ import collections.abc as collections_abc
12
+ import inspect
13
+ import itertools
14
+ import operator
15
+ import threading
16
+ import types
17
+ from types import CodeType
18
+ from typing import Any
19
+ from typing import Callable
20
+ from typing import cast
21
+ from typing import List
22
+ from typing import MutableMapping
23
+ from typing import Optional
24
+ from typing import Tuple
25
+ from typing import Type
26
+ from typing import TYPE_CHECKING
27
+ from typing import TypeVar
28
+ from typing import Union
29
+ import weakref
30
+
31
+ from . import cache_key as _cache_key
32
+ from . import coercions
33
+ from . import elements
34
+ from . import roles
35
+ from . import schema
36
+ from . import visitors
37
+ from .base import _clone
38
+ from .base import Executable
39
+ from .base import Options
40
+ from .cache_key import CacheConst
41
+ from .operators import ColumnOperators
42
+ from .. import exc
43
+ from .. import inspection
44
+ from .. import util
45
+ from ..util.typing import Literal
46
+
47
+
48
+ if TYPE_CHECKING:
49
+ from .elements import BindParameter
50
+ from .elements import ClauseElement
51
+ from .roles import SQLRole
52
+ from .visitors import _CloneCallableType
53
+
54
+ _LambdaCacheType = MutableMapping[
55
+ Tuple[Any, ...], Union["NonAnalyzedFunction", "AnalyzedFunction"]
56
+ ]
57
+ _BoundParameterGetter = Callable[..., Any]
58
+
59
+ _closure_per_cache_key: _LambdaCacheType = util.LRUCache(1000)
60
+
61
+
62
+ _LambdaType = Callable[[], Any]
63
+
64
+ _AnyLambdaType = Callable[..., Any]
65
+
66
+ _StmtLambdaType = Callable[[], Any]
67
+
68
+ _E = TypeVar("_E", bound=Executable)
69
+ _StmtLambdaElementType = Callable[[_E], Any]
70
+
71
+
72
+ class LambdaOptions(Options):
73
+ enable_tracking = True
74
+ track_closure_variables = True
75
+ track_on: Optional[object] = None
76
+ global_track_bound_values = True
77
+ track_bound_values = True
78
+ lambda_cache: Optional[_LambdaCacheType] = None
79
+
80
+
81
+ def lambda_stmt(
82
+ lmb: _StmtLambdaType,
83
+ enable_tracking: bool = True,
84
+ track_closure_variables: bool = True,
85
+ track_on: Optional[object] = None,
86
+ global_track_bound_values: bool = True,
87
+ track_bound_values: bool = True,
88
+ lambda_cache: Optional[_LambdaCacheType] = None,
89
+ ) -> StatementLambdaElement:
90
+ """Produce a SQL statement that is cached as a lambda.
91
+
92
+ The Python code object within the lambda is scanned for both Python
93
+ literals that will become bound parameters as well as closure variables
94
+ that refer to Core or ORM constructs that may vary. The lambda itself
95
+ will be invoked only once per particular set of constructs detected.
96
+
97
+ E.g.::
98
+
99
+ from sqlalchemy import lambda_stmt
100
+
101
+ stmt = lambda_stmt(lambda: table.select())
102
+ stmt += lambda s: s.where(table.c.id == 5)
103
+
104
+ result = connection.execute(stmt)
105
+
106
+ The object returned is an instance of :class:`_sql.StatementLambdaElement`.
107
+
108
+ .. versionadded:: 1.4
109
+
110
+ :param lmb: a Python function, typically a lambda, which takes no arguments
111
+ and returns a SQL expression construct
112
+ :param enable_tracking: when False, all scanning of the given lambda for
113
+ changes in closure variables or bound parameters is disabled. Use for
114
+ a lambda that produces the identical results in all cases with no
115
+ parameterization.
116
+ :param track_closure_variables: when False, changes in closure variables
117
+ within the lambda will not be scanned. Use for a lambda where the
118
+ state of its closure variables will never change the SQL structure
119
+ returned by the lambda.
120
+ :param track_bound_values: when False, bound parameter tracking will
121
+ be disabled for the given lambda. Use for a lambda that either does
122
+ not produce any bound values, or where the initial bound values never
123
+ change.
124
+ :param global_track_bound_values: when False, bound parameter tracking
125
+ will be disabled for the entire statement including additional links
126
+ added via the :meth:`_sql.StatementLambdaElement.add_criteria` method.
127
+ :param lambda_cache: a dictionary or other mapping-like object where
128
+ information about the lambda's Python code as well as the tracked closure
129
+ variables in the lambda itself will be stored. Defaults
130
+ to a global LRU cache. This cache is independent of the "compiled_cache"
131
+ used by the :class:`_engine.Connection` object.
132
+
133
+ .. seealso::
134
+
135
+ :ref:`engine_lambda_caching`
136
+
137
+
138
+ """
139
+
140
+ return StatementLambdaElement(
141
+ lmb,
142
+ roles.StatementRole,
143
+ LambdaOptions(
144
+ enable_tracking=enable_tracking,
145
+ track_on=track_on,
146
+ track_closure_variables=track_closure_variables,
147
+ global_track_bound_values=global_track_bound_values,
148
+ track_bound_values=track_bound_values,
149
+ lambda_cache=lambda_cache,
150
+ ),
151
+ )
152
+
153
+
154
+ class LambdaElement(elements.ClauseElement):
155
+ """A SQL construct where the state is stored as an un-invoked lambda.
156
+
157
+ The :class:`_sql.LambdaElement` is produced transparently whenever
158
+ passing lambda expressions into SQL constructs, such as::
159
+
160
+ stmt = select(table).where(lambda: table.c.col == parameter)
161
+
162
+ The :class:`_sql.LambdaElement` is the base of the
163
+ :class:`_sql.StatementLambdaElement` which represents a full statement
164
+ within a lambda.
165
+
166
+ .. versionadded:: 1.4
167
+
168
+ .. seealso::
169
+
170
+ :ref:`engine_lambda_caching`
171
+
172
+ """
173
+
174
+ __visit_name__ = "lambda_element"
175
+
176
+ _is_lambda_element = True
177
+
178
+ _traverse_internals = [
179
+ ("_resolved", visitors.InternalTraversal.dp_clauseelement)
180
+ ]
181
+
182
+ _transforms: Tuple[_CloneCallableType, ...] = ()
183
+
184
+ _resolved_bindparams: List[BindParameter[Any]]
185
+ parent_lambda: Optional[StatementLambdaElement] = None
186
+ closure_cache_key: Union[Tuple[Any, ...], Literal[CacheConst.NO_CACHE]]
187
+ role: Type[SQLRole]
188
+ _rec: Union[AnalyzedFunction, NonAnalyzedFunction]
189
+ fn: _AnyLambdaType
190
+ tracker_key: Tuple[CodeType, ...]
191
+
192
+ def __repr__(self):
193
+ return "%s(%r)" % (
194
+ self.__class__.__name__,
195
+ self.fn.__code__,
196
+ )
197
+
198
+ def __init__(
199
+ self,
200
+ fn: _LambdaType,
201
+ role: Type[SQLRole],
202
+ opts: Union[Type[LambdaOptions], LambdaOptions] = LambdaOptions,
203
+ apply_propagate_attrs: Optional[ClauseElement] = None,
204
+ ):
205
+ self.fn = fn
206
+ self.role = role
207
+ self.tracker_key = (fn.__code__,)
208
+ self.opts = opts
209
+
210
+ if apply_propagate_attrs is None and (role is roles.StatementRole):
211
+ apply_propagate_attrs = self
212
+
213
+ rec = self._retrieve_tracker_rec(fn, apply_propagate_attrs, opts)
214
+
215
+ if apply_propagate_attrs is not None:
216
+ propagate_attrs = rec.propagate_attrs
217
+ if propagate_attrs:
218
+ apply_propagate_attrs._propagate_attrs = propagate_attrs
219
+
220
+ def _retrieve_tracker_rec(self, fn, apply_propagate_attrs, opts):
221
+ lambda_cache = opts.lambda_cache
222
+ if lambda_cache is None:
223
+ lambda_cache = _closure_per_cache_key
224
+
225
+ tracker_key = self.tracker_key
226
+
227
+ fn = self.fn
228
+ closure = fn.__closure__
229
+ tracker = AnalyzedCode.get(
230
+ fn,
231
+ self,
232
+ opts,
233
+ )
234
+
235
+ bindparams: List[BindParameter[Any]]
236
+ self._resolved_bindparams = bindparams = []
237
+
238
+ if self.parent_lambda is not None:
239
+ parent_closure_cache_key = self.parent_lambda.closure_cache_key
240
+ else:
241
+ parent_closure_cache_key = ()
242
+
243
+ cache_key: Union[Tuple[Any, ...], Literal[CacheConst.NO_CACHE]]
244
+
245
+ if parent_closure_cache_key is not _cache_key.NO_CACHE:
246
+ anon_map = visitors.anon_map()
247
+ cache_key = tuple(
248
+ [
249
+ getter(closure, opts, anon_map, bindparams)
250
+ for getter in tracker.closure_trackers
251
+ ]
252
+ )
253
+
254
+ if _cache_key.NO_CACHE not in anon_map:
255
+ cache_key = parent_closure_cache_key + cache_key
256
+
257
+ self.closure_cache_key = cache_key
258
+
259
+ rec = lambda_cache.get(tracker_key + cache_key)
260
+ else:
261
+ cache_key = _cache_key.NO_CACHE
262
+ rec = None
263
+
264
+ else:
265
+ cache_key = _cache_key.NO_CACHE
266
+ rec = None
267
+
268
+ self.closure_cache_key = cache_key
269
+
270
+ if rec is None:
271
+ if cache_key is not _cache_key.NO_CACHE:
272
+ with AnalyzedCode._generation_mutex:
273
+ key = tracker_key + cache_key
274
+ if key not in lambda_cache:
275
+ rec = AnalyzedFunction(
276
+ tracker, self, apply_propagate_attrs, fn
277
+ )
278
+ rec.closure_bindparams = list(bindparams)
279
+ lambda_cache[key] = rec
280
+ else:
281
+ rec = lambda_cache[key]
282
+ else:
283
+ rec = NonAnalyzedFunction(self._invoke_user_fn(fn))
284
+
285
+ else:
286
+ bindparams[:] = [
287
+ orig_bind._with_value(new_bind.value, maintain_key=True)
288
+ for orig_bind, new_bind in zip(
289
+ rec.closure_bindparams, bindparams
290
+ )
291
+ ]
292
+
293
+ self._rec = rec
294
+
295
+ if cache_key is not _cache_key.NO_CACHE:
296
+ if self.parent_lambda is not None:
297
+ bindparams[:0] = self.parent_lambda._resolved_bindparams
298
+
299
+ lambda_element: Optional[LambdaElement] = self
300
+ while lambda_element is not None:
301
+ rec = lambda_element._rec
302
+ if rec.bindparam_trackers:
303
+ tracker_instrumented_fn = (
304
+ rec.tracker_instrumented_fn # type:ignore [union-attr] # noqa: E501
305
+ )
306
+ for tracker in rec.bindparam_trackers:
307
+ tracker(
308
+ lambda_element.fn,
309
+ tracker_instrumented_fn,
310
+ bindparams,
311
+ )
312
+ lambda_element = lambda_element.parent_lambda
313
+
314
+ return rec
315
+
316
+ def __getattr__(self, key):
317
+ return getattr(self._rec.expected_expr, key)
318
+
319
+ @property
320
+ def _is_sequence(self):
321
+ return self._rec.is_sequence
322
+
323
+ @property
324
+ def _select_iterable(self):
325
+ if self._is_sequence:
326
+ return itertools.chain.from_iterable(
327
+ [element._select_iterable for element in self._resolved]
328
+ )
329
+
330
+ else:
331
+ return self._resolved._select_iterable
332
+
333
+ @property
334
+ def _from_objects(self):
335
+ if self._is_sequence:
336
+ return itertools.chain.from_iterable(
337
+ [element._from_objects for element in self._resolved]
338
+ )
339
+
340
+ else:
341
+ return self._resolved._from_objects
342
+
343
+ def _param_dict(self):
344
+ return {b.key: b.value for b in self._resolved_bindparams}
345
+
346
+ def _setup_binds_for_tracked_expr(self, expr):
347
+ bindparam_lookup = {b.key: b for b in self._resolved_bindparams}
348
+
349
+ def replace(
350
+ element: Optional[visitors.ExternallyTraversible], **kw: Any
351
+ ) -> Optional[visitors.ExternallyTraversible]:
352
+ if isinstance(element, elements.BindParameter):
353
+ if element.key in bindparam_lookup:
354
+ bind = bindparam_lookup[element.key]
355
+ if element.expanding:
356
+ bind.expanding = True
357
+ bind.expand_op = element.expand_op
358
+ bind.type = element.type
359
+ return bind
360
+
361
+ return None
362
+
363
+ if self._rec.is_sequence:
364
+ expr = [
365
+ visitors.replacement_traverse(sub_expr, {}, replace)
366
+ for sub_expr in expr
367
+ ]
368
+ elif getattr(expr, "is_clause_element", False):
369
+ expr = visitors.replacement_traverse(expr, {}, replace)
370
+
371
+ return expr
372
+
373
+ def _copy_internals(
374
+ self,
375
+ clone: _CloneCallableType = _clone,
376
+ deferred_copy_internals: Optional[_CloneCallableType] = None,
377
+ **kw: Any,
378
+ ) -> None:
379
+ # TODO: this needs A LOT of tests
380
+ self._resolved = clone(
381
+ self._resolved,
382
+ deferred_copy_internals=deferred_copy_internals,
383
+ **kw,
384
+ )
385
+
386
+ @util.memoized_property
387
+ def _resolved(self):
388
+ expr = self._rec.expected_expr
389
+
390
+ if self._resolved_bindparams:
391
+ expr = self._setup_binds_for_tracked_expr(expr)
392
+
393
+ return expr
394
+
395
+ def _gen_cache_key(self, anon_map, bindparams):
396
+ if self.closure_cache_key is _cache_key.NO_CACHE:
397
+ anon_map[_cache_key.NO_CACHE] = True
398
+ return None
399
+
400
+ cache_key = (
401
+ self.fn.__code__,
402
+ self.__class__,
403
+ ) + self.closure_cache_key
404
+
405
+ parent = self.parent_lambda
406
+
407
+ while parent is not None:
408
+ assert parent.closure_cache_key is not CacheConst.NO_CACHE
409
+ parent_closure_cache_key: Tuple[Any, ...] = (
410
+ parent.closure_cache_key
411
+ )
412
+
413
+ cache_key = (
414
+ (parent.fn.__code__,) + parent_closure_cache_key + cache_key
415
+ )
416
+
417
+ parent = parent.parent_lambda
418
+
419
+ if self._resolved_bindparams:
420
+ bindparams.extend(self._resolved_bindparams)
421
+ return cache_key
422
+
423
+ def _invoke_user_fn(self, fn: _AnyLambdaType, *arg: Any) -> ClauseElement:
424
+ return fn() # type: ignore[no-any-return]
425
+
426
+
427
+ class DeferredLambdaElement(LambdaElement):
428
+ """A LambdaElement where the lambda accepts arguments and is
429
+ invoked within the compile phase with special context.
430
+
431
+ This lambda doesn't normally produce its real SQL expression outside of the
432
+ compile phase. It is passed a fixed set of initial arguments
433
+ so that it can generate a sample expression.
434
+
435
+ """
436
+
437
+ def __init__(
438
+ self,
439
+ fn: _AnyLambdaType,
440
+ role: Type[roles.SQLRole],
441
+ opts: Union[Type[LambdaOptions], LambdaOptions] = LambdaOptions,
442
+ lambda_args: Tuple[Any, ...] = (),
443
+ ):
444
+ self.lambda_args = lambda_args
445
+ super().__init__(fn, role, opts)
446
+
447
+ def _invoke_user_fn(self, fn, *arg):
448
+ return fn(*self.lambda_args)
449
+
450
+ def _resolve_with_args(self, *lambda_args: Any) -> ClauseElement:
451
+ assert isinstance(self._rec, AnalyzedFunction)
452
+ tracker_fn = self._rec.tracker_instrumented_fn
453
+ expr = tracker_fn(*lambda_args)
454
+
455
+ expr = coercions.expect(self.role, expr)
456
+
457
+ expr = self._setup_binds_for_tracked_expr(expr)
458
+
459
+ # this validation is getting very close, but not quite, to achieving
460
+ # #5767. The problem is if the base lambda uses an unnamed column
461
+ # as is very common with mixins, the parameter name is different
462
+ # and it produces a false positive; that is, for the documented case
463
+ # that is exactly what people will be doing, it doesn't work, so
464
+ # I'm not really sure how to handle this right now.
465
+ # expected_binds = [
466
+ # b._orig_key
467
+ # for b in self._rec.expr._generate_cache_key()[1]
468
+ # if b.required
469
+ # ]
470
+ # got_binds = [
471
+ # b._orig_key for b in expr._generate_cache_key()[1] if b.required
472
+ # ]
473
+ # if expected_binds != got_binds:
474
+ # raise exc.InvalidRequestError(
475
+ # "Lambda callable at %s produced a different set of bound "
476
+ # "parameters than its original run: %s"
477
+ # % (self.fn.__code__, ", ".join(got_binds))
478
+ # )
479
+
480
+ # TODO: TEST TEST TEST, this is very out there
481
+ for deferred_copy_internals in self._transforms:
482
+ expr = deferred_copy_internals(expr)
483
+
484
+ return expr # type: ignore
485
+
486
+ def _copy_internals(
487
+ self, clone=_clone, deferred_copy_internals=None, **kw
488
+ ):
489
+ super()._copy_internals(
490
+ clone=clone,
491
+ deferred_copy_internals=deferred_copy_internals, # **kw
492
+ opts=kw,
493
+ )
494
+
495
+ # TODO: A LOT A LOT of tests. for _resolve_with_args, we don't know
496
+ # our expression yet. so hold onto the replacement
497
+ if deferred_copy_internals:
498
+ self._transforms += (deferred_copy_internals,)
499
+
500
+
501
+ class StatementLambdaElement(
502
+ roles.AllowsLambdaRole, LambdaElement, Executable
503
+ ):
504
+ """Represent a composable SQL statement as a :class:`_sql.LambdaElement`.
505
+
506
+ The :class:`_sql.StatementLambdaElement` is constructed using the
507
+ :func:`_sql.lambda_stmt` function::
508
+
509
+
510
+ from sqlalchemy import lambda_stmt
511
+
512
+ stmt = lambda_stmt(lambda: select(table))
513
+
514
+ Once constructed, additional criteria can be built onto the statement
515
+ by adding subsequent lambdas, which accept the existing statement
516
+ object as a single parameter::
517
+
518
+ stmt += lambda s: s.where(table.c.col == parameter)
519
+
520
+ .. versionadded:: 1.4
521
+
522
+ .. seealso::
523
+
524
+ :ref:`engine_lambda_caching`
525
+
526
+ """
527
+
528
+ if TYPE_CHECKING:
529
+
530
+ def __init__(
531
+ self,
532
+ fn: _StmtLambdaType,
533
+ role: Type[SQLRole],
534
+ opts: Union[Type[LambdaOptions], LambdaOptions] = LambdaOptions,
535
+ apply_propagate_attrs: Optional[ClauseElement] = None,
536
+ ): ...
537
+
538
+ def __add__(
539
+ self, other: _StmtLambdaElementType[Any]
540
+ ) -> StatementLambdaElement:
541
+ return self.add_criteria(other)
542
+
543
+ def add_criteria(
544
+ self,
545
+ other: _StmtLambdaElementType[Any],
546
+ enable_tracking: bool = True,
547
+ track_on: Optional[Any] = None,
548
+ track_closure_variables: bool = True,
549
+ track_bound_values: bool = True,
550
+ ) -> StatementLambdaElement:
551
+ """Add new criteria to this :class:`_sql.StatementLambdaElement`.
552
+
553
+ E.g.::
554
+
555
+ >>> def my_stmt(parameter):
556
+ ... stmt = lambda_stmt(
557
+ ... lambda: select(table.c.x, table.c.y),
558
+ ... )
559
+ ... stmt = stmt.add_criteria(lambda: table.c.x > parameter)
560
+ ... return stmt
561
+
562
+ The :meth:`_sql.StatementLambdaElement.add_criteria` method is
563
+ equivalent to using the Python addition operator to add a new
564
+ lambda, except that additional arguments may be added including
565
+ ``track_closure_values`` and ``track_on``::
566
+
567
+ >>> def my_stmt(self, foo):
568
+ ... stmt = lambda_stmt(
569
+ ... lambda: select(func.max(foo.x, foo.y)),
570
+ ... track_closure_variables=False,
571
+ ... )
572
+ ... stmt = stmt.add_criteria(lambda: self.where_criteria, track_on=[self])
573
+ ... return stmt
574
+
575
+ See :func:`_sql.lambda_stmt` for a description of the parameters
576
+ accepted.
577
+
578
+ """ # noqa: E501
579
+
580
+ opts = self.opts + dict(
581
+ enable_tracking=enable_tracking,
582
+ track_closure_variables=track_closure_variables,
583
+ global_track_bound_values=self.opts.global_track_bound_values,
584
+ track_on=track_on,
585
+ track_bound_values=track_bound_values,
586
+ )
587
+
588
+ return LinkedLambdaElement(other, parent_lambda=self, opts=opts)
589
+
590
+ def _execute_on_connection(
591
+ self, connection, distilled_params, execution_options
592
+ ):
593
+ if TYPE_CHECKING:
594
+ assert isinstance(self._rec.expected_expr, ClauseElement)
595
+ if self._rec.expected_expr.supports_execution:
596
+ return connection._execute_clauseelement(
597
+ self, distilled_params, execution_options
598
+ )
599
+ else:
600
+ raise exc.ObjectNotExecutableError(self)
601
+
602
+ @property
603
+ def _proxied(self) -> Any:
604
+ return self._rec_expected_expr
605
+
606
+ @property
607
+ def _with_options(self): # type: ignore[override]
608
+ return self._proxied._with_options
609
+
610
+ @property
611
+ def _effective_plugin_target(self):
612
+ return self._proxied._effective_plugin_target
613
+
614
+ @property
615
+ def _execution_options(self): # type: ignore[override]
616
+ return self._proxied._execution_options
617
+
618
+ @property
619
+ def _all_selected_columns(self):
620
+ return self._proxied._all_selected_columns
621
+
622
+ @property
623
+ def is_select(self): # type: ignore[override]
624
+ return self._proxied.is_select
625
+
626
+ @property
627
+ def is_update(self): # type: ignore[override]
628
+ return self._proxied.is_update
629
+
630
+ @property
631
+ def is_insert(self): # type: ignore[override]
632
+ return self._proxied.is_insert
633
+
634
+ @property
635
+ def is_text(self): # type: ignore[override]
636
+ return self._proxied.is_text
637
+
638
+ @property
639
+ def is_delete(self): # type: ignore[override]
640
+ return self._proxied.is_delete
641
+
642
+ @property
643
+ def is_dml(self): # type: ignore[override]
644
+ return self._proxied.is_dml
645
+
646
+ def spoil(self) -> NullLambdaStatement:
647
+ """Return a new :class:`.StatementLambdaElement` that will run
648
+ all lambdas unconditionally each time.
649
+
650
+ """
651
+ return NullLambdaStatement(self.fn())
652
+
653
+
654
+ class NullLambdaStatement(roles.AllowsLambdaRole, elements.ClauseElement):
655
+ """Provides the :class:`.StatementLambdaElement` API but does not
656
+ cache or analyze lambdas.
657
+
658
+ the lambdas are instead invoked immediately.
659
+
660
+ The intended use is to isolate issues that may arise when using
661
+ lambda statements.
662
+
663
+ """
664
+
665
+ __visit_name__ = "lambda_element"
666
+
667
+ _is_lambda_element = True
668
+
669
+ _traverse_internals = [
670
+ ("_resolved", visitors.InternalTraversal.dp_clauseelement)
671
+ ]
672
+
673
+ def __init__(self, statement):
674
+ self._resolved = statement
675
+ self._propagate_attrs = statement._propagate_attrs
676
+
677
+ def __getattr__(self, key):
678
+ return getattr(self._resolved, key)
679
+
680
+ def __add__(self, other):
681
+ statement = other(self._resolved)
682
+
683
+ return NullLambdaStatement(statement)
684
+
685
+ def add_criteria(self, other, **kw):
686
+ statement = other(self._resolved)
687
+
688
+ return NullLambdaStatement(statement)
689
+
690
+ def _execute_on_connection(
691
+ self, connection, distilled_params, execution_options
692
+ ):
693
+ if self._resolved.supports_execution:
694
+ return connection._execute_clauseelement(
695
+ self, distilled_params, execution_options
696
+ )
697
+ else:
698
+ raise exc.ObjectNotExecutableError(self)
699
+
700
+
701
+ class LinkedLambdaElement(StatementLambdaElement):
702
+ """Represent subsequent links of a :class:`.StatementLambdaElement`."""
703
+
704
+ parent_lambda: StatementLambdaElement
705
+
706
+ def __init__(
707
+ self,
708
+ fn: _StmtLambdaElementType[Any],
709
+ parent_lambda: StatementLambdaElement,
710
+ opts: Union[Type[LambdaOptions], LambdaOptions],
711
+ ):
712
+ self.opts = opts
713
+ self.fn = fn
714
+ self.parent_lambda = parent_lambda
715
+
716
+ self.tracker_key = parent_lambda.tracker_key + (fn.__code__,)
717
+ self._retrieve_tracker_rec(fn, self, opts)
718
+ self._propagate_attrs = parent_lambda._propagate_attrs
719
+
720
+ def _invoke_user_fn(self, fn, *arg):
721
+ return fn(self.parent_lambda._resolved)
722
+
723
+
724
+ class AnalyzedCode:
725
+ __slots__ = (
726
+ "track_closure_variables",
727
+ "track_bound_values",
728
+ "bindparam_trackers",
729
+ "closure_trackers",
730
+ "build_py_wrappers",
731
+ )
732
+ _fns: weakref.WeakKeyDictionary[CodeType, AnalyzedCode] = (
733
+ weakref.WeakKeyDictionary()
734
+ )
735
+
736
+ _generation_mutex = threading.RLock()
737
+
738
+ @classmethod
739
+ def get(cls, fn, lambda_element, lambda_kw, **kw):
740
+ try:
741
+ # TODO: validate kw haven't changed?
742
+ return cls._fns[fn.__code__]
743
+ except KeyError:
744
+ pass
745
+
746
+ with cls._generation_mutex:
747
+ # check for other thread already created object
748
+ if fn.__code__ in cls._fns:
749
+ return cls._fns[fn.__code__]
750
+
751
+ analyzed: AnalyzedCode
752
+ cls._fns[fn.__code__] = analyzed = AnalyzedCode(
753
+ fn, lambda_element, lambda_kw, **kw
754
+ )
755
+ return analyzed
756
+
757
+ def __init__(self, fn, lambda_element, opts):
758
+ if inspect.ismethod(fn):
759
+ raise exc.ArgumentError(
760
+ "Method %s may not be passed as a SQL expression" % fn
761
+ )
762
+ closure = fn.__closure__
763
+
764
+ self.track_bound_values = (
765
+ opts.track_bound_values and opts.global_track_bound_values
766
+ )
767
+ enable_tracking = opts.enable_tracking
768
+ track_on = opts.track_on
769
+ track_closure_variables = opts.track_closure_variables
770
+
771
+ self.track_closure_variables = track_closure_variables and not track_on
772
+
773
+ # a list of callables generated from _bound_parameter_getter_*
774
+ # functions. Each of these uses a PyWrapper object to retrieve
775
+ # a parameter value
776
+ self.bindparam_trackers = []
777
+
778
+ # a list of callables generated from _cache_key_getter_* functions
779
+ # these callables work to generate a cache key for the lambda
780
+ # based on what's inside its closure variables.
781
+ self.closure_trackers = []
782
+
783
+ self.build_py_wrappers = []
784
+
785
+ if enable_tracking:
786
+ if track_on:
787
+ self._init_track_on(track_on)
788
+
789
+ self._init_globals(fn)
790
+
791
+ if closure:
792
+ self._init_closure(fn)
793
+
794
+ self._setup_additional_closure_trackers(fn, lambda_element, opts)
795
+
796
+ def _init_track_on(self, track_on):
797
+ self.closure_trackers.extend(
798
+ self._cache_key_getter_track_on(idx, elem)
799
+ for idx, elem in enumerate(track_on)
800
+ )
801
+
802
+ def _init_globals(self, fn):
803
+ build_py_wrappers = self.build_py_wrappers
804
+ bindparam_trackers = self.bindparam_trackers
805
+ track_bound_values = self.track_bound_values
806
+
807
+ for name in fn.__code__.co_names:
808
+ if name not in fn.__globals__:
809
+ continue
810
+
811
+ _bound_value = self._roll_down_to_literal(fn.__globals__[name])
812
+
813
+ if coercions._deep_is_literal(_bound_value):
814
+ build_py_wrappers.append((name, None))
815
+ if track_bound_values:
816
+ bindparam_trackers.append(
817
+ self._bound_parameter_getter_func_globals(name)
818
+ )
819
+
820
+ def _init_closure(self, fn):
821
+ build_py_wrappers = self.build_py_wrappers
822
+ closure = fn.__closure__
823
+
824
+ track_bound_values = self.track_bound_values
825
+ track_closure_variables = self.track_closure_variables
826
+ bindparam_trackers = self.bindparam_trackers
827
+ closure_trackers = self.closure_trackers
828
+
829
+ for closure_index, (fv, cell) in enumerate(
830
+ zip(fn.__code__.co_freevars, closure)
831
+ ):
832
+ _bound_value = self._roll_down_to_literal(cell.cell_contents)
833
+
834
+ if coercions._deep_is_literal(_bound_value):
835
+ build_py_wrappers.append((fv, closure_index))
836
+ if track_bound_values:
837
+ bindparam_trackers.append(
838
+ self._bound_parameter_getter_func_closure(
839
+ fv, closure_index
840
+ )
841
+ )
842
+ else:
843
+ # for normal cell contents, add them to a list that
844
+ # we can compare later when we get new lambdas. if
845
+ # any identities have changed, then we will
846
+ # recalculate the whole lambda and run it again.
847
+
848
+ if track_closure_variables:
849
+ closure_trackers.append(
850
+ self._cache_key_getter_closure_variable(
851
+ fn, fv, closure_index, cell.cell_contents
852
+ )
853
+ )
854
+
855
+ def _setup_additional_closure_trackers(self, fn, lambda_element, opts):
856
+ # an additional step is to actually run the function, then
857
+ # go through the PyWrapper objects that were set up to catch a bound
858
+ # parameter. then if they *didn't* make a param, oh they're another
859
+ # object in the closure we have to track for our cache key. so
860
+ # create trackers to catch those.
861
+
862
+ analyzed_function = AnalyzedFunction(
863
+ self,
864
+ lambda_element,
865
+ None,
866
+ fn,
867
+ )
868
+
869
+ closure_trackers = self.closure_trackers
870
+
871
+ for pywrapper in analyzed_function.closure_pywrappers:
872
+ if not pywrapper._sa__has_param:
873
+ closure_trackers.append(
874
+ self._cache_key_getter_tracked_literal(fn, pywrapper)
875
+ )
876
+
877
+ @classmethod
878
+ def _roll_down_to_literal(cls, element):
879
+ is_clause_element = hasattr(element, "__clause_element__")
880
+
881
+ if is_clause_element:
882
+ while not isinstance(
883
+ element, (elements.ClauseElement, schema.SchemaItem, type)
884
+ ):
885
+ try:
886
+ element = element.__clause_element__()
887
+ except AttributeError:
888
+ break
889
+
890
+ if not is_clause_element:
891
+ insp = inspection.inspect(element, raiseerr=False)
892
+ if insp is not None:
893
+ try:
894
+ return insp.__clause_element__()
895
+ except AttributeError:
896
+ return insp
897
+
898
+ # TODO: should we coerce consts None/True/False here?
899
+ return element
900
+ else:
901
+ return element
902
+
903
+ def _bound_parameter_getter_func_globals(self, name):
904
+ """Return a getter that will extend a list of bound parameters
905
+ with new entries from the ``__globals__`` collection of a particular
906
+ lambda.
907
+
908
+ """
909
+
910
+ def extract_parameter_value(
911
+ current_fn, tracker_instrumented_fn, result
912
+ ):
913
+ wrapper = tracker_instrumented_fn.__globals__[name]
914
+ object.__getattribute__(wrapper, "_extract_bound_parameters")(
915
+ current_fn.__globals__[name], result
916
+ )
917
+
918
+ return extract_parameter_value
919
+
920
+ def _bound_parameter_getter_func_closure(self, name, closure_index):
921
+ """Return a getter that will extend a list of bound parameters
922
+ with new entries from the ``__closure__`` collection of a particular
923
+ lambda.
924
+
925
+ """
926
+
927
+ def extract_parameter_value(
928
+ current_fn, tracker_instrumented_fn, result
929
+ ):
930
+ wrapper = tracker_instrumented_fn.__closure__[
931
+ closure_index
932
+ ].cell_contents
933
+ object.__getattribute__(wrapper, "_extract_bound_parameters")(
934
+ current_fn.__closure__[closure_index].cell_contents, result
935
+ )
936
+
937
+ return extract_parameter_value
938
+
939
+ def _cache_key_getter_track_on(self, idx, elem):
940
+ """Return a getter that will extend a cache key with new entries
941
+ from the "track_on" parameter passed to a :class:`.LambdaElement`.
942
+
943
+ """
944
+
945
+ if isinstance(elem, tuple):
946
+ # tuple must contain hascachekey elements
947
+ def get(closure, opts, anon_map, bindparams):
948
+ return tuple(
949
+ tup_elem._gen_cache_key(anon_map, bindparams)
950
+ for tup_elem in opts.track_on[idx]
951
+ )
952
+
953
+ elif isinstance(elem, _cache_key.HasCacheKey):
954
+
955
+ def get(closure, opts, anon_map, bindparams):
956
+ return opts.track_on[idx]._gen_cache_key(anon_map, bindparams)
957
+
958
+ else:
959
+
960
+ def get(closure, opts, anon_map, bindparams):
961
+ return opts.track_on[idx]
962
+
963
+ return get
964
+
965
+ def _cache_key_getter_closure_variable(
966
+ self,
967
+ fn,
968
+ variable_name,
969
+ idx,
970
+ cell_contents,
971
+ use_clause_element=False,
972
+ use_inspect=False,
973
+ ):
974
+ """Return a getter that will extend a cache key with new entries
975
+ from the ``__closure__`` collection of a particular lambda.
976
+
977
+ """
978
+
979
+ if isinstance(cell_contents, _cache_key.HasCacheKey):
980
+
981
+ def get(closure, opts, anon_map, bindparams):
982
+ obj = closure[idx].cell_contents
983
+ if use_inspect:
984
+ obj = inspection.inspect(obj)
985
+ elif use_clause_element:
986
+ while hasattr(obj, "__clause_element__"):
987
+ if not getattr(obj, "is_clause_element", False):
988
+ obj = obj.__clause_element__()
989
+
990
+ return obj._gen_cache_key(anon_map, bindparams)
991
+
992
+ elif isinstance(cell_contents, types.FunctionType):
993
+
994
+ def get(closure, opts, anon_map, bindparams):
995
+ return closure[idx].cell_contents.__code__
996
+
997
+ elif isinstance(cell_contents, collections_abc.Sequence):
998
+
999
+ def get(closure, opts, anon_map, bindparams):
1000
+ contents = closure[idx].cell_contents
1001
+
1002
+ try:
1003
+ return tuple(
1004
+ elem._gen_cache_key(anon_map, bindparams)
1005
+ for elem in contents
1006
+ )
1007
+ except AttributeError as ae:
1008
+ self._raise_for_uncacheable_closure_variable(
1009
+ variable_name, fn, from_=ae
1010
+ )
1011
+
1012
+ else:
1013
+ # if the object is a mapped class or aliased class, or some
1014
+ # other object in the ORM realm of things like that, imitate
1015
+ # the logic used in coercions.expect() to roll it down to the
1016
+ # SQL element
1017
+ element = cell_contents
1018
+ is_clause_element = False
1019
+ while hasattr(element, "__clause_element__"):
1020
+ is_clause_element = True
1021
+ if not getattr(element, "is_clause_element", False):
1022
+ element = element.__clause_element__()
1023
+ else:
1024
+ break
1025
+
1026
+ if not is_clause_element:
1027
+ insp = inspection.inspect(element, raiseerr=False)
1028
+ if insp is not None:
1029
+ return self._cache_key_getter_closure_variable(
1030
+ fn, variable_name, idx, insp, use_inspect=True
1031
+ )
1032
+ else:
1033
+ return self._cache_key_getter_closure_variable(
1034
+ fn, variable_name, idx, element, use_clause_element=True
1035
+ )
1036
+
1037
+ self._raise_for_uncacheable_closure_variable(variable_name, fn)
1038
+
1039
+ return get
1040
+
1041
+ def _raise_for_uncacheable_closure_variable(
1042
+ self, variable_name, fn, from_=None
1043
+ ):
1044
+ raise exc.InvalidRequestError(
1045
+ "Closure variable named '%s' inside of lambda callable %s "
1046
+ "does not refer to a cacheable SQL element, and also does not "
1047
+ "appear to be serving as a SQL literal bound value based on "
1048
+ "the default "
1049
+ "SQL expression returned by the function. This variable "
1050
+ "needs to remain outside the scope of a SQL-generating lambda "
1051
+ "so that a proper cache key may be generated from the "
1052
+ "lambda's state. Evaluate this variable outside of the "
1053
+ "lambda, set track_on=[<elements>] to explicitly select "
1054
+ "closure elements to track, or set "
1055
+ "track_closure_variables=False to exclude "
1056
+ "closure variables from being part of the cache key."
1057
+ % (variable_name, fn.__code__),
1058
+ ) from from_
1059
+
1060
+ def _cache_key_getter_tracked_literal(self, fn, pytracker):
1061
+ """Return a getter that will extend a cache key with new entries
1062
+ from the ``__closure__`` collection of a particular lambda.
1063
+
1064
+ this getter differs from _cache_key_getter_closure_variable
1065
+ in that these are detected after the function is run, and PyWrapper
1066
+ objects have recorded that a particular literal value is in fact
1067
+ not being interpreted as a bound parameter.
1068
+
1069
+ """
1070
+
1071
+ elem = pytracker._sa__to_evaluate
1072
+ closure_index = pytracker._sa__closure_index
1073
+ variable_name = pytracker._sa__name
1074
+
1075
+ return self._cache_key_getter_closure_variable(
1076
+ fn, variable_name, closure_index, elem
1077
+ )
1078
+
1079
+
1080
+ class NonAnalyzedFunction:
1081
+ __slots__ = ("expr",)
1082
+
1083
+ closure_bindparams: Optional[List[BindParameter[Any]]] = None
1084
+ bindparam_trackers: Optional[List[_BoundParameterGetter]] = None
1085
+
1086
+ is_sequence = False
1087
+
1088
+ expr: ClauseElement
1089
+
1090
+ def __init__(self, expr: ClauseElement):
1091
+ self.expr = expr
1092
+
1093
+ @property
1094
+ def expected_expr(self) -> ClauseElement:
1095
+ return self.expr
1096
+
1097
+
1098
+ class AnalyzedFunction:
1099
+ __slots__ = (
1100
+ "analyzed_code",
1101
+ "fn",
1102
+ "closure_pywrappers",
1103
+ "tracker_instrumented_fn",
1104
+ "expr",
1105
+ "bindparam_trackers",
1106
+ "expected_expr",
1107
+ "is_sequence",
1108
+ "propagate_attrs",
1109
+ "closure_bindparams",
1110
+ )
1111
+
1112
+ closure_bindparams: Optional[List[BindParameter[Any]]]
1113
+ expected_expr: Union[ClauseElement, List[ClauseElement]]
1114
+ bindparam_trackers: Optional[List[_BoundParameterGetter]]
1115
+
1116
+ def __init__(
1117
+ self,
1118
+ analyzed_code,
1119
+ lambda_element,
1120
+ apply_propagate_attrs,
1121
+ fn,
1122
+ ):
1123
+ self.analyzed_code = analyzed_code
1124
+ self.fn = fn
1125
+
1126
+ self.bindparam_trackers = analyzed_code.bindparam_trackers
1127
+
1128
+ self._instrument_and_run_function(lambda_element)
1129
+
1130
+ self._coerce_expression(lambda_element, apply_propagate_attrs)
1131
+
1132
+ def _instrument_and_run_function(self, lambda_element):
1133
+ analyzed_code = self.analyzed_code
1134
+
1135
+ fn = self.fn
1136
+ self.closure_pywrappers = closure_pywrappers = []
1137
+
1138
+ build_py_wrappers = analyzed_code.build_py_wrappers
1139
+
1140
+ if not build_py_wrappers:
1141
+ self.tracker_instrumented_fn = tracker_instrumented_fn = fn
1142
+ self.expr = lambda_element._invoke_user_fn(tracker_instrumented_fn)
1143
+ else:
1144
+ track_closure_variables = analyzed_code.track_closure_variables
1145
+ closure = fn.__closure__
1146
+
1147
+ # will form the __closure__ of the function when we rebuild it
1148
+ if closure:
1149
+ new_closure = {
1150
+ fv: cell.cell_contents
1151
+ for fv, cell in zip(fn.__code__.co_freevars, closure)
1152
+ }
1153
+ else:
1154
+ new_closure = {}
1155
+
1156
+ # will form the __globals__ of the function when we rebuild it
1157
+ new_globals = fn.__globals__.copy()
1158
+
1159
+ for name, closure_index in build_py_wrappers:
1160
+ if closure_index is not None:
1161
+ value = closure[closure_index].cell_contents
1162
+ new_closure[name] = bind = PyWrapper(
1163
+ fn,
1164
+ name,
1165
+ value,
1166
+ closure_index=closure_index,
1167
+ track_bound_values=(
1168
+ self.analyzed_code.track_bound_values
1169
+ ),
1170
+ )
1171
+ if track_closure_variables:
1172
+ closure_pywrappers.append(bind)
1173
+ else:
1174
+ value = fn.__globals__[name]
1175
+ new_globals[name] = PyWrapper(fn, name, value)
1176
+
1177
+ # rewrite the original fn. things that look like they will
1178
+ # become bound parameters are wrapped in a PyWrapper.
1179
+ self.tracker_instrumented_fn = tracker_instrumented_fn = (
1180
+ self._rewrite_code_obj(
1181
+ fn,
1182
+ [new_closure[name] for name in fn.__code__.co_freevars],
1183
+ new_globals,
1184
+ )
1185
+ )
1186
+
1187
+ # now invoke the function. This will give us a new SQL
1188
+ # expression, but all the places that there would be a bound
1189
+ # parameter, the PyWrapper in its place will give us a bind
1190
+ # with a predictable name we can match up later.
1191
+
1192
+ # additionally, each PyWrapper will log that it did in fact
1193
+ # create a parameter, otherwise, it's some kind of Python
1194
+ # object in the closure and we want to track that, to make
1195
+ # sure it doesn't change to something else, or if it does,
1196
+ # that we create a different tracked function with that
1197
+ # variable.
1198
+ self.expr = lambda_element._invoke_user_fn(tracker_instrumented_fn)
1199
+
1200
+ def _coerce_expression(self, lambda_element, apply_propagate_attrs):
1201
+ """Run the tracker-generated expression through coercion rules.
1202
+
1203
+ After the user-defined lambda has been invoked to produce a statement
1204
+ for reuse, run it through coercion rules to both check that it's the
1205
+ correct type of object and also to coerce it to its useful form.
1206
+
1207
+ """
1208
+
1209
+ parent_lambda = lambda_element.parent_lambda
1210
+ expr = self.expr
1211
+
1212
+ if parent_lambda is None:
1213
+ if isinstance(expr, collections_abc.Sequence):
1214
+ self.expected_expr = [
1215
+ cast(
1216
+ "ClauseElement",
1217
+ coercions.expect(
1218
+ lambda_element.role,
1219
+ sub_expr,
1220
+ apply_propagate_attrs=apply_propagate_attrs,
1221
+ ),
1222
+ )
1223
+ for sub_expr in expr
1224
+ ]
1225
+ self.is_sequence = True
1226
+ else:
1227
+ self.expected_expr = cast(
1228
+ "ClauseElement",
1229
+ coercions.expect(
1230
+ lambda_element.role,
1231
+ expr,
1232
+ apply_propagate_attrs=apply_propagate_attrs,
1233
+ ),
1234
+ )
1235
+ self.is_sequence = False
1236
+ else:
1237
+ self.expected_expr = expr
1238
+ self.is_sequence = False
1239
+
1240
+ if apply_propagate_attrs is not None:
1241
+ self.propagate_attrs = apply_propagate_attrs._propagate_attrs
1242
+ else:
1243
+ self.propagate_attrs = util.EMPTY_DICT
1244
+
1245
+ def _rewrite_code_obj(self, f, cell_values, globals_):
1246
+ """Return a copy of f, with a new closure and new globals
1247
+
1248
+ yes it works in pypy :P
1249
+
1250
+ """
1251
+
1252
+ argrange = range(len(cell_values))
1253
+
1254
+ code = "def make_cells():\n"
1255
+ if cell_values:
1256
+ code += " (%s) = (%s)\n" % (
1257
+ ", ".join("i%d" % i for i in argrange),
1258
+ ", ".join("o%d" % i for i in argrange),
1259
+ )
1260
+ code += " def closure():\n"
1261
+ code += " return %s\n" % ", ".join("i%d" % i for i in argrange)
1262
+ code += " return closure.__closure__"
1263
+ vars_ = {"o%d" % i: cell_values[i] for i in argrange}
1264
+ exec(code, vars_, vars_)
1265
+ closure = vars_["make_cells"]()
1266
+
1267
+ func = type(f)(
1268
+ f.__code__, globals_, f.__name__, f.__defaults__, closure
1269
+ )
1270
+ func.__annotations__ = f.__annotations__
1271
+ func.__kwdefaults__ = f.__kwdefaults__
1272
+ func.__doc__ = f.__doc__
1273
+ func.__module__ = f.__module__
1274
+
1275
+ return func
1276
+
1277
+
1278
+ class PyWrapper(ColumnOperators):
1279
+ """A wrapper object that is injected into the ``__globals__`` and
1280
+ ``__closure__`` of a Python function.
1281
+
1282
+ When the function is instrumented with :class:`.PyWrapper` objects, it is
1283
+ then invoked just once in order to set up the wrappers. We look through
1284
+ all the :class:`.PyWrapper` objects we made to find the ones that generated
1285
+ a :class:`.BindParameter` object, e.g. the expression system interpreted
1286
+ something as a literal. Those positions in the globals/closure are then
1287
+ ones that we will look at, each time a new lambda comes in that refers to
1288
+ the same ``__code__`` object. In this way, we keep a single version of
1289
+ the SQL expression that this lambda produced, without calling upon the
1290
+ Python function that created it more than once, unless its other closure
1291
+ variables have changed. The expression is then transformed to have the
1292
+ new bound values embedded into it.
1293
+
1294
+ """
1295
+
1296
+ def __init__(
1297
+ self,
1298
+ fn,
1299
+ name,
1300
+ to_evaluate,
1301
+ closure_index=None,
1302
+ getter=None,
1303
+ track_bound_values=True,
1304
+ ):
1305
+ self.fn = fn
1306
+ self._name = name
1307
+ self._to_evaluate = to_evaluate
1308
+ self._param = None
1309
+ self._has_param = False
1310
+ self._bind_paths = {}
1311
+ self._getter = getter
1312
+ self._closure_index = closure_index
1313
+ self.track_bound_values = track_bound_values
1314
+
1315
+ def __call__(self, *arg, **kw):
1316
+ elem = object.__getattribute__(self, "_to_evaluate")
1317
+ value = elem(*arg, **kw)
1318
+ if (
1319
+ self._sa_track_bound_values
1320
+ and coercions._deep_is_literal(value)
1321
+ and not isinstance(
1322
+ # TODO: coverage where an ORM option or similar is here
1323
+ value,
1324
+ _cache_key.HasCacheKey,
1325
+ )
1326
+ ):
1327
+ name = object.__getattribute__(self, "_name")
1328
+ raise exc.InvalidRequestError(
1329
+ "Can't invoke Python callable %s() inside of lambda "
1330
+ "expression argument at %s; lambda SQL constructs should "
1331
+ "not invoke functions from closure variables to produce "
1332
+ "literal values since the "
1333
+ "lambda SQL system normally extracts bound values without "
1334
+ "actually "
1335
+ "invoking the lambda or any functions within it. Call the "
1336
+ "function outside of the "
1337
+ "lambda and assign to a local variable that is used in the "
1338
+ "lambda as a closure variable, or set "
1339
+ "track_bound_values=False if the return value of this "
1340
+ "function is used in some other way other than a SQL bound "
1341
+ "value." % (name, self._sa_fn.__code__)
1342
+ )
1343
+ else:
1344
+ return value
1345
+
1346
+ def operate(self, op, *other, **kwargs):
1347
+ elem = object.__getattribute__(self, "_py_wrapper_literal")()
1348
+ return op(elem, *other, **kwargs)
1349
+
1350
+ def reverse_operate(self, op, other, **kwargs):
1351
+ elem = object.__getattribute__(self, "_py_wrapper_literal")()
1352
+ return op(other, elem, **kwargs)
1353
+
1354
+ def _extract_bound_parameters(self, starting_point, result_list):
1355
+ param = object.__getattribute__(self, "_param")
1356
+ if param is not None:
1357
+ param = param._with_value(starting_point, maintain_key=True)
1358
+ result_list.append(param)
1359
+ for pywrapper in object.__getattribute__(self, "_bind_paths").values():
1360
+ getter = object.__getattribute__(pywrapper, "_getter")
1361
+ element = getter(starting_point)
1362
+ pywrapper._sa__extract_bound_parameters(element, result_list)
1363
+
1364
+ def _py_wrapper_literal(self, expr=None, operator=None, **kw):
1365
+ param = object.__getattribute__(self, "_param")
1366
+ to_evaluate = object.__getattribute__(self, "_to_evaluate")
1367
+ if param is None:
1368
+ name = object.__getattribute__(self, "_name")
1369
+ self._param = param = elements.BindParameter(
1370
+ name,
1371
+ required=False,
1372
+ unique=True,
1373
+ _compared_to_operator=operator,
1374
+ _compared_to_type=expr.type if expr is not None else None,
1375
+ )
1376
+ self._has_param = True
1377
+ return param._with_value(to_evaluate, maintain_key=True)
1378
+
1379
+ def __bool__(self):
1380
+ to_evaluate = object.__getattribute__(self, "_to_evaluate")
1381
+ return bool(to_evaluate)
1382
+
1383
+ def __getattribute__(self, key):
1384
+ if key.startswith("_sa_"):
1385
+ return object.__getattribute__(self, key[4:])
1386
+ elif key in (
1387
+ "__clause_element__",
1388
+ "operate",
1389
+ "reverse_operate",
1390
+ "_py_wrapper_literal",
1391
+ "__class__",
1392
+ "__dict__",
1393
+ ):
1394
+ return object.__getattribute__(self, key)
1395
+
1396
+ if key.startswith("__"):
1397
+ elem = object.__getattribute__(self, "_to_evaluate")
1398
+ return getattr(elem, key)
1399
+ else:
1400
+ return self._sa__add_getter(key, operator.attrgetter)
1401
+
1402
+ def __iter__(self):
1403
+ elem = object.__getattribute__(self, "_to_evaluate")
1404
+ return iter(elem)
1405
+
1406
+ def __getitem__(self, key):
1407
+ elem = object.__getattribute__(self, "_to_evaluate")
1408
+ if not hasattr(elem, "__getitem__"):
1409
+ raise AttributeError("__getitem__")
1410
+
1411
+ if isinstance(key, PyWrapper):
1412
+ # TODO: coverage
1413
+ raise exc.InvalidRequestError(
1414
+ "Dictionary keys / list indexes inside of a cached "
1415
+ "lambda must be Python literals only"
1416
+ )
1417
+ return self._sa__add_getter(key, operator.itemgetter)
1418
+
1419
+ def _add_getter(self, key, getter_fn):
1420
+ bind_paths = object.__getattribute__(self, "_bind_paths")
1421
+
1422
+ bind_path_key = (key, getter_fn)
1423
+ if bind_path_key in bind_paths:
1424
+ return bind_paths[bind_path_key]
1425
+
1426
+ getter = getter_fn(key)
1427
+ elem = object.__getattribute__(self, "_to_evaluate")
1428
+ value = getter(elem)
1429
+
1430
+ rolled_down_value = AnalyzedCode._roll_down_to_literal(value)
1431
+
1432
+ if coercions._deep_is_literal(rolled_down_value):
1433
+ wrapper = PyWrapper(self._sa_fn, key, value, getter=getter)
1434
+ bind_paths[bind_path_key] = wrapper
1435
+ return wrapper
1436
+ else:
1437
+ return value
1438
+
1439
+
1440
+ @inspection._inspects(LambdaElement)
1441
+ def insp(lmb):
1442
+ return inspection.inspect(lmb._resolved)