lambdas.py 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442
  1. # sql/lambdas.py
  2. # Copyright (C) 2005-2025 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. from __future__ import annotations
  9. import collections.abc as collections_abc
  10. import inspect
  11. import itertools
  12. import operator
  13. import threading
  14. import types
  15. from types import CodeType
  16. from typing import Any
  17. from typing import Callable
  18. from typing import cast
  19. from typing import List
  20. from typing import MutableMapping
  21. from typing import Optional
  22. from typing import Tuple
  23. from typing import Type
  24. from typing import TYPE_CHECKING
  25. from typing import TypeVar
  26. from typing import Union
  27. import weakref
  28. from . import cache_key as _cache_key
  29. from . import coercions
  30. from . import elements
  31. from . import roles
  32. from . import schema
  33. from . import visitors
  34. from .base import _clone
  35. from .base import Executable
  36. from .base import Options
  37. from .cache_key import CacheConst
  38. from .operators import ColumnOperators
  39. from .. import exc
  40. from .. import inspection
  41. from .. import util
  42. from ..util.typing import Literal
  43. if TYPE_CHECKING:
  44. from .elements import BindParameter
  45. from .elements import ClauseElement
  46. from .roles import SQLRole
  47. from .visitors import _CloneCallableType
  48. _LambdaCacheType = MutableMapping[
  49. Tuple[Any, ...], Union["NonAnalyzedFunction", "AnalyzedFunction"]
  50. ]
  51. _BoundParameterGetter = Callable[..., Any]
  52. _closure_per_cache_key: _LambdaCacheType = util.LRUCache(1000)
  53. _LambdaType = Callable[[], Any]
  54. _AnyLambdaType = Callable[..., Any]
  55. _StmtLambdaType = Callable[[], Any]
  56. _E = TypeVar("_E", bound=Executable)
  57. _StmtLambdaElementType = Callable[[_E], Any]
  58. class LambdaOptions(Options):
  59. enable_tracking = True
  60. track_closure_variables = True
  61. track_on: Optional[object] = None
  62. global_track_bound_values = True
  63. track_bound_values = True
  64. lambda_cache: Optional[_LambdaCacheType] = None
  65. def lambda_stmt(
  66. lmb: _StmtLambdaType,
  67. enable_tracking: bool = True,
  68. track_closure_variables: bool = True,
  69. track_on: Optional[object] = None,
  70. global_track_bound_values: bool = True,
  71. track_bound_values: bool = True,
  72. lambda_cache: Optional[_LambdaCacheType] = None,
  73. ) -> StatementLambdaElement:
  74. """Produce a SQL statement that is cached as a lambda.
  75. The Python code object within the lambda is scanned for both Python
  76. literals that will become bound parameters as well as closure variables
  77. that refer to Core or ORM constructs that may vary. The lambda itself
  78. will be invoked only once per particular set of constructs detected.
  79. E.g.::
  80. from sqlalchemy import lambda_stmt
  81. stmt = lambda_stmt(lambda: table.select())
  82. stmt += lambda s: s.where(table.c.id == 5)
  83. result = connection.execute(stmt)
  84. The object returned is an instance of :class:`_sql.StatementLambdaElement`.
  85. .. versionadded:: 1.4
  86. :param lmb: a Python function, typically a lambda, which takes no arguments
  87. and returns a SQL expression construct
  88. :param enable_tracking: when False, all scanning of the given lambda for
  89. changes in closure variables or bound parameters is disabled. Use for
  90. a lambda that produces the identical results in all cases with no
  91. parameterization.
  92. :param track_closure_variables: when False, changes in closure variables
  93. within the lambda will not be scanned. Use for a lambda where the
  94. state of its closure variables will never change the SQL structure
  95. returned by the lambda.
  96. :param track_bound_values: when False, bound parameter tracking will
  97. be disabled for the given lambda. Use for a lambda that either does
  98. not produce any bound values, or where the initial bound values never
  99. change.
  100. :param global_track_bound_values: when False, bound parameter tracking
  101. will be disabled for the entire statement including additional links
  102. added via the :meth:`_sql.StatementLambdaElement.add_criteria` method.
  103. :param lambda_cache: a dictionary or other mapping-like object where
  104. information about the lambda's Python code as well as the tracked closure
  105. variables in the lambda itself will be stored. Defaults
  106. to a global LRU cache. This cache is independent of the "compiled_cache"
  107. used by the :class:`_engine.Connection` object.
  108. .. seealso::
  109. :ref:`engine_lambda_caching`
  110. """
  111. return StatementLambdaElement(
  112. lmb,
  113. roles.StatementRole,
  114. LambdaOptions(
  115. enable_tracking=enable_tracking,
  116. track_on=track_on,
  117. track_closure_variables=track_closure_variables,
  118. global_track_bound_values=global_track_bound_values,
  119. track_bound_values=track_bound_values,
  120. lambda_cache=lambda_cache,
  121. ),
  122. )
  123. class LambdaElement(elements.ClauseElement):
  124. """A SQL construct where the state is stored as an un-invoked lambda.
  125. The :class:`_sql.LambdaElement` is produced transparently whenever
  126. passing lambda expressions into SQL constructs, such as::
  127. stmt = select(table).where(lambda: table.c.col == parameter)
  128. The :class:`_sql.LambdaElement` is the base of the
  129. :class:`_sql.StatementLambdaElement` which represents a full statement
  130. within a lambda.
  131. .. versionadded:: 1.4
  132. .. seealso::
  133. :ref:`engine_lambda_caching`
  134. """
  135. __visit_name__ = "lambda_element"
  136. _is_lambda_element = True
  137. _traverse_internals = [
  138. ("_resolved", visitors.InternalTraversal.dp_clauseelement)
  139. ]
  140. _transforms: Tuple[_CloneCallableType, ...] = ()
  141. _resolved_bindparams: List[BindParameter[Any]]
  142. parent_lambda: Optional[StatementLambdaElement] = None
  143. closure_cache_key: Union[Tuple[Any, ...], Literal[CacheConst.NO_CACHE]]
  144. role: Type[SQLRole]
  145. _rec: Union[AnalyzedFunction, NonAnalyzedFunction]
  146. fn: _AnyLambdaType
  147. tracker_key: Tuple[CodeType, ...]
  148. def __repr__(self):
  149. return "%s(%r)" % (
  150. self.__class__.__name__,
  151. self.fn.__code__,
  152. )
  153. def __init__(
  154. self,
  155. fn: _LambdaType,
  156. role: Type[SQLRole],
  157. opts: Union[Type[LambdaOptions], LambdaOptions] = LambdaOptions,
  158. apply_propagate_attrs: Optional[ClauseElement] = None,
  159. ):
  160. self.fn = fn
  161. self.role = role
  162. self.tracker_key = (fn.__code__,)
  163. self.opts = opts
  164. if apply_propagate_attrs is None and (role is roles.StatementRole):
  165. apply_propagate_attrs = self
  166. rec = self._retrieve_tracker_rec(fn, apply_propagate_attrs, opts)
  167. if apply_propagate_attrs is not None:
  168. propagate_attrs = rec.propagate_attrs
  169. if propagate_attrs:
  170. apply_propagate_attrs._propagate_attrs = propagate_attrs
  171. def _retrieve_tracker_rec(self, fn, apply_propagate_attrs, opts):
  172. lambda_cache = opts.lambda_cache
  173. if lambda_cache is None:
  174. lambda_cache = _closure_per_cache_key
  175. tracker_key = self.tracker_key
  176. fn = self.fn
  177. closure = fn.__closure__
  178. tracker = AnalyzedCode.get(
  179. fn,
  180. self,
  181. opts,
  182. )
  183. bindparams: List[BindParameter[Any]]
  184. self._resolved_bindparams = bindparams = []
  185. if self.parent_lambda is not None:
  186. parent_closure_cache_key = self.parent_lambda.closure_cache_key
  187. else:
  188. parent_closure_cache_key = ()
  189. cache_key: Union[Tuple[Any, ...], Literal[CacheConst.NO_CACHE]]
  190. if parent_closure_cache_key is not _cache_key.NO_CACHE:
  191. anon_map = visitors.anon_map()
  192. cache_key = tuple(
  193. [
  194. getter(closure, opts, anon_map, bindparams)
  195. for getter in tracker.closure_trackers
  196. ]
  197. )
  198. if _cache_key.NO_CACHE not in anon_map:
  199. cache_key = parent_closure_cache_key + cache_key
  200. self.closure_cache_key = cache_key
  201. rec = lambda_cache.get(tracker_key + cache_key)
  202. else:
  203. cache_key = _cache_key.NO_CACHE
  204. rec = None
  205. else:
  206. cache_key = _cache_key.NO_CACHE
  207. rec = None
  208. self.closure_cache_key = cache_key
  209. if rec is None:
  210. if cache_key is not _cache_key.NO_CACHE:
  211. with AnalyzedCode._generation_mutex:
  212. key = tracker_key + cache_key
  213. if key not in lambda_cache:
  214. rec = AnalyzedFunction(
  215. tracker, self, apply_propagate_attrs, fn
  216. )
  217. rec.closure_bindparams = list(bindparams)
  218. lambda_cache[key] = rec
  219. else:
  220. rec = lambda_cache[key]
  221. else:
  222. rec = NonAnalyzedFunction(self._invoke_user_fn(fn))
  223. else:
  224. bindparams[:] = [
  225. orig_bind._with_value(new_bind.value, maintain_key=True)
  226. for orig_bind, new_bind in zip(
  227. rec.closure_bindparams, bindparams
  228. )
  229. ]
  230. self._rec = rec
  231. if cache_key is not _cache_key.NO_CACHE:
  232. if self.parent_lambda is not None:
  233. bindparams[:0] = self.parent_lambda._resolved_bindparams
  234. lambda_element: Optional[LambdaElement] = self
  235. while lambda_element is not None:
  236. rec = lambda_element._rec
  237. if rec.bindparam_trackers:
  238. tracker_instrumented_fn = (
  239. rec.tracker_instrumented_fn # type:ignore [union-attr] # noqa: E501
  240. )
  241. for tracker in rec.bindparam_trackers:
  242. tracker(
  243. lambda_element.fn,
  244. tracker_instrumented_fn,
  245. bindparams,
  246. )
  247. lambda_element = lambda_element.parent_lambda
  248. return rec
  249. def __getattr__(self, key):
  250. return getattr(self._rec.expected_expr, key)
  251. @property
  252. def _is_sequence(self):
  253. return self._rec.is_sequence
  254. @property
  255. def _select_iterable(self):
  256. if self._is_sequence:
  257. return itertools.chain.from_iterable(
  258. [element._select_iterable for element in self._resolved]
  259. )
  260. else:
  261. return self._resolved._select_iterable
  262. @property
  263. def _from_objects(self):
  264. if self._is_sequence:
  265. return itertools.chain.from_iterable(
  266. [element._from_objects for element in self._resolved]
  267. )
  268. else:
  269. return self._resolved._from_objects
  270. def _param_dict(self):
  271. return {b.key: b.value for b in self._resolved_bindparams}
  272. def _setup_binds_for_tracked_expr(self, expr):
  273. bindparam_lookup = {b.key: b for b in self._resolved_bindparams}
  274. def replace(
  275. element: Optional[visitors.ExternallyTraversible], **kw: Any
  276. ) -> Optional[visitors.ExternallyTraversible]:
  277. if isinstance(element, elements.BindParameter):
  278. if element.key in bindparam_lookup:
  279. bind = bindparam_lookup[element.key]
  280. if element.expanding:
  281. bind.expanding = True
  282. bind.expand_op = element.expand_op
  283. bind.type = element.type
  284. return bind
  285. return None
  286. if self._rec.is_sequence:
  287. expr = [
  288. visitors.replacement_traverse(sub_expr, {}, replace)
  289. for sub_expr in expr
  290. ]
  291. elif getattr(expr, "is_clause_element", False):
  292. expr = visitors.replacement_traverse(expr, {}, replace)
  293. return expr
  294. def _copy_internals(
  295. self,
  296. clone: _CloneCallableType = _clone,
  297. deferred_copy_internals: Optional[_CloneCallableType] = None,
  298. **kw: Any,
  299. ) -> None:
  300. # TODO: this needs A LOT of tests
  301. self._resolved = clone(
  302. self._resolved,
  303. deferred_copy_internals=deferred_copy_internals,
  304. **kw,
  305. )
  306. @util.memoized_property
  307. def _resolved(self):
  308. expr = self._rec.expected_expr
  309. if self._resolved_bindparams:
  310. expr = self._setup_binds_for_tracked_expr(expr)
  311. return expr
  312. def _gen_cache_key(self, anon_map, bindparams):
  313. if self.closure_cache_key is _cache_key.NO_CACHE:
  314. anon_map[_cache_key.NO_CACHE] = True
  315. return None
  316. cache_key = (
  317. self.fn.__code__,
  318. self.__class__,
  319. ) + self.closure_cache_key
  320. parent = self.parent_lambda
  321. while parent is not None:
  322. assert parent.closure_cache_key is not CacheConst.NO_CACHE
  323. parent_closure_cache_key: Tuple[Any, ...] = (
  324. parent.closure_cache_key
  325. )
  326. cache_key = (
  327. (parent.fn.__code__,) + parent_closure_cache_key + cache_key
  328. )
  329. parent = parent.parent_lambda
  330. if self._resolved_bindparams:
  331. bindparams.extend(self._resolved_bindparams)
  332. return cache_key
  333. def _invoke_user_fn(self, fn: _AnyLambdaType, *arg: Any) -> ClauseElement:
  334. return fn() # type: ignore[no-any-return]
  335. class DeferredLambdaElement(LambdaElement):
  336. """A LambdaElement where the lambda accepts arguments and is
  337. invoked within the compile phase with special context.
  338. This lambda doesn't normally produce its real SQL expression outside of the
  339. compile phase. It is passed a fixed set of initial arguments
  340. so that it can generate a sample expression.
  341. """
  342. def __init__(
  343. self,
  344. fn: _AnyLambdaType,
  345. role: Type[roles.SQLRole],
  346. opts: Union[Type[LambdaOptions], LambdaOptions] = LambdaOptions,
  347. lambda_args: Tuple[Any, ...] = (),
  348. ):
  349. self.lambda_args = lambda_args
  350. super().__init__(fn, role, opts)
  351. def _invoke_user_fn(self, fn, *arg):
  352. return fn(*self.lambda_args)
  353. def _resolve_with_args(self, *lambda_args: Any) -> ClauseElement:
  354. assert isinstance(self._rec, AnalyzedFunction)
  355. tracker_fn = self._rec.tracker_instrumented_fn
  356. expr = tracker_fn(*lambda_args)
  357. expr = coercions.expect(self.role, expr)
  358. expr = self._setup_binds_for_tracked_expr(expr)
  359. # this validation is getting very close, but not quite, to achieving
  360. # #5767. The problem is if the base lambda uses an unnamed column
  361. # as is very common with mixins, the parameter name is different
  362. # and it produces a false positive; that is, for the documented case
  363. # that is exactly what people will be doing, it doesn't work, so
  364. # I'm not really sure how to handle this right now.
  365. # expected_binds = [
  366. # b._orig_key
  367. # for b in self._rec.expr._generate_cache_key()[1]
  368. # if b.required
  369. # ]
  370. # got_binds = [
  371. # b._orig_key for b in expr._generate_cache_key()[1] if b.required
  372. # ]
  373. # if expected_binds != got_binds:
  374. # raise exc.InvalidRequestError(
  375. # "Lambda callable at %s produced a different set of bound "
  376. # "parameters than its original run: %s"
  377. # % (self.fn.__code__, ", ".join(got_binds))
  378. # )
  379. # TODO: TEST TEST TEST, this is very out there
  380. for deferred_copy_internals in self._transforms:
  381. expr = deferred_copy_internals(expr)
  382. return expr # type: ignore
  383. def _copy_internals(
  384. self, clone=_clone, deferred_copy_internals=None, **kw
  385. ):
  386. super()._copy_internals(
  387. clone=clone,
  388. deferred_copy_internals=deferred_copy_internals, # **kw
  389. opts=kw,
  390. )
  391. # TODO: A LOT A LOT of tests. for _resolve_with_args, we don't know
  392. # our expression yet. so hold onto the replacement
  393. if deferred_copy_internals:
  394. self._transforms += (deferred_copy_internals,)
  395. class StatementLambdaElement(
  396. roles.AllowsLambdaRole, LambdaElement, Executable
  397. ):
  398. """Represent a composable SQL statement as a :class:`_sql.LambdaElement`.
  399. The :class:`_sql.StatementLambdaElement` is constructed using the
  400. :func:`_sql.lambda_stmt` function::
  401. from sqlalchemy import lambda_stmt
  402. stmt = lambda_stmt(lambda: select(table))
  403. Once constructed, additional criteria can be built onto the statement
  404. by adding subsequent lambdas, which accept the existing statement
  405. object as a single parameter::
  406. stmt += lambda s: s.where(table.c.col == parameter)
  407. .. versionadded:: 1.4
  408. .. seealso::
  409. :ref:`engine_lambda_caching`
  410. """
  411. if TYPE_CHECKING:
  412. def __init__(
  413. self,
  414. fn: _StmtLambdaType,
  415. role: Type[SQLRole],
  416. opts: Union[Type[LambdaOptions], LambdaOptions] = LambdaOptions,
  417. apply_propagate_attrs: Optional[ClauseElement] = None,
  418. ): ...
  419. def __add__(
  420. self, other: _StmtLambdaElementType[Any]
  421. ) -> StatementLambdaElement:
  422. return self.add_criteria(other)
  423. def add_criteria(
  424. self,
  425. other: _StmtLambdaElementType[Any],
  426. enable_tracking: bool = True,
  427. track_on: Optional[Any] = None,
  428. track_closure_variables: bool = True,
  429. track_bound_values: bool = True,
  430. ) -> StatementLambdaElement:
  431. """Add new criteria to this :class:`_sql.StatementLambdaElement`.
  432. E.g.::
  433. >>> def my_stmt(parameter):
  434. ... stmt = lambda_stmt(
  435. ... lambda: select(table.c.x, table.c.y),
  436. ... )
  437. ... stmt = stmt.add_criteria(lambda: table.c.x > parameter)
  438. ... return stmt
  439. The :meth:`_sql.StatementLambdaElement.add_criteria` method is
  440. equivalent to using the Python addition operator to add a new
  441. lambda, except that additional arguments may be added including
  442. ``track_closure_values`` and ``track_on``::
  443. >>> def my_stmt(self, foo):
  444. ... stmt = lambda_stmt(
  445. ... lambda: select(func.max(foo.x, foo.y)),
  446. ... track_closure_variables=False,
  447. ... )
  448. ... stmt = stmt.add_criteria(lambda: self.where_criteria, track_on=[self])
  449. ... return stmt
  450. See :func:`_sql.lambda_stmt` for a description of the parameters
  451. accepted.
  452. """ # noqa: E501
  453. opts = self.opts + dict(
  454. enable_tracking=enable_tracking,
  455. track_closure_variables=track_closure_variables,
  456. global_track_bound_values=self.opts.global_track_bound_values,
  457. track_on=track_on,
  458. track_bound_values=track_bound_values,
  459. )
  460. return LinkedLambdaElement(other, parent_lambda=self, opts=opts)
  461. def _execute_on_connection(
  462. self, connection, distilled_params, execution_options
  463. ):
  464. if TYPE_CHECKING:
  465. assert isinstance(self._rec.expected_expr, ClauseElement)
  466. if self._rec.expected_expr.supports_execution:
  467. return connection._execute_clauseelement(
  468. self, distilled_params, execution_options
  469. )
  470. else:
  471. raise exc.ObjectNotExecutableError(self)
  472. @property
  473. def _proxied(self) -> Any:
  474. return self._rec_expected_expr
  475. @property
  476. def _with_options(self): # type: ignore[override]
  477. return self._proxied._with_options
  478. @property
  479. def _effective_plugin_target(self):
  480. return self._proxied._effective_plugin_target
  481. @property
  482. def _execution_options(self): # type: ignore[override]
  483. return self._proxied._execution_options
  484. @property
  485. def _all_selected_columns(self):
  486. return self._proxied._all_selected_columns
  487. @property
  488. def is_select(self): # type: ignore[override]
  489. return self._proxied.is_select
  490. @property
  491. def is_update(self): # type: ignore[override]
  492. return self._proxied.is_update
  493. @property
  494. def is_insert(self): # type: ignore[override]
  495. return self._proxied.is_insert
  496. @property
  497. def is_text(self): # type: ignore[override]
  498. return self._proxied.is_text
  499. @property
  500. def is_delete(self): # type: ignore[override]
  501. return self._proxied.is_delete
  502. @property
  503. def is_dml(self): # type: ignore[override]
  504. return self._proxied.is_dml
  505. def spoil(self) -> NullLambdaStatement:
  506. """Return a new :class:`.StatementLambdaElement` that will run
  507. all lambdas unconditionally each time.
  508. """
  509. return NullLambdaStatement(self.fn())
  510. class NullLambdaStatement(roles.AllowsLambdaRole, elements.ClauseElement):
  511. """Provides the :class:`.StatementLambdaElement` API but does not
  512. cache or analyze lambdas.
  513. the lambdas are instead invoked immediately.
  514. The intended use is to isolate issues that may arise when using
  515. lambda statements.
  516. """
  517. __visit_name__ = "lambda_element"
  518. _is_lambda_element = True
  519. _traverse_internals = [
  520. ("_resolved", visitors.InternalTraversal.dp_clauseelement)
  521. ]
  522. def __init__(self, statement):
  523. self._resolved = statement
  524. self._propagate_attrs = statement._propagate_attrs
  525. def __getattr__(self, key):
  526. return getattr(self._resolved, key)
  527. def __add__(self, other):
  528. statement = other(self._resolved)
  529. return NullLambdaStatement(statement)
  530. def add_criteria(self, other, **kw):
  531. statement = other(self._resolved)
  532. return NullLambdaStatement(statement)
  533. def _execute_on_connection(
  534. self, connection, distilled_params, execution_options
  535. ):
  536. if self._resolved.supports_execution:
  537. return connection._execute_clauseelement(
  538. self, distilled_params, execution_options
  539. )
  540. else:
  541. raise exc.ObjectNotExecutableError(self)
  542. class LinkedLambdaElement(StatementLambdaElement):
  543. """Represent subsequent links of a :class:`.StatementLambdaElement`."""
  544. parent_lambda: StatementLambdaElement
  545. def __init__(
  546. self,
  547. fn: _StmtLambdaElementType[Any],
  548. parent_lambda: StatementLambdaElement,
  549. opts: Union[Type[LambdaOptions], LambdaOptions],
  550. ):
  551. self.opts = opts
  552. self.fn = fn
  553. self.parent_lambda = parent_lambda
  554. self.tracker_key = parent_lambda.tracker_key + (fn.__code__,)
  555. self._retrieve_tracker_rec(fn, self, opts)
  556. self._propagate_attrs = parent_lambda._propagate_attrs
  557. def _invoke_user_fn(self, fn, *arg):
  558. return fn(self.parent_lambda._resolved)
  559. class AnalyzedCode:
  560. __slots__ = (
  561. "track_closure_variables",
  562. "track_bound_values",
  563. "bindparam_trackers",
  564. "closure_trackers",
  565. "build_py_wrappers",
  566. )
  567. _fns: weakref.WeakKeyDictionary[CodeType, AnalyzedCode] = (
  568. weakref.WeakKeyDictionary()
  569. )
  570. _generation_mutex = threading.RLock()
  571. @classmethod
  572. def get(cls, fn, lambda_element, lambda_kw, **kw):
  573. try:
  574. # TODO: validate kw haven't changed?
  575. return cls._fns[fn.__code__]
  576. except KeyError:
  577. pass
  578. with cls._generation_mutex:
  579. # check for other thread already created object
  580. if fn.__code__ in cls._fns:
  581. return cls._fns[fn.__code__]
  582. analyzed: AnalyzedCode
  583. cls._fns[fn.__code__] = analyzed = AnalyzedCode(
  584. fn, lambda_element, lambda_kw, **kw
  585. )
  586. return analyzed
  587. def __init__(self, fn, lambda_element, opts):
  588. if inspect.ismethod(fn):
  589. raise exc.ArgumentError(
  590. "Method %s may not be passed as a SQL expression" % fn
  591. )
  592. closure = fn.__closure__
  593. self.track_bound_values = (
  594. opts.track_bound_values and opts.global_track_bound_values
  595. )
  596. enable_tracking = opts.enable_tracking
  597. track_on = opts.track_on
  598. track_closure_variables = opts.track_closure_variables
  599. self.track_closure_variables = track_closure_variables and not track_on
  600. # a list of callables generated from _bound_parameter_getter_*
  601. # functions. Each of these uses a PyWrapper object to retrieve
  602. # a parameter value
  603. self.bindparam_trackers = []
  604. # a list of callables generated from _cache_key_getter_* functions
  605. # these callables work to generate a cache key for the lambda
  606. # based on what's inside its closure variables.
  607. self.closure_trackers = []
  608. self.build_py_wrappers = []
  609. if enable_tracking:
  610. if track_on:
  611. self._init_track_on(track_on)
  612. self._init_globals(fn)
  613. if closure:
  614. self._init_closure(fn)
  615. self._setup_additional_closure_trackers(fn, lambda_element, opts)
  616. def _init_track_on(self, track_on):
  617. self.closure_trackers.extend(
  618. self._cache_key_getter_track_on(idx, elem)
  619. for idx, elem in enumerate(track_on)
  620. )
  621. def _init_globals(self, fn):
  622. build_py_wrappers = self.build_py_wrappers
  623. bindparam_trackers = self.bindparam_trackers
  624. track_bound_values = self.track_bound_values
  625. for name in fn.__code__.co_names:
  626. if name not in fn.__globals__:
  627. continue
  628. _bound_value = self._roll_down_to_literal(fn.__globals__[name])
  629. if coercions._deep_is_literal(_bound_value):
  630. build_py_wrappers.append((name, None))
  631. if track_bound_values:
  632. bindparam_trackers.append(
  633. self._bound_parameter_getter_func_globals(name)
  634. )
  635. def _init_closure(self, fn):
  636. build_py_wrappers = self.build_py_wrappers
  637. closure = fn.__closure__
  638. track_bound_values = self.track_bound_values
  639. track_closure_variables = self.track_closure_variables
  640. bindparam_trackers = self.bindparam_trackers
  641. closure_trackers = self.closure_trackers
  642. for closure_index, (fv, cell) in enumerate(
  643. zip(fn.__code__.co_freevars, closure)
  644. ):
  645. _bound_value = self._roll_down_to_literal(cell.cell_contents)
  646. if coercions._deep_is_literal(_bound_value):
  647. build_py_wrappers.append((fv, closure_index))
  648. if track_bound_values:
  649. bindparam_trackers.append(
  650. self._bound_parameter_getter_func_closure(
  651. fv, closure_index
  652. )
  653. )
  654. else:
  655. # for normal cell contents, add them to a list that
  656. # we can compare later when we get new lambdas. if
  657. # any identities have changed, then we will
  658. # recalculate the whole lambda and run it again.
  659. if track_closure_variables:
  660. closure_trackers.append(
  661. self._cache_key_getter_closure_variable(
  662. fn, fv, closure_index, cell.cell_contents
  663. )
  664. )
  665. def _setup_additional_closure_trackers(self, fn, lambda_element, opts):
  666. # an additional step is to actually run the function, then
  667. # go through the PyWrapper objects that were set up to catch a bound
  668. # parameter. then if they *didn't* make a param, oh they're another
  669. # object in the closure we have to track for our cache key. so
  670. # create trackers to catch those.
  671. analyzed_function = AnalyzedFunction(
  672. self,
  673. lambda_element,
  674. None,
  675. fn,
  676. )
  677. closure_trackers = self.closure_trackers
  678. for pywrapper in analyzed_function.closure_pywrappers:
  679. if not pywrapper._sa__has_param:
  680. closure_trackers.append(
  681. self._cache_key_getter_tracked_literal(fn, pywrapper)
  682. )
  683. @classmethod
  684. def _roll_down_to_literal(cls, element):
  685. is_clause_element = hasattr(element, "__clause_element__")
  686. if is_clause_element:
  687. while not isinstance(
  688. element, (elements.ClauseElement, schema.SchemaItem, type)
  689. ):
  690. try:
  691. element = element.__clause_element__()
  692. except AttributeError:
  693. break
  694. if not is_clause_element:
  695. insp = inspection.inspect(element, raiseerr=False)
  696. if insp is not None:
  697. try:
  698. return insp.__clause_element__()
  699. except AttributeError:
  700. return insp
  701. # TODO: should we coerce consts None/True/False here?
  702. return element
  703. else:
  704. return element
  705. def _bound_parameter_getter_func_globals(self, name):
  706. """Return a getter that will extend a list of bound parameters
  707. with new entries from the ``__globals__`` collection of a particular
  708. lambda.
  709. """
  710. def extract_parameter_value(
  711. current_fn, tracker_instrumented_fn, result
  712. ):
  713. wrapper = tracker_instrumented_fn.__globals__[name]
  714. object.__getattribute__(wrapper, "_extract_bound_parameters")(
  715. current_fn.__globals__[name], result
  716. )
  717. return extract_parameter_value
  718. def _bound_parameter_getter_func_closure(self, name, closure_index):
  719. """Return a getter that will extend a list of bound parameters
  720. with new entries from the ``__closure__`` collection of a particular
  721. lambda.
  722. """
  723. def extract_parameter_value(
  724. current_fn, tracker_instrumented_fn, result
  725. ):
  726. wrapper = tracker_instrumented_fn.__closure__[
  727. closure_index
  728. ].cell_contents
  729. object.__getattribute__(wrapper, "_extract_bound_parameters")(
  730. current_fn.__closure__[closure_index].cell_contents, result
  731. )
  732. return extract_parameter_value
  733. def _cache_key_getter_track_on(self, idx, elem):
  734. """Return a getter that will extend a cache key with new entries
  735. from the "track_on" parameter passed to a :class:`.LambdaElement`.
  736. """
  737. if isinstance(elem, tuple):
  738. # tuple must contain hascachekey elements
  739. def get(closure, opts, anon_map, bindparams):
  740. return tuple(
  741. tup_elem._gen_cache_key(anon_map, bindparams)
  742. for tup_elem in opts.track_on[idx]
  743. )
  744. elif isinstance(elem, _cache_key.HasCacheKey):
  745. def get(closure, opts, anon_map, bindparams):
  746. return opts.track_on[idx]._gen_cache_key(anon_map, bindparams)
  747. else:
  748. def get(closure, opts, anon_map, bindparams):
  749. return opts.track_on[idx]
  750. return get
  751. def _cache_key_getter_closure_variable(
  752. self,
  753. fn,
  754. variable_name,
  755. idx,
  756. cell_contents,
  757. use_clause_element=False,
  758. use_inspect=False,
  759. ):
  760. """Return a getter that will extend a cache key with new entries
  761. from the ``__closure__`` collection of a particular lambda.
  762. """
  763. if isinstance(cell_contents, _cache_key.HasCacheKey):
  764. def get(closure, opts, anon_map, bindparams):
  765. obj = closure[idx].cell_contents
  766. if use_inspect:
  767. obj = inspection.inspect(obj)
  768. elif use_clause_element:
  769. while hasattr(obj, "__clause_element__"):
  770. if not getattr(obj, "is_clause_element", False):
  771. obj = obj.__clause_element__()
  772. return obj._gen_cache_key(anon_map, bindparams)
  773. elif isinstance(cell_contents, types.FunctionType):
  774. def get(closure, opts, anon_map, bindparams):
  775. return closure[idx].cell_contents.__code__
  776. elif isinstance(cell_contents, collections_abc.Sequence):
  777. def get(closure, opts, anon_map, bindparams):
  778. contents = closure[idx].cell_contents
  779. try:
  780. return tuple(
  781. elem._gen_cache_key(anon_map, bindparams)
  782. for elem in contents
  783. )
  784. except AttributeError as ae:
  785. self._raise_for_uncacheable_closure_variable(
  786. variable_name, fn, from_=ae
  787. )
  788. else:
  789. # if the object is a mapped class or aliased class, or some
  790. # other object in the ORM realm of things like that, imitate
  791. # the logic used in coercions.expect() to roll it down to the
  792. # SQL element
  793. element = cell_contents
  794. is_clause_element = False
  795. while hasattr(element, "__clause_element__"):
  796. is_clause_element = True
  797. if not getattr(element, "is_clause_element", False):
  798. element = element.__clause_element__()
  799. else:
  800. break
  801. if not is_clause_element:
  802. insp = inspection.inspect(element, raiseerr=False)
  803. if insp is not None:
  804. return self._cache_key_getter_closure_variable(
  805. fn, variable_name, idx, insp, use_inspect=True
  806. )
  807. else:
  808. return self._cache_key_getter_closure_variable(
  809. fn, variable_name, idx, element, use_clause_element=True
  810. )
  811. self._raise_for_uncacheable_closure_variable(variable_name, fn)
  812. return get
  813. def _raise_for_uncacheable_closure_variable(
  814. self, variable_name, fn, from_=None
  815. ):
  816. raise exc.InvalidRequestError(
  817. "Closure variable named '%s' inside of lambda callable %s "
  818. "does not refer to a cacheable SQL element, and also does not "
  819. "appear to be serving as a SQL literal bound value based on "
  820. "the default "
  821. "SQL expression returned by the function. This variable "
  822. "needs to remain outside the scope of a SQL-generating lambda "
  823. "so that a proper cache key may be generated from the "
  824. "lambda's state. Evaluate this variable outside of the "
  825. "lambda, set track_on=[<elements>] to explicitly select "
  826. "closure elements to track, or set "
  827. "track_closure_variables=False to exclude "
  828. "closure variables from being part of the cache key."
  829. % (variable_name, fn.__code__),
  830. ) from from_
  831. def _cache_key_getter_tracked_literal(self, fn, pytracker):
  832. """Return a getter that will extend a cache key with new entries
  833. from the ``__closure__`` collection of a particular lambda.
  834. this getter differs from _cache_key_getter_closure_variable
  835. in that these are detected after the function is run, and PyWrapper
  836. objects have recorded that a particular literal value is in fact
  837. not being interpreted as a bound parameter.
  838. """
  839. elem = pytracker._sa__to_evaluate
  840. closure_index = pytracker._sa__closure_index
  841. variable_name = pytracker._sa__name
  842. return self._cache_key_getter_closure_variable(
  843. fn, variable_name, closure_index, elem
  844. )
  845. class NonAnalyzedFunction:
  846. __slots__ = ("expr",)
  847. closure_bindparams: Optional[List[BindParameter[Any]]] = None
  848. bindparam_trackers: Optional[List[_BoundParameterGetter]] = None
  849. is_sequence = False
  850. expr: ClauseElement
  851. def __init__(self, expr: ClauseElement):
  852. self.expr = expr
  853. @property
  854. def expected_expr(self) -> ClauseElement:
  855. return self.expr
  856. class AnalyzedFunction:
  857. __slots__ = (
  858. "analyzed_code",
  859. "fn",
  860. "closure_pywrappers",
  861. "tracker_instrumented_fn",
  862. "expr",
  863. "bindparam_trackers",
  864. "expected_expr",
  865. "is_sequence",
  866. "propagate_attrs",
  867. "closure_bindparams",
  868. )
  869. closure_bindparams: Optional[List[BindParameter[Any]]]
  870. expected_expr: Union[ClauseElement, List[ClauseElement]]
  871. bindparam_trackers: Optional[List[_BoundParameterGetter]]
  872. def __init__(
  873. self,
  874. analyzed_code,
  875. lambda_element,
  876. apply_propagate_attrs,
  877. fn,
  878. ):
  879. self.analyzed_code = analyzed_code
  880. self.fn = fn
  881. self.bindparam_trackers = analyzed_code.bindparam_trackers
  882. self._instrument_and_run_function(lambda_element)
  883. self._coerce_expression(lambda_element, apply_propagate_attrs)
  884. def _instrument_and_run_function(self, lambda_element):
  885. analyzed_code = self.analyzed_code
  886. fn = self.fn
  887. self.closure_pywrappers = closure_pywrappers = []
  888. build_py_wrappers = analyzed_code.build_py_wrappers
  889. if not build_py_wrappers:
  890. self.tracker_instrumented_fn = tracker_instrumented_fn = fn
  891. self.expr = lambda_element._invoke_user_fn(tracker_instrumented_fn)
  892. else:
  893. track_closure_variables = analyzed_code.track_closure_variables
  894. closure = fn.__closure__
  895. # will form the __closure__ of the function when we rebuild it
  896. if closure:
  897. new_closure = {
  898. fv: cell.cell_contents
  899. for fv, cell in zip(fn.__code__.co_freevars, closure)
  900. }
  901. else:
  902. new_closure = {}
  903. # will form the __globals__ of the function when we rebuild it
  904. new_globals = fn.__globals__.copy()
  905. for name, closure_index in build_py_wrappers:
  906. if closure_index is not None:
  907. value = closure[closure_index].cell_contents
  908. new_closure[name] = bind = PyWrapper(
  909. fn,
  910. name,
  911. value,
  912. closure_index=closure_index,
  913. track_bound_values=(
  914. self.analyzed_code.track_bound_values
  915. ),
  916. )
  917. if track_closure_variables:
  918. closure_pywrappers.append(bind)
  919. else:
  920. value = fn.__globals__[name]
  921. new_globals[name] = PyWrapper(fn, name, value)
  922. # rewrite the original fn. things that look like they will
  923. # become bound parameters are wrapped in a PyWrapper.
  924. self.tracker_instrumented_fn = tracker_instrumented_fn = (
  925. self._rewrite_code_obj(
  926. fn,
  927. [new_closure[name] for name in fn.__code__.co_freevars],
  928. new_globals,
  929. )
  930. )
  931. # now invoke the function. This will give us a new SQL
  932. # expression, but all the places that there would be a bound
  933. # parameter, the PyWrapper in its place will give us a bind
  934. # with a predictable name we can match up later.
  935. # additionally, each PyWrapper will log that it did in fact
  936. # create a parameter, otherwise, it's some kind of Python
  937. # object in the closure and we want to track that, to make
  938. # sure it doesn't change to something else, or if it does,
  939. # that we create a different tracked function with that
  940. # variable.
  941. self.expr = lambda_element._invoke_user_fn(tracker_instrumented_fn)
  942. def _coerce_expression(self, lambda_element, apply_propagate_attrs):
  943. """Run the tracker-generated expression through coercion rules.
  944. After the user-defined lambda has been invoked to produce a statement
  945. for re-use, run it through coercion rules to both check that it's the
  946. correct type of object and also to coerce it to its useful form.
  947. """
  948. parent_lambda = lambda_element.parent_lambda
  949. expr = self.expr
  950. if parent_lambda is None:
  951. if isinstance(expr, collections_abc.Sequence):
  952. self.expected_expr = [
  953. cast(
  954. "ClauseElement",
  955. coercions.expect(
  956. lambda_element.role,
  957. sub_expr,
  958. apply_propagate_attrs=apply_propagate_attrs,
  959. ),
  960. )
  961. for sub_expr in expr
  962. ]
  963. self.is_sequence = True
  964. else:
  965. self.expected_expr = cast(
  966. "ClauseElement",
  967. coercions.expect(
  968. lambda_element.role,
  969. expr,
  970. apply_propagate_attrs=apply_propagate_attrs,
  971. ),
  972. )
  973. self.is_sequence = False
  974. else:
  975. self.expected_expr = expr
  976. self.is_sequence = False
  977. if apply_propagate_attrs is not None:
  978. self.propagate_attrs = apply_propagate_attrs._propagate_attrs
  979. else:
  980. self.propagate_attrs = util.EMPTY_DICT
  981. def _rewrite_code_obj(self, f, cell_values, globals_):
  982. """Return a copy of f, with a new closure and new globals
  983. yes it works in pypy :P
  984. """
  985. argrange = range(len(cell_values))
  986. code = "def make_cells():\n"
  987. if cell_values:
  988. code += " (%s) = (%s)\n" % (
  989. ", ".join("i%d" % i for i in argrange),
  990. ", ".join("o%d" % i for i in argrange),
  991. )
  992. code += " def closure():\n"
  993. code += " return %s\n" % ", ".join("i%d" % i for i in argrange)
  994. code += " return closure.__closure__"
  995. vars_ = {"o%d" % i: cell_values[i] for i in argrange}
  996. exec(code, vars_, vars_)
  997. closure = vars_["make_cells"]()
  998. func = type(f)(
  999. f.__code__, globals_, f.__name__, f.__defaults__, closure
  1000. )
  1001. func.__annotations__ = f.__annotations__
  1002. func.__kwdefaults__ = f.__kwdefaults__
  1003. func.__doc__ = f.__doc__
  1004. func.__module__ = f.__module__
  1005. return func
  1006. class PyWrapper(ColumnOperators):
  1007. """A wrapper object that is injected into the ``__globals__`` and
  1008. ``__closure__`` of a Python function.
  1009. When the function is instrumented with :class:`.PyWrapper` objects, it is
  1010. then invoked just once in order to set up the wrappers. We look through
  1011. all the :class:`.PyWrapper` objects we made to find the ones that generated
  1012. a :class:`.BindParameter` object, e.g. the expression system interpreted
  1013. something as a literal. Those positions in the globals/closure are then
  1014. ones that we will look at, each time a new lambda comes in that refers to
  1015. the same ``__code__`` object. In this way, we keep a single version of
  1016. the SQL expression that this lambda produced, without calling upon the
  1017. Python function that created it more than once, unless its other closure
  1018. variables have changed. The expression is then transformed to have the
  1019. new bound values embedded into it.
  1020. """
  1021. def __init__(
  1022. self,
  1023. fn,
  1024. name,
  1025. to_evaluate,
  1026. closure_index=None,
  1027. getter=None,
  1028. track_bound_values=True,
  1029. ):
  1030. self.fn = fn
  1031. self._name = name
  1032. self._to_evaluate = to_evaluate
  1033. self._param = None
  1034. self._has_param = False
  1035. self._bind_paths = {}
  1036. self._getter = getter
  1037. self._closure_index = closure_index
  1038. self.track_bound_values = track_bound_values
  1039. def __call__(self, *arg, **kw):
  1040. elem = object.__getattribute__(self, "_to_evaluate")
  1041. value = elem(*arg, **kw)
  1042. if (
  1043. self._sa_track_bound_values
  1044. and coercions._deep_is_literal(value)
  1045. and not isinstance(
  1046. # TODO: coverage where an ORM option or similar is here
  1047. value,
  1048. _cache_key.HasCacheKey,
  1049. )
  1050. ):
  1051. name = object.__getattribute__(self, "_name")
  1052. raise exc.InvalidRequestError(
  1053. "Can't invoke Python callable %s() inside of lambda "
  1054. "expression argument at %s; lambda SQL constructs should "
  1055. "not invoke functions from closure variables to produce "
  1056. "literal values since the "
  1057. "lambda SQL system normally extracts bound values without "
  1058. "actually "
  1059. "invoking the lambda or any functions within it. Call the "
  1060. "function outside of the "
  1061. "lambda and assign to a local variable that is used in the "
  1062. "lambda as a closure variable, or set "
  1063. "track_bound_values=False if the return value of this "
  1064. "function is used in some other way other than a SQL bound "
  1065. "value." % (name, self._sa_fn.__code__)
  1066. )
  1067. else:
  1068. return value
  1069. def operate(self, op, *other, **kwargs):
  1070. elem = object.__getattribute__(self, "_py_wrapper_literal")()
  1071. return op(elem, *other, **kwargs)
  1072. def reverse_operate(self, op, other, **kwargs):
  1073. elem = object.__getattribute__(self, "_py_wrapper_literal")()
  1074. return op(other, elem, **kwargs)
  1075. def _extract_bound_parameters(self, starting_point, result_list):
  1076. param = object.__getattribute__(self, "_param")
  1077. if param is not None:
  1078. param = param._with_value(starting_point, maintain_key=True)
  1079. result_list.append(param)
  1080. for pywrapper in object.__getattribute__(self, "_bind_paths").values():
  1081. getter = object.__getattribute__(pywrapper, "_getter")
  1082. element = getter(starting_point)
  1083. pywrapper._sa__extract_bound_parameters(element, result_list)
  1084. def _py_wrapper_literal(self, expr=None, operator=None, **kw):
  1085. param = object.__getattribute__(self, "_param")
  1086. to_evaluate = object.__getattribute__(self, "_to_evaluate")
  1087. if param is None:
  1088. name = object.__getattribute__(self, "_name")
  1089. self._param = param = elements.BindParameter(
  1090. name,
  1091. required=False,
  1092. unique=True,
  1093. _compared_to_operator=operator,
  1094. _compared_to_type=expr.type if expr is not None else None,
  1095. )
  1096. self._has_param = True
  1097. return param._with_value(to_evaluate, maintain_key=True)
  1098. def __bool__(self):
  1099. to_evaluate = object.__getattribute__(self, "_to_evaluate")
  1100. return bool(to_evaluate)
  1101. def __getattribute__(self, key):
  1102. if key.startswith("_sa_"):
  1103. return object.__getattribute__(self, key[4:])
  1104. elif key in (
  1105. "__clause_element__",
  1106. "operate",
  1107. "reverse_operate",
  1108. "_py_wrapper_literal",
  1109. "__class__",
  1110. "__dict__",
  1111. ):
  1112. return object.__getattribute__(self, key)
  1113. if key.startswith("__"):
  1114. elem = object.__getattribute__(self, "_to_evaluate")
  1115. return getattr(elem, key)
  1116. else:
  1117. return self._sa__add_getter(key, operator.attrgetter)
  1118. def __iter__(self):
  1119. elem = object.__getattribute__(self, "_to_evaluate")
  1120. return iter(elem)
  1121. def __getitem__(self, key):
  1122. elem = object.__getattribute__(self, "_to_evaluate")
  1123. if not hasattr(elem, "__getitem__"):
  1124. raise AttributeError("__getitem__")
  1125. if isinstance(key, PyWrapper):
  1126. # TODO: coverage
  1127. raise exc.InvalidRequestError(
  1128. "Dictionary keys / list indexes inside of a cached "
  1129. "lambda must be Python literals only"
  1130. )
  1131. return self._sa__add_getter(key, operator.itemgetter)
  1132. def _add_getter(self, key, getter_fn):
  1133. bind_paths = object.__getattribute__(self, "_bind_paths")
  1134. bind_path_key = (key, getter_fn)
  1135. if bind_path_key in bind_paths:
  1136. return bind_paths[bind_path_key]
  1137. getter = getter_fn(key)
  1138. elem = object.__getattribute__(self, "_to_evaluate")
  1139. value = getter(elem)
  1140. rolled_down_value = AnalyzedCode._roll_down_to_literal(value)
  1141. if coercions._deep_is_literal(rolled_down_value):
  1142. wrapper = PyWrapper(self._sa_fn, key, value, getter=getter)
  1143. bind_paths[bind_path_key] = wrapper
  1144. return wrapper
  1145. else:
  1146. return value
  1147. @inspection._inspects(LambdaElement)
  1148. def insp(lmb):
  1149. return inspection.inspect(lmb._resolved)