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