Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/sql/cache_key.py: 51%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

260 statements  

1# sql/cache_key.py 

2# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

5# This module is part of SQLAlchemy and is released under 

6# the MIT License: https://www.opensource.org/licenses/mit-license.php 

7 

8from __future__ import annotations 

9 

10from itertools import zip_longest 

11import typing 

12from typing import Any 

13from typing import cast 

14from typing import Dict 

15from typing import Final 

16from typing import Iterable 

17from typing import Iterator 

18from typing import List 

19from typing import Literal 

20from typing import MutableMapping 

21from typing import NamedTuple 

22from typing import Optional 

23from typing import Protocol 

24from typing import Sequence 

25from typing import Tuple 

26from typing import Type 

27from typing import Union 

28 

29from . import _cache_key_cy 

30from ._cache_key_cy import CacheConst as CacheConst 

31from ._cache_key_cy import CacheTraverseTarget as CacheTraverseTarget 

32from .visitors import anon_map 

33from .visitors import HasTraversalDispatch 

34from .visitors import HasTraverseInternals 

35from .visitors import InternalTraversal 

36from .visitors import prefix_anon_map 

37from .. import util 

38from ..inspection import inspect 

39from ..util import HasMemoized 

40 

41if typing.TYPE_CHECKING: 

42 from .elements import BindParameter 

43 from .elements import ClauseElement 

44 from .elements import ColumnElement 

45 from .visitors import _TraverseInternalsType 

46 from ..engine.interfaces import _CoreSingleExecuteParams 

47 

48 

49class _CacheKeyTraversalDispatchType(Protocol): 

50 def __call__( 

51 s, 

52 self: HasCacheKey, 

53 anon_map: anon_map, 

54 bindparams: List[BindParameter[Any]], 

55 id_: int, 

56 cls: Type[HasCacheKey], 

57 ) -> Optional[Tuple[Any, ...]]: ... 

58 

59 

60NO_CACHE: Final = CacheConst.NO_CACHE 

61 

62_CacheKeyTraversalType = Union[ 

63 "_TraverseInternalsType", Literal[CacheConst.NO_CACHE], Literal[None] 

64] 

65 

66 

67CACHE_IN_PLACE: Final = CacheTraverseTarget.CACHE_IN_PLACE 

68CALL_GEN_CACHE_KEY: Final = CacheTraverseTarget.CALL_GEN_CACHE_KEY 

69STATIC_CACHE_KEY: Final = CacheTraverseTarget.STATIC_CACHE_KEY 

70PROPAGATE_ATTRS: Final = CacheTraverseTarget.PROPAGATE_ATTRS 

71ANON_NAME: Final = CacheTraverseTarget.ANON_NAME 

72 

73 

74class HasCacheKey(_cache_key_cy.BaseHasCacheKey): 

75 """Mixin for objects which can produce a cache key. 

76 

77 This class is usually in a hierarchy that starts with the 

78 :class:`.HasTraverseInternals` base, but this is optional. Currently, 

79 the class should be able to work on its own without including 

80 :class:`.HasTraverseInternals`. 

81 

82 .. seealso:: 

83 

84 :class:`.CacheKey` 

85 

86 :ref:`sql_caching` 

87 

88 """ 

89 

90 __slots__ = () 

91 

92 _cache_key_traversal: _CacheKeyTraversalType = NO_CACHE 

93 

94 _is_has_cache_key = True 

95 

96 _hierarchy_supports_caching = True 

97 """private attribute which may be set to False to prevent the 

98 inherit_cache warning from being emitted for a hierarchy of subclasses. 

99 

100 Currently applies to the :class:`.ExecutableDDLElement` hierarchy which 

101 does not implement caching. 

102 

103 """ 

104 

105 inherit_cache: Optional[bool] = None 

106 """Indicate if this :class:`.HasCacheKey` instance should make use of the 

107 cache key generation scheme used by its immediate superclass. 

108 

109 The attribute defaults to ``None``, which indicates that a construct has 

110 not yet taken into account whether or not its appropriate for it to 

111 participate in caching; this is functionally equivalent to setting the 

112 value to ``False``, except that a warning is also emitted. 

113 

114 This flag can be set to ``True`` on a particular class, if the SQL that 

115 corresponds to the object does not change based on attributes which 

116 are local to this class, and not its superclass. 

117 

118 .. seealso:: 

119 

120 :ref:`compilerext_caching` - General guideslines for setting the 

121 :attr:`.HasCacheKey.inherit_cache` attribute for third-party or user 

122 defined SQL constructs. 

123 

124 """ 

125 

126 __slots__ = () 

127 

128 _generated_cache_key_traversal: Any 

129 

130 @classmethod 

131 def _generate_cache_attrs( 

132 cls, 

133 ) -> Union[_CacheKeyTraversalDispatchType, Literal[CacheConst.NO_CACHE]]: 

134 """generate cache key dispatcher for a new class. 

135 

136 This sets the _generated_cache_key_traversal attribute once called 

137 so should only be called once per class. 

138 

139 """ 

140 inherit_cache = cls.__dict__.get("inherit_cache", None) 

141 inherit = bool(inherit_cache) 

142 

143 if inherit: 

144 _cache_key_traversal = getattr(cls, "_cache_key_traversal", None) 

145 if _cache_key_traversal is None: 

146 try: 

147 assert issubclass(cls, HasTraverseInternals) 

148 _cache_key_traversal = cls._traverse_internals 

149 except AttributeError: 

150 cls._generated_cache_key_traversal = NO_CACHE 

151 return NO_CACHE 

152 

153 assert _cache_key_traversal is not NO_CACHE, ( 

154 f"class {cls} has _cache_key_traversal=NO_CACHE, " 

155 "which conflicts with inherit_cache=True" 

156 ) 

157 

158 # TODO: wouldn't we instead get this from our superclass? 

159 # also, our superclass may not have this yet, but in any case, 

160 # we'd generate for the superclass that has it. this is a little 

161 # more complicated, so for the moment this is a little less 

162 # efficient on startup but simpler. 

163 return cast( 

164 _CacheKeyTraversalDispatchType, 

165 _cache_key_traversal_visitor.generate_class_attrs( 

166 cls, 

167 _cache_key_traversal, 

168 "_generated_cache_key_traversal", 

169 ), 

170 ) 

171 else: 

172 _cache_key_traversal = cls.__dict__.get( 

173 "_cache_key_traversal", None 

174 ) 

175 if _cache_key_traversal is None: 

176 _cache_key_traversal = cls.__dict__.get( 

177 "_traverse_internals", None 

178 ) 

179 if _cache_key_traversal is None: 

180 cls._generated_cache_key_traversal = NO_CACHE 

181 if ( 

182 inherit_cache is None 

183 and cls._hierarchy_supports_caching 

184 ): 

185 util.warn( 

186 "Class %s will not make use of SQL compilation " 

187 "caching as it does not set the 'inherit_cache' " 

188 "attribute to ``True``. This can have " 

189 "significant performance implications including " 

190 "some performance degradations in comparison to " 

191 "prior SQLAlchemy versions. Set this attribute " 

192 "to True if this object can make use of the cache " 

193 "key generated by the superclass. Alternatively, " 

194 "this attribute may be set to False which will " 

195 "disable this warning." % (cls.__name__), 

196 code="cprf", 

197 ) 

198 return NO_CACHE 

199 

200 return cast( 

201 _CacheKeyTraversalDispatchType, 

202 _cache_key_traversal_visitor.generate_class_attrs( 

203 cls, 

204 _cache_key_traversal, 

205 "_generated_cache_key_traversal", 

206 ), 

207 ) 

208 

209 def _generate_cache_key(self) -> Optional[CacheKey]: 

210 """return a cache key. 

211 

212 The cache key is a tuple which can contain any series of 

213 objects that are hashable and also identifies 

214 this object uniquely within the presence of a larger SQL expression 

215 or statement, for the purposes of caching the resulting query. 

216 

217 The cache key should be based on the SQL compiled structure that would 

218 ultimately be produced. That is, two structures that are composed in 

219 exactly the same way should produce the same cache key; any difference 

220 in the structures that would affect the SQL string or the type handlers 

221 should result in a different cache key. 

222 

223 The cache key returned by this method is an instance of 

224 :class:`.CacheKey`, which consists of a tuple representing the 

225 cache key, as well as a list of :class:`.BindParameter` objects 

226 which are extracted from the expression. While two expressions 

227 that produce identical cache key tuples will themselves generate 

228 identical SQL strings, the list of :class:`.BindParameter` objects 

229 indicates the bound values which may have different values in 

230 each one; these bound parameters must be consulted in order to 

231 execute the statement with the correct parameters. 

232 

233 a :class:`_expression.ClauseElement` structure that does not implement 

234 a :meth:`._gen_cache_key` method and does not implement a 

235 :attr:`.traverse_internals` attribute will not be cacheable; when 

236 such an element is embedded into a larger structure, this method 

237 will return None, indicating no cache key is available. 

238 

239 """ 

240 

241 bindparams: List[BindParameter[Any]] = [] 

242 

243 _anon_map = anon_map() 

244 key = self._gen_cache_key(_anon_map, bindparams) 

245 if NO_CACHE in _anon_map: 

246 return None 

247 else: 

248 assert key is not None 

249 return CacheKey( 

250 key, 

251 bindparams, 

252 _anon_map.get(CacheConst.PARAMS), # type: ignore[arg-type] 

253 ) 

254 

255 

256class HasCacheKeyTraverse(HasTraverseInternals, HasCacheKey): 

257 pass 

258 

259 

260class MemoizedHasCacheKey(HasCacheKey, HasMemoized): 

261 __slots__ = () 

262 

263 @HasMemoized.memoized_instancemethod 

264 def _generate_cache_key(self) -> Optional[CacheKey]: 

265 return HasCacheKey._generate_cache_key(self) 

266 

267 

268class SlotsMemoizedHasCacheKey(HasCacheKey, util.MemoizedSlots): 

269 __slots__ = () 

270 

271 def _memoized_method__generate_cache_key(self) -> Optional[CacheKey]: 

272 return HasCacheKey._generate_cache_key(self) 

273 

274 

275class CacheKey(NamedTuple): 

276 """The key used to identify a SQL statement construct in the 

277 SQL compilation cache. 

278 

279 .. seealso:: 

280 

281 :ref:`sql_caching` 

282 

283 """ 

284 

285 key: Tuple[Any, ...] 

286 bindparams: Sequence[BindParameter[Any]] 

287 params: _CoreSingleExecuteParams | None 

288 

289 # can't set __hash__ attribute because it interferes 

290 # with namedtuple 

291 # can't use "if not TYPE_CHECKING" because mypy rejects it 

292 # inside of a NamedTuple 

293 def __hash__(self) -> Optional[int]: # type: ignore[override] 

294 """CacheKey itself is not hashable - hash the .key portion""" 

295 return None 

296 

297 def to_offline_string( 

298 self, 

299 statement_cache: MutableMapping[Any, str], 

300 statement: ClauseElement, 

301 parameters: _CoreSingleExecuteParams, 

302 ) -> str: 

303 """Generate an "offline string" form of this :class:`.CacheKey` 

304 

305 The "offline string" is basically the string SQL for the 

306 statement plus a repr of the bound parameter values in series. 

307 Whereas the :class:`.CacheKey` object is dependent on in-memory 

308 identities in order to work as a cache key, the "offline" version 

309 is suitable for a cache that will work for other processes as well. 

310 

311 The given ``statement_cache`` is a dictionary-like object where the 

312 string form of the statement itself will be cached. This dictionary 

313 should be in a longer lived scope in order to reduce the time spent 

314 stringifying statements. 

315 

316 

317 """ 

318 if self.key not in statement_cache: 

319 statement_cache[self.key] = sql_str = str(statement) 

320 else: 

321 sql_str = statement_cache[self.key] 

322 

323 if not self.bindparams: 

324 param_tuple = tuple(parameters[key] for key in sorted(parameters)) 

325 else: 

326 param_tuple = tuple( 

327 parameters.get(bindparam.key, bindparam.value) 

328 for bindparam in self.bindparams 

329 ) 

330 

331 return repr((sql_str, param_tuple)) 

332 

333 def __eq__(self, other: Any) -> bool: 

334 return other is not None and bool(self.key == other.key) 

335 

336 def __ne__(self, other: Any) -> bool: 

337 return other is None or not (self.key == other.key) 

338 

339 @classmethod 

340 def _diff_tuples(cls, left: CacheKey, right: CacheKey) -> str: 

341 ck1 = CacheKey(left, [], None) 

342 ck2 = CacheKey(right, [], None) 

343 return ck1._diff(ck2) 

344 

345 def _whats_different(self, other: CacheKey) -> Iterator[str]: 

346 k1 = self.key 

347 k2 = other.key 

348 

349 stack: List[int] = [] 

350 pickup_index = 0 

351 while True: 

352 s1, s2 = k1, k2 

353 for idx in stack: 

354 s1 = s1[idx] 

355 s2 = s2[idx] 

356 

357 for idx, (e1, e2) in enumerate(zip_longest(s1, s2)): 

358 if idx < pickup_index: 

359 continue 

360 if e1 != e2: 

361 if isinstance(e1, tuple) and isinstance(e2, tuple): 

362 stack.append(idx) 

363 break 

364 else: 

365 yield "key%s[%d]: %s != %s" % ( 

366 "".join("[%d]" % id_ for id_ in stack), 

367 idx, 

368 e1, 

369 e2, 

370 ) 

371 else: 

372 stack.pop(-1) 

373 break 

374 

375 def _diff(self, other: CacheKey) -> str: 

376 return ", ".join(self._whats_different(other)) 

377 

378 def __str__(self) -> str: 

379 stack: List[Union[Tuple[Any, ...], HasCacheKey]] = [self.key] 

380 

381 output = [] 

382 sentinel = object() 

383 indent = -1 

384 while stack: 

385 elem = stack.pop(0) 

386 if elem is sentinel: 

387 output.append((" " * (indent * 2)) + "),") 

388 indent -= 1 

389 elif isinstance(elem, tuple): 

390 if not elem: 

391 output.append((" " * ((indent + 1) * 2)) + "()") 

392 else: 

393 indent += 1 

394 stack = list(elem) + [sentinel] + stack 

395 output.append((" " * (indent * 2)) + "(") 

396 else: 

397 if isinstance(elem, HasCacheKey): 

398 repr_ = "<%s object at %s>" % ( 

399 type(elem).__name__, 

400 hex(id(elem)), 

401 ) 

402 else: 

403 repr_ = repr(elem) 

404 output.append((" " * (indent * 2)) + " " + repr_ + ", ") 

405 

406 return "CacheKey(key=%s)" % ("\n".join(output),) 

407 

408 def _generate_param_dict(self) -> Dict[str, Any]: 

409 """used for testing""" 

410 

411 _anon_map = prefix_anon_map() 

412 return {b.key % _anon_map: b.effective_value for b in self.bindparams} 

413 

414 @util.preload_module("sqlalchemy.sql.elements") 

415 def _apply_params_to_element( 

416 self, original_cache_key: CacheKey, target_element: ColumnElement[Any] 

417 ) -> ColumnElement[Any]: 

418 if target_element._is_immutable or original_cache_key is self: 

419 return target_element 

420 

421 elements = util.preloaded.sql_elements 

422 return elements._OverrideBinds( 

423 target_element, self.bindparams, original_cache_key.bindparams 

424 ) 

425 

426 

427def _ad_hoc_cache_key_from_args( 

428 tokens: Tuple[Any, ...], 

429 traverse_args: Iterable[Tuple[str, InternalTraversal]], 

430 args: Iterable[Any], 

431) -> Tuple[Any, ...]: 

432 """a quick cache key generator used by reflection.flexi_cache.""" 

433 bindparams: List[BindParameter[Any]] = [] 

434 

435 _anon_map = anon_map() 

436 

437 tup = tokens 

438 

439 for (attrname, sym), arg in zip(traverse_args, args): 

440 key = sym.name 

441 visit_key = key.replace("dp_", "visit_") 

442 

443 if arg is None: 

444 tup += (attrname, None) 

445 continue 

446 

447 meth = getattr(_cache_key_traversal_visitor, visit_key) 

448 if meth is CACHE_IN_PLACE: 

449 tup += (attrname, arg) 

450 elif meth in ( 

451 CALL_GEN_CACHE_KEY, 

452 STATIC_CACHE_KEY, 

453 ANON_NAME, 

454 PROPAGATE_ATTRS, 

455 ): 

456 raise NotImplementedError( 

457 f"Haven't implemented symbol {meth} for ad-hoc key from args" 

458 ) 

459 else: 

460 tup += meth(attrname, arg, None, _anon_map, bindparams) 

461 return tup 

462 

463 

464class _CacheKeyTraversal( 

465 HasTraversalDispatch, _cache_key_cy._BaseCacheKeyTraversal 

466): 

467 # the dispatch symbols below are resolved at class setup time by 

468 # _generate_class_attrs(), which records the handler for each attribute 

469 # as an integer "kind" that the compiled traversal switches on directly; 

470 # only the symbols that have no such inline handler are left as actual 

471 # methods on this class 

472 

473 visit_has_cache_key = visit_clauseelement = CALL_GEN_CACHE_KEY 

474 visit_clauseelement_list = InternalTraversal.dp_clauseelement_list 

475 visit_annotations_key = InternalTraversal.dp_annotations_key 

476 visit_clauseelement_tuple = InternalTraversal.dp_clauseelement_tuple 

477 visit_memoized_select_entities = ( 

478 InternalTraversal.dp_memoized_select_entities 

479 ) 

480 

481 visit_string = visit_boolean = visit_operator = visit_plain_obj = ( 

482 CACHE_IN_PLACE 

483 ) 

484 visit_statement_hint_list = CACHE_IN_PLACE 

485 visit_type = STATIC_CACHE_KEY 

486 visit_anon_name = ANON_NAME 

487 

488 visit_propagate_attrs = PROPAGATE_ATTRS 

489 

490 def visit_compile_state_funcs( 

491 self, 

492 attrname: str, 

493 obj: Any, 

494 parent: Any, 

495 anon_map: anon_map, 

496 bindparams: List[BindParameter[Any]], 

497 ) -> Tuple[Any, ...]: 

498 return tuple([(fn.__code__, c_key) for fn, c_key in obj]) 

499 

500 def visit_inspectable( 

501 self, 

502 attrname: str, 

503 obj: Any, 

504 parent: Any, 

505 anon_map: anon_map, 

506 bindparams: List[BindParameter[Any]], 

507 ) -> Tuple[Any, ...]: 

508 return (attrname, inspect(obj)._gen_cache_key(anon_map, bindparams)) 

509 

510 def visit_string_list( 

511 self, 

512 attrname: str, 

513 obj: Any, 

514 parent: Any, 

515 anon_map: anon_map, 

516 bindparams: List[BindParameter[Any]], 

517 ) -> Tuple[Any, ...]: 

518 return tuple(obj) 

519 

520 def visit_multi( 

521 self, 

522 attrname: str, 

523 obj: Any, 

524 parent: Any, 

525 anon_map: anon_map, 

526 bindparams: List[BindParameter[Any]], 

527 ) -> Tuple[Any, ...]: 

528 return ( 

529 attrname, 

530 ( 

531 obj._gen_cache_key(anon_map, bindparams) 

532 if isinstance(obj, HasCacheKey) 

533 else obj 

534 ), 

535 ) 

536 

537 def visit_multi_list( 

538 self, 

539 attrname: str, 

540 obj: Any, 

541 parent: Any, 

542 anon_map: anon_map, 

543 bindparams: List[BindParameter[Any]], 

544 ) -> Tuple[Any, ...]: 

545 return ( 

546 attrname, 

547 tuple( 

548 [ 

549 ( 

550 elem._gen_cache_key(anon_map, bindparams) 

551 if isinstance(elem, HasCacheKey) 

552 else elem 

553 ) 

554 for elem in obj 

555 ] 

556 ), 

557 ) 

558 

559 def visit_has_cache_key_tuples( 

560 self, 

561 attrname: str, 

562 obj: Any, 

563 parent: Any, 

564 anon_map: anon_map, 

565 bindparams: List[BindParameter[Any]], 

566 ) -> Tuple[Any, ...]: 

567 return ( 

568 attrname, 

569 tuple( 

570 [ 

571 tuple( 

572 [ 

573 elem._gen_cache_key(anon_map, bindparams) 

574 for elem in tup_elem 

575 ] 

576 ) 

577 for tup_elem in obj 

578 ] 

579 ), 

580 ) 

581 

582 def visit_has_cache_key_list( 

583 self, 

584 attrname: str, 

585 obj: Any, 

586 parent: Any, 

587 anon_map: anon_map, 

588 bindparams: List[BindParameter[Any]], 

589 ) -> Tuple[Any, ...]: 

590 return ( 

591 attrname, 

592 tuple([elem._gen_cache_key(anon_map, bindparams) for elem in obj]), 

593 ) 

594 

595 def visit_executable_options( 

596 self, 

597 attrname: str, 

598 obj: Any, 

599 parent: Any, 

600 anon_map: anon_map, 

601 bindparams: List[BindParameter[Any]], 

602 ) -> Tuple[Any, ...]: 

603 return ( 

604 attrname, 

605 tuple( 

606 [ 

607 elem._gen_cache_key(anon_map, bindparams) 

608 for elem in obj 

609 if elem._is_has_cache_key 

610 ] 

611 ), 

612 ) 

613 

614 visit_clauseelement_tuples = visit_has_cache_key_tuples 

615 

616 def visit_fromclause_ordered_set( 

617 self, 

618 attrname: str, 

619 obj: Any, 

620 parent: Any, 

621 anon_map: anon_map, 

622 bindparams: List[BindParameter[Any]], 

623 ) -> Tuple[Any, ...]: 

624 return ( 

625 attrname, 

626 tuple([elem._gen_cache_key(anon_map, bindparams) for elem in obj]), 

627 ) 

628 

629 def visit_clauseelement_unordered_set( 

630 self, 

631 attrname: str, 

632 obj: Any, 

633 parent: Any, 

634 anon_map: anon_map, 

635 bindparams: List[BindParameter[Any]], 

636 ) -> Tuple[Any, ...]: 

637 cache_keys = [ 

638 elem._gen_cache_key(anon_map, bindparams) for elem in obj 

639 ] 

640 return ( 

641 attrname, 

642 tuple( 

643 sorted(cache_keys) 

644 ), # cache keys all start with (id_, class) 

645 ) 

646 

647 def visit_named_ddl_element( 

648 self, 

649 attrname: str, 

650 obj: Any, 

651 parent: Any, 

652 anon_map: anon_map, 

653 bindparams: List[BindParameter[Any]], 

654 ) -> Tuple[Any, ...]: 

655 return (attrname, obj.name) 

656 

657 def visit_prefix_sequence( 

658 self, 

659 attrname: str, 

660 obj: Any, 

661 parent: Any, 

662 anon_map: anon_map, 

663 bindparams: List[BindParameter[Any]], 

664 ) -> Tuple[Any, ...]: 

665 return ( 

666 attrname, 

667 tuple( 

668 [ 

669 (clause._gen_cache_key(anon_map, bindparams), strval) 

670 for clause, strval in obj 

671 ] 

672 ), 

673 ) 

674 

675 def visit_setup_join_tuple( 

676 self, 

677 attrname: str, 

678 obj: Any, 

679 parent: Any, 

680 anon_map: anon_map, 

681 bindparams: List[BindParameter[Any]], 

682 ) -> Tuple[Any, ...]: 

683 return tuple( 

684 [ 

685 ( 

686 target._gen_cache_key(anon_map, bindparams), 

687 ( 

688 onclause._gen_cache_key(anon_map, bindparams) 

689 if onclause is not None 

690 else None 

691 ), 

692 ( 

693 from_._gen_cache_key(anon_map, bindparams) 

694 if from_ is not None 

695 else None 

696 ), 

697 tuple([(key, flags[key]) for key in sorted(flags)]), 

698 ) 

699 for (target, onclause, from_, flags) in obj 

700 ] 

701 ) 

702 

703 def visit_table_hint_list( 

704 self, 

705 attrname: str, 

706 obj: Any, 

707 parent: Any, 

708 anon_map: anon_map, 

709 bindparams: List[BindParameter[Any]], 

710 ) -> Tuple[Any, ...]: 

711 return ( 

712 attrname, 

713 tuple( 

714 [ 

715 ( 

716 clause._gen_cache_key(anon_map, bindparams), 

717 dialect_name, 

718 text, 

719 ) 

720 for (clause, dialect_name), text in obj.items() 

721 ] 

722 ), 

723 ) 

724 

725 def visit_plain_dict( 

726 self, 

727 attrname: str, 

728 obj: Any, 

729 parent: Any, 

730 anon_map: anon_map, 

731 bindparams: List[BindParameter[Any]], 

732 ) -> Tuple[Any, ...]: 

733 return (attrname, tuple([(key, obj[key]) for key in sorted(obj)])) 

734 

735 def visit_dialect_options( 

736 self, 

737 attrname: str, 

738 obj: Any, 

739 parent: Any, 

740 anon_map: anon_map, 

741 bindparams: List[BindParameter[Any]], 

742 ) -> Tuple[Any, ...]: 

743 return ( 

744 attrname, 

745 tuple( 

746 [ 

747 ( 

748 dialect_name, 

749 tuple( 

750 [ 

751 (key, obj[dialect_name][key]) 

752 for key in sorted(obj[dialect_name]) 

753 ] 

754 ), 

755 ) 

756 for dialect_name in sorted(obj) 

757 ] 

758 ), 

759 ) 

760 

761 def visit_string_clauseelement_dict( 

762 self, 

763 attrname: str, 

764 obj: Any, 

765 parent: Any, 

766 anon_map: anon_map, 

767 bindparams: List[BindParameter[Any]], 

768 ) -> Tuple[Any, ...]: 

769 return ( 

770 attrname, 

771 tuple( 

772 [ 

773 (key, obj[key]._gen_cache_key(anon_map, bindparams)) 

774 for key in sorted(obj) 

775 ] 

776 ), 

777 ) 

778 

779 def visit_string_multi_dict( 

780 self, 

781 attrname: str, 

782 obj: Any, 

783 parent: Any, 

784 anon_map: anon_map, 

785 bindparams: List[BindParameter[Any]], 

786 ) -> Tuple[Any, ...]: 

787 return ( 

788 attrname, 

789 tuple( 

790 [ 

791 ( 

792 key, 

793 ( 

794 value._gen_cache_key(anon_map, bindparams) 

795 if isinstance(value, HasCacheKey) 

796 else value 

797 ), 

798 ) 

799 for key, value in [(key, obj[key]) for key in sorted(obj)] 

800 ] 

801 ), 

802 ) 

803 

804 def visit_fromclause_canonical_column_collection( 

805 self, 

806 attrname: str, 

807 obj: Any, 

808 parent: Any, 

809 anon_map: anon_map, 

810 bindparams: List[BindParameter[Any]], 

811 ) -> Tuple[Any, ...]: 

812 # inlining into the internals of ColumnCollection 

813 return ( 

814 attrname, 

815 tuple( 

816 [ 

817 col._gen_cache_key(anon_map, bindparams) 

818 for k, col, _ in obj._collection 

819 ] 

820 ), 

821 ) 

822 

823 def visit_unknown_structure( 

824 self, 

825 attrname: str, 

826 obj: Any, 

827 parent: Any, 

828 anon_map: anon_map, 

829 bindparams: List[BindParameter[Any]], 

830 ) -> Tuple[Any, ...]: 

831 anon_map[NO_CACHE] = True 

832 return () 

833 

834 def visit_dml_ordered_values( 

835 self, 

836 attrname: str, 

837 obj: Any, 

838 parent: Any, 

839 anon_map: anon_map, 

840 bindparams: List[BindParameter[Any]], 

841 ) -> Tuple[Any, ...]: 

842 return ( 

843 attrname, 

844 tuple( 

845 [ 

846 ( 

847 ( 

848 key._gen_cache_key(anon_map, bindparams) 

849 if hasattr(key, "__clause_element__") 

850 else key 

851 ), 

852 value._gen_cache_key(anon_map, bindparams), 

853 ) 

854 for key, value in obj 

855 ] 

856 ), 

857 ) 

858 

859 def visit_dml_values( 

860 self, 

861 attrname: str, 

862 obj: Any, 

863 parent: Any, 

864 anon_map: anon_map, 

865 bindparams: List[BindParameter[Any]], 

866 ) -> Tuple[Any, ...]: 

867 # in py37 we can assume two dictionaries created in the same 

868 # insert ordering will retain that sorting 

869 return ( 

870 attrname, 

871 tuple( 

872 [ 

873 ( 

874 ( 

875 k._gen_cache_key(anon_map, bindparams) 

876 if hasattr(k, "__clause_element__") 

877 else k 

878 ), 

879 obj[k]._gen_cache_key(anon_map, bindparams), 

880 ) 

881 for k in obj 

882 ] 

883 ), 

884 ) 

885 

886 def visit_dml_multi_values( 

887 self, 

888 attrname: str, 

889 obj: Any, 

890 parent: Any, 

891 anon_map: anon_map, 

892 bindparams: List[BindParameter[Any]], 

893 ) -> Tuple[Any, ...]: 

894 # multivalues are simply not cacheable right now 

895 anon_map[NO_CACHE] = True 

896 return () 

897 

898 def visit_params( 

899 self, 

900 attrname: str, 

901 obj: Any, 

902 parent: Any, 

903 anon_map: anon_map, 

904 bindparams: List[BindParameter[Any]], 

905 ) -> Tuple[Any, ...]: 

906 if obj: 

907 if CacheConst.PARAMS in anon_map: 

908 to_set = anon_map[CacheConst.PARAMS] | obj 

909 else: 

910 to_set = obj 

911 anon_map[CacheConst.PARAMS] = to_set 

912 return () 

913 

914 

915_cache_key_traversal_visitor = _CacheKeyTraversal()