SQLAlchemy 2.0.47__cp313-cp313t-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlalchemy/__init__.py +283 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +184 -0
- sqlalchemy/connectors/asyncio.py +429 -0
- sqlalchemy/connectors/pyodbc.py +250 -0
- sqlalchemy/cyextension/__init__.py +6 -0
- sqlalchemy/cyextension/collections.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/collections.pyx +409 -0
- sqlalchemy/cyextension/immutabledict.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/immutabledict.pxd +8 -0
- sqlalchemy/cyextension/immutabledict.pyx +133 -0
- sqlalchemy/cyextension/processors.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/processors.pyx +68 -0
- sqlalchemy/cyextension/resultproxy.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/resultproxy.pyx +102 -0
- sqlalchemy/cyextension/util.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/util.pyx +90 -0
- sqlalchemy/dialects/__init__.py +62 -0
- sqlalchemy/dialects/_typing.py +30 -0
- sqlalchemy/dialects/mssql/__init__.py +88 -0
- sqlalchemy/dialects/mssql/aioodbc.py +63 -0
- sqlalchemy/dialects/mssql/base.py +4093 -0
- sqlalchemy/dialects/mssql/information_schema.py +285 -0
- sqlalchemy/dialects/mssql/json.py +129 -0
- sqlalchemy/dialects/mssql/provision.py +185 -0
- sqlalchemy/dialects/mssql/pymssql.py +126 -0
- sqlalchemy/dialects/mssql/pyodbc.py +760 -0
- sqlalchemy/dialects/mysql/__init__.py +104 -0
- sqlalchemy/dialects/mysql/aiomysql.py +250 -0
- sqlalchemy/dialects/mysql/asyncmy.py +231 -0
- sqlalchemy/dialects/mysql/base.py +3949 -0
- sqlalchemy/dialects/mysql/cymysql.py +106 -0
- sqlalchemy/dialects/mysql/dml.py +225 -0
- sqlalchemy/dialects/mysql/enumerated.py +282 -0
- sqlalchemy/dialects/mysql/expression.py +146 -0
- sqlalchemy/dialects/mysql/json.py +91 -0
- sqlalchemy/dialects/mysql/mariadb.py +72 -0
- sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
- sqlalchemy/dialects/mysql/mysqldb.py +314 -0
- sqlalchemy/dialects/mysql/provision.py +153 -0
- sqlalchemy/dialects/mysql/pymysql.py +158 -0
- sqlalchemy/dialects/mysql/pyodbc.py +157 -0
- sqlalchemy/dialects/mysql/reflection.py +727 -0
- sqlalchemy/dialects/mysql/reserved_words.py +570 -0
- sqlalchemy/dialects/mysql/types.py +835 -0
- sqlalchemy/dialects/oracle/__init__.py +81 -0
- sqlalchemy/dialects/oracle/base.py +3802 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/oracledb.py +941 -0
- sqlalchemy/dialects/oracle/provision.py +297 -0
- sqlalchemy/dialects/oracle/types.py +316 -0
- sqlalchemy/dialects/oracle/vector.py +365 -0
- sqlalchemy/dialects/postgresql/__init__.py +167 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
- sqlalchemy/dialects/postgresql/array.py +519 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
- sqlalchemy/dialects/postgresql/base.py +5378 -0
- sqlalchemy/dialects/postgresql/dml.py +339 -0
- sqlalchemy/dialects/postgresql/ext.py +540 -0
- sqlalchemy/dialects/postgresql/hstore.py +406 -0
- sqlalchemy/dialects/postgresql/json.py +404 -0
- sqlalchemy/dialects/postgresql/named_types.py +524 -0
- sqlalchemy/dialects/postgresql/operators.py +129 -0
- sqlalchemy/dialects/postgresql/pg8000.py +669 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
- sqlalchemy/dialects/postgresql/provision.py +183 -0
- sqlalchemy/dialects/postgresql/psycopg.py +862 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
- sqlalchemy/dialects/postgresql/ranges.py +1031 -0
- sqlalchemy/dialects/postgresql/types.py +313 -0
- sqlalchemy/dialects/sqlite/__init__.py +57 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
- sqlalchemy/dialects/sqlite/base.py +3056 -0
- sqlalchemy/dialects/sqlite/dml.py +263 -0
- sqlalchemy/dialects/sqlite/json.py +92 -0
- sqlalchemy/dialects/sqlite/provision.py +229 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_py_processors.py +136 -0
- sqlalchemy/engine/_py_row.py +128 -0
- sqlalchemy/engine/_py_util.py +74 -0
- sqlalchemy/engine/base.py +3390 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +893 -0
- sqlalchemy/engine/cursor.py +2298 -0
- sqlalchemy/engine/default.py +2394 -0
- sqlalchemy/engine/events.py +965 -0
- sqlalchemy/engine/interfaces.py +3471 -0
- sqlalchemy/engine/mock.py +134 -0
- sqlalchemy/engine/processors.py +61 -0
- sqlalchemy/engine/reflection.py +2102 -0
- sqlalchemy/engine/result.py +2399 -0
- sqlalchemy/engine/row.py +400 -0
- sqlalchemy/engine/strategies.py +16 -0
- sqlalchemy/engine/url.py +924 -0
- sqlalchemy/engine/util.py +167 -0
- sqlalchemy/event/__init__.py +26 -0
- sqlalchemy/event/api.py +220 -0
- sqlalchemy/event/attr.py +676 -0
- sqlalchemy/event/base.py +472 -0
- sqlalchemy/event/legacy.py +258 -0
- sqlalchemy/event/registry.py +390 -0
- sqlalchemy/events.py +17 -0
- sqlalchemy/exc.py +832 -0
- sqlalchemy/ext/__init__.py +11 -0
- sqlalchemy/ext/associationproxy.py +2027 -0
- sqlalchemy/ext/asyncio/__init__.py +25 -0
- sqlalchemy/ext/asyncio/base.py +281 -0
- sqlalchemy/ext/asyncio/engine.py +1471 -0
- sqlalchemy/ext/asyncio/exc.py +21 -0
- sqlalchemy/ext/asyncio/result.py +965 -0
- sqlalchemy/ext/asyncio/scoping.py +1599 -0
- sqlalchemy/ext/asyncio/session.py +1947 -0
- sqlalchemy/ext/automap.py +1701 -0
- sqlalchemy/ext/baked.py +570 -0
- sqlalchemy/ext/compiler.py +600 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +564 -0
- sqlalchemy/ext/horizontal_shard.py +478 -0
- sqlalchemy/ext/hybrid.py +1535 -0
- sqlalchemy/ext/indexable.py +364 -0
- sqlalchemy/ext/instrumentation.py +450 -0
- sqlalchemy/ext/mutable.py +1085 -0
- sqlalchemy/ext/mypy/__init__.py +6 -0
- sqlalchemy/ext/mypy/apply.py +324 -0
- sqlalchemy/ext/mypy/decl_class.py +515 -0
- sqlalchemy/ext/mypy/infer.py +590 -0
- sqlalchemy/ext/mypy/names.py +335 -0
- sqlalchemy/ext/mypy/plugin.py +303 -0
- sqlalchemy/ext/mypy/util.py +357 -0
- sqlalchemy/ext/orderinglist.py +439 -0
- sqlalchemy/ext/serializer.py +185 -0
- sqlalchemy/future/__init__.py +16 -0
- sqlalchemy/future/engine.py +15 -0
- sqlalchemy/inspection.py +174 -0
- sqlalchemy/log.py +288 -0
- sqlalchemy/orm/__init__.py +171 -0
- sqlalchemy/orm/_orm_constructors.py +2661 -0
- sqlalchemy/orm/_typing.py +179 -0
- sqlalchemy/orm/attributes.py +2845 -0
- sqlalchemy/orm/base.py +971 -0
- sqlalchemy/orm/bulk_persistence.py +2135 -0
- sqlalchemy/orm/clsregistry.py +571 -0
- sqlalchemy/orm/collections.py +1627 -0
- sqlalchemy/orm/context.py +3334 -0
- sqlalchemy/orm/decl_api.py +2004 -0
- sqlalchemy/orm/decl_base.py +2192 -0
- sqlalchemy/orm/dependency.py +1302 -0
- sqlalchemy/orm/descriptor_props.py +1092 -0
- sqlalchemy/orm/dynamic.py +300 -0
- sqlalchemy/orm/evaluator.py +379 -0
- sqlalchemy/orm/events.py +3252 -0
- sqlalchemy/orm/exc.py +237 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +754 -0
- sqlalchemy/orm/interfaces.py +1496 -0
- sqlalchemy/orm/loading.py +1686 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4444 -0
- sqlalchemy/orm/path_registry.py +809 -0
- sqlalchemy/orm/persistence.py +1788 -0
- sqlalchemy/orm/properties.py +935 -0
- sqlalchemy/orm/query.py +3459 -0
- sqlalchemy/orm/relationships.py +3508 -0
- sqlalchemy/orm/scoping.py +2148 -0
- sqlalchemy/orm/session.py +5280 -0
- sqlalchemy/orm/state.py +1168 -0
- sqlalchemy/orm/state_changes.py +196 -0
- sqlalchemy/orm/strategies.py +3470 -0
- sqlalchemy/orm/strategy_options.py +2568 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +796 -0
- sqlalchemy/orm/util.py +2403 -0
- sqlalchemy/orm/writeonly.py +674 -0
- sqlalchemy/pool/__init__.py +44 -0
- sqlalchemy/pool/base.py +1524 -0
- sqlalchemy/pool/events.py +375 -0
- sqlalchemy/pool/impl.py +588 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +69 -0
- sqlalchemy/sql/__init__.py +145 -0
- sqlalchemy/sql/_dml_constructors.py +132 -0
- sqlalchemy/sql/_elements_constructors.py +1872 -0
- sqlalchemy/sql/_orm_types.py +20 -0
- sqlalchemy/sql/_py_util.py +75 -0
- sqlalchemy/sql/_selectable_constructors.py +763 -0
- sqlalchemy/sql/_typing.py +482 -0
- sqlalchemy/sql/annotation.py +587 -0
- sqlalchemy/sql/base.py +2293 -0
- sqlalchemy/sql/cache_key.py +1057 -0
- sqlalchemy/sql/coercions.py +1404 -0
- sqlalchemy/sql/compiler.py +8081 -0
- sqlalchemy/sql/crud.py +1752 -0
- sqlalchemy/sql/ddl.py +1444 -0
- sqlalchemy/sql/default_comparator.py +551 -0
- sqlalchemy/sql/dml.py +1850 -0
- sqlalchemy/sql/elements.py +5589 -0
- sqlalchemy/sql/events.py +458 -0
- sqlalchemy/sql/expression.py +159 -0
- sqlalchemy/sql/functions.py +2158 -0
- sqlalchemy/sql/lambdas.py +1442 -0
- sqlalchemy/sql/naming.py +209 -0
- sqlalchemy/sql/operators.py +2623 -0
- sqlalchemy/sql/roles.py +323 -0
- sqlalchemy/sql/schema.py +6222 -0
- sqlalchemy/sql/selectable.py +7265 -0
- sqlalchemy/sql/sqltypes.py +3930 -0
- sqlalchemy/sql/traversals.py +1024 -0
- sqlalchemy/sql/type_api.py +2368 -0
- sqlalchemy/sql/util.py +1485 -0
- sqlalchemy/sql/visitors.py +1164 -0
- sqlalchemy/testing/__init__.py +96 -0
- sqlalchemy/testing/assertions.py +994 -0
- sqlalchemy/testing/assertsql.py +520 -0
- sqlalchemy/testing/asyncio.py +135 -0
- sqlalchemy/testing/config.py +434 -0
- sqlalchemy/testing/engines.py +483 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +476 -0
- sqlalchemy/testing/fixtures/__init__.py +28 -0
- sqlalchemy/testing/fixtures/base.py +384 -0
- sqlalchemy/testing/fixtures/mypy.py +332 -0
- sqlalchemy/testing/fixtures/orm.py +227 -0
- sqlalchemy/testing/fixtures/sql.py +482 -0
- sqlalchemy/testing/pickleable.py +155 -0
- sqlalchemy/testing/plugin/__init__.py +6 -0
- sqlalchemy/testing/plugin/bootstrap.py +51 -0
- sqlalchemy/testing/plugin/plugin_base.py +828 -0
- sqlalchemy/testing/plugin/pytestplugin.py +892 -0
- sqlalchemy/testing/profiling.py +329 -0
- sqlalchemy/testing/provision.py +603 -0
- sqlalchemy/testing/requirements.py +1945 -0
- sqlalchemy/testing/schema.py +198 -0
- sqlalchemy/testing/suite/__init__.py +19 -0
- sqlalchemy/testing/suite/test_cte.py +237 -0
- sqlalchemy/testing/suite/test_ddl.py +389 -0
- sqlalchemy/testing/suite/test_deprecations.py +153 -0
- sqlalchemy/testing/suite/test_dialect.py +776 -0
- sqlalchemy/testing/suite/test_insert.py +630 -0
- sqlalchemy/testing/suite/test_reflection.py +3557 -0
- sqlalchemy/testing/suite/test_results.py +504 -0
- sqlalchemy/testing/suite/test_rowcount.py +258 -0
- sqlalchemy/testing/suite/test_select.py +2010 -0
- sqlalchemy/testing/suite/test_sequence.py +317 -0
- sqlalchemy/testing/suite/test_types.py +2147 -0
- sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
- sqlalchemy/testing/suite/test_update_delete.py +139 -0
- sqlalchemy/testing/util.py +535 -0
- sqlalchemy/testing/warnings.py +52 -0
- sqlalchemy/types.py +74 -0
- sqlalchemy/util/__init__.py +162 -0
- sqlalchemy/util/_collections.py +712 -0
- sqlalchemy/util/_concurrency_py3k.py +288 -0
- sqlalchemy/util/_has_cy.py +40 -0
- sqlalchemy/util/_py_collections.py +541 -0
- sqlalchemy/util/compat.py +421 -0
- sqlalchemy/util/concurrency.py +110 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2203 -0
- sqlalchemy/util/preloaded.py +150 -0
- sqlalchemy/util/queue.py +322 -0
- sqlalchemy/util/tool_support.py +201 -0
- sqlalchemy/util/topological.py +120 -0
- sqlalchemy/util/typing.py +734 -0
- sqlalchemy-2.0.47.dist-info/METADATA +243 -0
- sqlalchemy-2.0.47.dist-info/RECORD +274 -0
- sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
- sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
- sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1057 @@
|
|
|
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
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import enum
|
|
11
|
+
from itertools import zip_longest
|
|
12
|
+
import typing
|
|
13
|
+
from typing import Any
|
|
14
|
+
from typing import Callable
|
|
15
|
+
from typing import Dict
|
|
16
|
+
from typing import Iterable
|
|
17
|
+
from typing import Iterator
|
|
18
|
+
from typing import List
|
|
19
|
+
from typing import MutableMapping
|
|
20
|
+
from typing import NamedTuple
|
|
21
|
+
from typing import Optional
|
|
22
|
+
from typing import Sequence
|
|
23
|
+
from typing import Tuple
|
|
24
|
+
from typing import Union
|
|
25
|
+
|
|
26
|
+
from .visitors import anon_map
|
|
27
|
+
from .visitors import HasTraversalDispatch
|
|
28
|
+
from .visitors import HasTraverseInternals
|
|
29
|
+
from .visitors import InternalTraversal
|
|
30
|
+
from .visitors import prefix_anon_map
|
|
31
|
+
from .. import util
|
|
32
|
+
from ..inspection import inspect
|
|
33
|
+
from ..util import HasMemoized
|
|
34
|
+
from ..util.typing import Literal
|
|
35
|
+
from ..util.typing import Protocol
|
|
36
|
+
|
|
37
|
+
if 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
|
+
|
|
45
|
+
class _CacheKeyTraversalDispatchType(Protocol):
|
|
46
|
+
def __call__(
|
|
47
|
+
s, self: HasCacheKey, visitor: _CacheKeyTraversal
|
|
48
|
+
) -> _CacheKeyTraversalDispatchTypeReturn: ...
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CacheConst(enum.Enum):
|
|
52
|
+
NO_CACHE = 0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
NO_CACHE = CacheConst.NO_CACHE
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
_CacheKeyTraversalType = Union[
|
|
59
|
+
"_TraverseInternalsType", Literal[CacheConst.NO_CACHE], Literal[None]
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class 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
|
+
|
|
92
|
+
class 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
|
+
|
|
404
|
+
class HasCacheKeyTraverse(HasTraverseInternals, HasCacheKey):
|
|
405
|
+
pass
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class 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
|
+
|
|
416
|
+
class 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
|
+
|
|
423
|
+
class 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
|
+
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
|
+
|
|
574
|
+
def _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
|
+
|
|
611
|
+
class _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()
|