Team Ai
Datasetpublic

codekingpro/portable-devtools

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