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,1024 @@
|
|
|
1
|
+
# sql/traversals.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
|
+
# mypy: allow-untyped-defs, allow-untyped-calls
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections import deque
|
|
12
|
+
import collections.abc as collections_abc
|
|
13
|
+
import itertools
|
|
14
|
+
from itertools import zip_longest
|
|
15
|
+
import operator
|
|
16
|
+
import typing
|
|
17
|
+
from typing import Any
|
|
18
|
+
from typing import Callable
|
|
19
|
+
from typing import Deque
|
|
20
|
+
from typing import Dict
|
|
21
|
+
from typing import Iterable
|
|
22
|
+
from typing import Optional
|
|
23
|
+
from typing import Set
|
|
24
|
+
from typing import Tuple
|
|
25
|
+
from typing import Type
|
|
26
|
+
|
|
27
|
+
from . import operators
|
|
28
|
+
from .cache_key import HasCacheKey
|
|
29
|
+
from .visitors import _TraverseInternalsType
|
|
30
|
+
from .visitors import anon_map
|
|
31
|
+
from .visitors import ExternallyTraversible
|
|
32
|
+
from .visitors import HasTraversalDispatch
|
|
33
|
+
from .visitors import HasTraverseInternals
|
|
34
|
+
from .. import util
|
|
35
|
+
from ..util import langhelpers
|
|
36
|
+
from ..util.typing import Self
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
SKIP_TRAVERSE = util.symbol("skip_traverse")
|
|
40
|
+
COMPARE_FAILED = False
|
|
41
|
+
COMPARE_SUCCEEDED = True
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def compare(obj1: Any, obj2: Any, **kw: Any) -> bool:
|
|
45
|
+
strategy: TraversalComparatorStrategy
|
|
46
|
+
if kw.get("use_proxies", False):
|
|
47
|
+
strategy = ColIdentityComparatorStrategy()
|
|
48
|
+
else:
|
|
49
|
+
strategy = TraversalComparatorStrategy()
|
|
50
|
+
|
|
51
|
+
return strategy.compare(obj1, obj2, **kw)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _preconfigure_traversals(target_hierarchy: Type[Any]) -> None:
|
|
55
|
+
for cls in util.walk_subclasses(target_hierarchy):
|
|
56
|
+
if hasattr(cls, "_generate_cache_attrs") and hasattr(
|
|
57
|
+
cls, "_traverse_internals"
|
|
58
|
+
):
|
|
59
|
+
cls._generate_cache_attrs()
|
|
60
|
+
_copy_internals.generate_dispatch(
|
|
61
|
+
cls,
|
|
62
|
+
cls._traverse_internals,
|
|
63
|
+
"_generated_copy_internals_traversal",
|
|
64
|
+
)
|
|
65
|
+
_get_children.generate_dispatch(
|
|
66
|
+
cls,
|
|
67
|
+
cls._traverse_internals,
|
|
68
|
+
"_generated_get_children_traversal",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class HasShallowCopy(HasTraverseInternals):
|
|
73
|
+
"""attribute-wide operations that are useful for classes that use
|
|
74
|
+
__slots__ and therefore can't operate on their attributes in a dictionary.
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
__slots__ = ()
|
|
80
|
+
|
|
81
|
+
if typing.TYPE_CHECKING:
|
|
82
|
+
|
|
83
|
+
def _generated_shallow_copy_traversal(self, other: Self) -> None: ...
|
|
84
|
+
|
|
85
|
+
def _generated_shallow_from_dict_traversal(
|
|
86
|
+
self, d: Dict[str, Any]
|
|
87
|
+
) -> None: ...
|
|
88
|
+
|
|
89
|
+
def _generated_shallow_to_dict_traversal(self) -> Dict[str, Any]: ...
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def _generate_shallow_copy(
|
|
93
|
+
cls,
|
|
94
|
+
internal_dispatch: _TraverseInternalsType,
|
|
95
|
+
method_name: str,
|
|
96
|
+
) -> Callable[[Self, Self], None]:
|
|
97
|
+
code = "\n".join(
|
|
98
|
+
f" other.{attrname} = self.{attrname}"
|
|
99
|
+
for attrname, _ in internal_dispatch
|
|
100
|
+
)
|
|
101
|
+
meth_text = f"def {method_name}(self, other):\n{code}\n"
|
|
102
|
+
return langhelpers._exec_code_in_env(meth_text, {}, method_name)
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def _generate_shallow_to_dict(
|
|
106
|
+
cls,
|
|
107
|
+
internal_dispatch: _TraverseInternalsType,
|
|
108
|
+
method_name: str,
|
|
109
|
+
) -> Callable[[Self], Dict[str, Any]]:
|
|
110
|
+
code = ",\n".join(
|
|
111
|
+
f" '{attrname}': self.{attrname}"
|
|
112
|
+
for attrname, _ in internal_dispatch
|
|
113
|
+
)
|
|
114
|
+
meth_text = f"def {method_name}(self):\n return {{{code}}}\n"
|
|
115
|
+
return langhelpers._exec_code_in_env(meth_text, {}, method_name)
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def _generate_shallow_from_dict(
|
|
119
|
+
cls,
|
|
120
|
+
internal_dispatch: _TraverseInternalsType,
|
|
121
|
+
method_name: str,
|
|
122
|
+
) -> Callable[[Self, Dict[str, Any]], None]:
|
|
123
|
+
code = "\n".join(
|
|
124
|
+
f" self.{attrname} = d['{attrname}']"
|
|
125
|
+
for attrname, _ in internal_dispatch
|
|
126
|
+
)
|
|
127
|
+
meth_text = f"def {method_name}(self, d):\n{code}\n"
|
|
128
|
+
return langhelpers._exec_code_in_env(meth_text, {}, method_name)
|
|
129
|
+
|
|
130
|
+
def _shallow_from_dict(self, d: Dict[str, Any]) -> None:
|
|
131
|
+
cls = self.__class__
|
|
132
|
+
|
|
133
|
+
shallow_from_dict: Callable[[HasShallowCopy, Dict[str, Any]], None]
|
|
134
|
+
try:
|
|
135
|
+
shallow_from_dict = cls.__dict__[
|
|
136
|
+
"_generated_shallow_from_dict_traversal"
|
|
137
|
+
]
|
|
138
|
+
except KeyError:
|
|
139
|
+
shallow_from_dict = self._generate_shallow_from_dict(
|
|
140
|
+
cls._traverse_internals,
|
|
141
|
+
"_generated_shallow_from_dict_traversal",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
cls._generated_shallow_from_dict_traversal = shallow_from_dict # type: ignore # noqa: E501
|
|
145
|
+
|
|
146
|
+
shallow_from_dict(self, d)
|
|
147
|
+
|
|
148
|
+
def _shallow_to_dict(self) -> Dict[str, Any]:
|
|
149
|
+
cls = self.__class__
|
|
150
|
+
|
|
151
|
+
shallow_to_dict: Callable[[HasShallowCopy], Dict[str, Any]]
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
shallow_to_dict = cls.__dict__[
|
|
155
|
+
"_generated_shallow_to_dict_traversal"
|
|
156
|
+
]
|
|
157
|
+
except KeyError:
|
|
158
|
+
shallow_to_dict = self._generate_shallow_to_dict(
|
|
159
|
+
cls._traverse_internals, "_generated_shallow_to_dict_traversal"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
cls._generated_shallow_to_dict_traversal = shallow_to_dict # type: ignore # noqa: E501
|
|
163
|
+
return shallow_to_dict(self)
|
|
164
|
+
|
|
165
|
+
def _shallow_copy_to(self, other: Self) -> None:
|
|
166
|
+
cls = self.__class__
|
|
167
|
+
|
|
168
|
+
shallow_copy: Callable[[Self, Self], None]
|
|
169
|
+
try:
|
|
170
|
+
shallow_copy = cls.__dict__["_generated_shallow_copy_traversal"]
|
|
171
|
+
except KeyError:
|
|
172
|
+
shallow_copy = self._generate_shallow_copy(
|
|
173
|
+
cls._traverse_internals, "_generated_shallow_copy_traversal"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
cls._generated_shallow_copy_traversal = shallow_copy # type: ignore # noqa: E501
|
|
177
|
+
shallow_copy(self, other)
|
|
178
|
+
|
|
179
|
+
def _clone(self, **kw: Any) -> Self:
|
|
180
|
+
"""Create a shallow copy"""
|
|
181
|
+
c = self.__class__.__new__(self.__class__)
|
|
182
|
+
self._shallow_copy_to(c)
|
|
183
|
+
return c
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class GenerativeOnTraversal(HasShallowCopy):
|
|
187
|
+
"""Supplies Generative behavior but making use of traversals to shallow
|
|
188
|
+
copy.
|
|
189
|
+
|
|
190
|
+
.. seealso::
|
|
191
|
+
|
|
192
|
+
:class:`sqlalchemy.sql.base.Generative`
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
__slots__ = ()
|
|
198
|
+
|
|
199
|
+
def _generate(self) -> Self:
|
|
200
|
+
cls = self.__class__
|
|
201
|
+
s = cls.__new__(cls)
|
|
202
|
+
self._shallow_copy_to(s)
|
|
203
|
+
return s
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _clone(element, **kw):
|
|
207
|
+
return element._clone()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class HasCopyInternals(HasTraverseInternals):
|
|
211
|
+
__slots__ = ()
|
|
212
|
+
|
|
213
|
+
def _clone(self, **kw):
|
|
214
|
+
raise NotImplementedError()
|
|
215
|
+
|
|
216
|
+
def _copy_internals(
|
|
217
|
+
self, *, omit_attrs: Iterable[str] = (), **kw: Any
|
|
218
|
+
) -> None:
|
|
219
|
+
"""Reassign internal elements to be clones of themselves.
|
|
220
|
+
|
|
221
|
+
Called during a copy-and-traverse operation on newly
|
|
222
|
+
shallow-copied elements to create a deep copy.
|
|
223
|
+
|
|
224
|
+
The given clone function should be used, which may be applying
|
|
225
|
+
additional transformations to the element (i.e. replacement
|
|
226
|
+
traversal, cloned traversal, annotations).
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
traverse_internals = self._traverse_internals
|
|
232
|
+
except AttributeError:
|
|
233
|
+
# user-defined classes may not have a _traverse_internals
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
for attrname, obj, meth in _copy_internals.run_generated_dispatch(
|
|
237
|
+
self, traverse_internals, "_generated_copy_internals_traversal"
|
|
238
|
+
):
|
|
239
|
+
if attrname in omit_attrs:
|
|
240
|
+
continue
|
|
241
|
+
|
|
242
|
+
if obj is not None:
|
|
243
|
+
result = meth(attrname, self, obj, **kw)
|
|
244
|
+
if result is not None:
|
|
245
|
+
setattr(self, attrname, result)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class _CopyInternalsTraversal(HasTraversalDispatch):
|
|
249
|
+
"""Generate a _copy_internals internal traversal dispatch for classes
|
|
250
|
+
with a _traverse_internals collection."""
|
|
251
|
+
|
|
252
|
+
def visit_clauseelement(
|
|
253
|
+
self, attrname, parent, element, clone=_clone, **kw
|
|
254
|
+
):
|
|
255
|
+
return clone(element, **kw)
|
|
256
|
+
|
|
257
|
+
def visit_clauseelement_list(
|
|
258
|
+
self, attrname, parent, element, clone=_clone, **kw
|
|
259
|
+
):
|
|
260
|
+
return [clone(clause, **kw) for clause in element]
|
|
261
|
+
|
|
262
|
+
def visit_clauseelement_tuple(
|
|
263
|
+
self, attrname, parent, element, clone=_clone, **kw
|
|
264
|
+
):
|
|
265
|
+
return tuple([clone(clause, **kw) for clause in element])
|
|
266
|
+
|
|
267
|
+
def visit_executable_options(
|
|
268
|
+
self, attrname, parent, element, clone=_clone, **kw
|
|
269
|
+
):
|
|
270
|
+
return tuple([clone(clause, **kw) for clause in element])
|
|
271
|
+
|
|
272
|
+
def visit_clauseelement_unordered_set(
|
|
273
|
+
self, attrname, parent, element, clone=_clone, **kw
|
|
274
|
+
):
|
|
275
|
+
return {clone(clause, **kw) for clause in element}
|
|
276
|
+
|
|
277
|
+
def visit_clauseelement_tuples(
|
|
278
|
+
self, attrname, parent, element, clone=_clone, **kw
|
|
279
|
+
):
|
|
280
|
+
return [
|
|
281
|
+
tuple(clone(tup_elem, **kw) for tup_elem in elem)
|
|
282
|
+
for elem in element
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
def visit_string_clauseelement_dict(
|
|
286
|
+
self, attrname, parent, element, clone=_clone, **kw
|
|
287
|
+
):
|
|
288
|
+
return {key: clone(value, **kw) for key, value in element.items()}
|
|
289
|
+
|
|
290
|
+
def visit_setup_join_tuple(
|
|
291
|
+
self, attrname, parent, element, clone=_clone, **kw
|
|
292
|
+
):
|
|
293
|
+
return tuple(
|
|
294
|
+
(
|
|
295
|
+
clone(target, **kw) if target is not None else None,
|
|
296
|
+
clone(onclause, **kw) if onclause is not None else None,
|
|
297
|
+
clone(from_, **kw) if from_ is not None else None,
|
|
298
|
+
flags,
|
|
299
|
+
)
|
|
300
|
+
for (target, onclause, from_, flags) in element
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
def visit_memoized_select_entities(self, attrname, parent, element, **kw):
|
|
304
|
+
return self.visit_clauseelement_tuple(attrname, parent, element, **kw)
|
|
305
|
+
|
|
306
|
+
def visit_dml_ordered_values(
|
|
307
|
+
self, attrname, parent, element, clone=_clone, **kw
|
|
308
|
+
):
|
|
309
|
+
# sequence of 2-tuples
|
|
310
|
+
return [
|
|
311
|
+
(
|
|
312
|
+
(
|
|
313
|
+
clone(key, **kw)
|
|
314
|
+
if hasattr(key, "__clause_element__")
|
|
315
|
+
else key
|
|
316
|
+
),
|
|
317
|
+
clone(value, **kw),
|
|
318
|
+
)
|
|
319
|
+
for key, value in element
|
|
320
|
+
]
|
|
321
|
+
|
|
322
|
+
def visit_dml_values(self, attrname, parent, element, clone=_clone, **kw):
|
|
323
|
+
return {
|
|
324
|
+
(
|
|
325
|
+
clone(key, **kw) if hasattr(key, "__clause_element__") else key
|
|
326
|
+
): clone(value, **kw)
|
|
327
|
+
for key, value in element.items()
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
def visit_dml_multi_values(
|
|
331
|
+
self, attrname, parent, element, clone=_clone, **kw
|
|
332
|
+
):
|
|
333
|
+
# sequence of sequences, each sequence contains a list/dict/tuple
|
|
334
|
+
|
|
335
|
+
def copy(elem):
|
|
336
|
+
if isinstance(elem, (list, tuple)):
|
|
337
|
+
return [
|
|
338
|
+
(
|
|
339
|
+
clone(value, **kw)
|
|
340
|
+
if hasattr(value, "__clause_element__")
|
|
341
|
+
else value
|
|
342
|
+
)
|
|
343
|
+
for value in elem
|
|
344
|
+
]
|
|
345
|
+
elif isinstance(elem, dict):
|
|
346
|
+
return {
|
|
347
|
+
(
|
|
348
|
+
clone(key, **kw)
|
|
349
|
+
if hasattr(key, "__clause_element__")
|
|
350
|
+
else key
|
|
351
|
+
): (
|
|
352
|
+
clone(value, **kw)
|
|
353
|
+
if hasattr(value, "__clause_element__")
|
|
354
|
+
else value
|
|
355
|
+
)
|
|
356
|
+
for key, value in elem.items()
|
|
357
|
+
}
|
|
358
|
+
else:
|
|
359
|
+
# TODO: use abc classes
|
|
360
|
+
assert False
|
|
361
|
+
|
|
362
|
+
return [
|
|
363
|
+
[copy(sub_element) for sub_element in sequence]
|
|
364
|
+
for sequence in element
|
|
365
|
+
]
|
|
366
|
+
|
|
367
|
+
def visit_propagate_attrs(
|
|
368
|
+
self, attrname, parent, element, clone=_clone, **kw
|
|
369
|
+
):
|
|
370
|
+
return element
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
_copy_internals = _CopyInternalsTraversal()
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _flatten_clauseelement(element):
|
|
377
|
+
while hasattr(element, "__clause_element__") and not getattr(
|
|
378
|
+
element, "is_clause_element", False
|
|
379
|
+
):
|
|
380
|
+
element = element.__clause_element__()
|
|
381
|
+
|
|
382
|
+
return element
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class _GetChildrenTraversal(HasTraversalDispatch):
|
|
386
|
+
"""Generate a _children_traversal internal traversal dispatch for classes
|
|
387
|
+
with a _traverse_internals collection."""
|
|
388
|
+
|
|
389
|
+
def visit_has_cache_key(self, element, **kw):
|
|
390
|
+
# the GetChildren traversal refers explicitly to ClauseElement
|
|
391
|
+
# structures. Within these, a plain HasCacheKey is not a
|
|
392
|
+
# ClauseElement, so don't include these.
|
|
393
|
+
return ()
|
|
394
|
+
|
|
395
|
+
def visit_clauseelement(self, element, **kw):
|
|
396
|
+
return (element,)
|
|
397
|
+
|
|
398
|
+
def visit_clauseelement_list(self, element, **kw):
|
|
399
|
+
return element
|
|
400
|
+
|
|
401
|
+
def visit_clauseelement_tuple(self, element, **kw):
|
|
402
|
+
return element
|
|
403
|
+
|
|
404
|
+
def visit_clauseelement_tuples(self, element, **kw):
|
|
405
|
+
return itertools.chain.from_iterable(element)
|
|
406
|
+
|
|
407
|
+
def visit_fromclause_canonical_column_collection(self, element, **kw):
|
|
408
|
+
return ()
|
|
409
|
+
|
|
410
|
+
def visit_string_clauseelement_dict(self, element, **kw):
|
|
411
|
+
return element.values()
|
|
412
|
+
|
|
413
|
+
def visit_fromclause_ordered_set(self, element, **kw):
|
|
414
|
+
return element
|
|
415
|
+
|
|
416
|
+
def visit_clauseelement_unordered_set(self, element, **kw):
|
|
417
|
+
return element
|
|
418
|
+
|
|
419
|
+
def visit_setup_join_tuple(self, element, **kw):
|
|
420
|
+
for target, onclause, from_, flags in element:
|
|
421
|
+
if from_ is not None:
|
|
422
|
+
yield from_
|
|
423
|
+
|
|
424
|
+
if not isinstance(target, str):
|
|
425
|
+
yield _flatten_clauseelement(target)
|
|
426
|
+
|
|
427
|
+
if onclause is not None and not isinstance(onclause, str):
|
|
428
|
+
yield _flatten_clauseelement(onclause)
|
|
429
|
+
|
|
430
|
+
def visit_memoized_select_entities(self, element, **kw):
|
|
431
|
+
return self.visit_clauseelement_tuple(element, **kw)
|
|
432
|
+
|
|
433
|
+
def visit_dml_ordered_values(self, element, **kw):
|
|
434
|
+
for k, v in element:
|
|
435
|
+
if hasattr(k, "__clause_element__"):
|
|
436
|
+
yield k
|
|
437
|
+
yield v
|
|
438
|
+
|
|
439
|
+
def visit_dml_values(self, element, **kw):
|
|
440
|
+
expr_values = {k for k in element if hasattr(k, "__clause_element__")}
|
|
441
|
+
str_values = expr_values.symmetric_difference(element)
|
|
442
|
+
|
|
443
|
+
for k in sorted(str_values):
|
|
444
|
+
yield element[k]
|
|
445
|
+
for k in expr_values:
|
|
446
|
+
yield k
|
|
447
|
+
yield element[k]
|
|
448
|
+
|
|
449
|
+
def visit_dml_multi_values(self, element, **kw):
|
|
450
|
+
return ()
|
|
451
|
+
|
|
452
|
+
def visit_propagate_attrs(self, element, **kw):
|
|
453
|
+
return ()
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
_get_children = _GetChildrenTraversal()
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
@util.preload_module("sqlalchemy.sql.elements")
|
|
460
|
+
def _resolve_name_for_compare(element, name, anon_map, **kw):
|
|
461
|
+
if isinstance(name, util.preloaded.sql_elements._anonymous_label):
|
|
462
|
+
name = name.apply_map(anon_map)
|
|
463
|
+
|
|
464
|
+
return name
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
class TraversalComparatorStrategy(HasTraversalDispatch, util.MemoizedSlots):
|
|
468
|
+
__slots__ = "stack", "cache", "anon_map"
|
|
469
|
+
|
|
470
|
+
def __init__(self):
|
|
471
|
+
self.stack: Deque[
|
|
472
|
+
Tuple[
|
|
473
|
+
Optional[ExternallyTraversible],
|
|
474
|
+
Optional[ExternallyTraversible],
|
|
475
|
+
]
|
|
476
|
+
] = deque()
|
|
477
|
+
self.cache = set()
|
|
478
|
+
|
|
479
|
+
def _memoized_attr_anon_map(self):
|
|
480
|
+
return (anon_map(), anon_map())
|
|
481
|
+
|
|
482
|
+
def compare(
|
|
483
|
+
self,
|
|
484
|
+
obj1: ExternallyTraversible,
|
|
485
|
+
obj2: ExternallyTraversible,
|
|
486
|
+
**kw: Any,
|
|
487
|
+
) -> bool:
|
|
488
|
+
stack = self.stack
|
|
489
|
+
cache = self.cache
|
|
490
|
+
|
|
491
|
+
compare_annotations = kw.get("compare_annotations", False)
|
|
492
|
+
|
|
493
|
+
stack.append((obj1, obj2))
|
|
494
|
+
|
|
495
|
+
while stack:
|
|
496
|
+
left, right = stack.popleft()
|
|
497
|
+
|
|
498
|
+
if left is right:
|
|
499
|
+
continue
|
|
500
|
+
elif left is None or right is None:
|
|
501
|
+
# we know they are different so no match
|
|
502
|
+
return False
|
|
503
|
+
elif (left, right) in cache:
|
|
504
|
+
continue
|
|
505
|
+
cache.add((left, right))
|
|
506
|
+
|
|
507
|
+
visit_name = left.__visit_name__
|
|
508
|
+
if visit_name != right.__visit_name__:
|
|
509
|
+
return False
|
|
510
|
+
|
|
511
|
+
meth = getattr(self, "compare_%s" % visit_name, None)
|
|
512
|
+
|
|
513
|
+
if meth:
|
|
514
|
+
attributes_compared = meth(left, right, **kw)
|
|
515
|
+
if attributes_compared is COMPARE_FAILED:
|
|
516
|
+
return False
|
|
517
|
+
elif attributes_compared is SKIP_TRAVERSE:
|
|
518
|
+
continue
|
|
519
|
+
|
|
520
|
+
# attributes_compared is returned as a list of attribute
|
|
521
|
+
# names that were "handled" by the comparison method above.
|
|
522
|
+
# remaining attribute names in the _traverse_internals
|
|
523
|
+
# will be compared.
|
|
524
|
+
else:
|
|
525
|
+
attributes_compared = ()
|
|
526
|
+
|
|
527
|
+
for (
|
|
528
|
+
(left_attrname, left_visit_sym),
|
|
529
|
+
(right_attrname, right_visit_sym),
|
|
530
|
+
) in zip_longest(
|
|
531
|
+
left._traverse_internals,
|
|
532
|
+
right._traverse_internals,
|
|
533
|
+
fillvalue=(None, None),
|
|
534
|
+
):
|
|
535
|
+
if not compare_annotations and (
|
|
536
|
+
(left_attrname == "_annotations")
|
|
537
|
+
or (right_attrname == "_annotations")
|
|
538
|
+
):
|
|
539
|
+
continue
|
|
540
|
+
|
|
541
|
+
if (
|
|
542
|
+
left_attrname != right_attrname
|
|
543
|
+
or left_visit_sym is not right_visit_sym
|
|
544
|
+
):
|
|
545
|
+
return False
|
|
546
|
+
elif left_attrname in attributes_compared:
|
|
547
|
+
continue
|
|
548
|
+
|
|
549
|
+
assert left_visit_sym is not None
|
|
550
|
+
assert left_attrname is not None
|
|
551
|
+
assert right_attrname is not None
|
|
552
|
+
|
|
553
|
+
dispatch = self.dispatch(left_visit_sym)
|
|
554
|
+
assert dispatch is not None, (
|
|
555
|
+
f"{self.__class__} has no dispatch for "
|
|
556
|
+
f"'{self._dispatch_lookup[left_visit_sym]}'"
|
|
557
|
+
)
|
|
558
|
+
left_child = operator.attrgetter(left_attrname)(left)
|
|
559
|
+
right_child = operator.attrgetter(right_attrname)(right)
|
|
560
|
+
if left_child is None:
|
|
561
|
+
if right_child is not None:
|
|
562
|
+
return False
|
|
563
|
+
else:
|
|
564
|
+
continue
|
|
565
|
+
elif right_child is None:
|
|
566
|
+
return False
|
|
567
|
+
|
|
568
|
+
comparison = dispatch(
|
|
569
|
+
left_attrname, left, left_child, right, right_child, **kw
|
|
570
|
+
)
|
|
571
|
+
if comparison is COMPARE_FAILED:
|
|
572
|
+
return False
|
|
573
|
+
|
|
574
|
+
return True
|
|
575
|
+
|
|
576
|
+
def compare_inner(self, obj1, obj2, **kw):
|
|
577
|
+
comparator = self.__class__()
|
|
578
|
+
return comparator.compare(obj1, obj2, **kw)
|
|
579
|
+
|
|
580
|
+
def visit_has_cache_key(
|
|
581
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
582
|
+
):
|
|
583
|
+
if left._gen_cache_key(self.anon_map[0], []) != right._gen_cache_key(
|
|
584
|
+
self.anon_map[1], []
|
|
585
|
+
):
|
|
586
|
+
return COMPARE_FAILED
|
|
587
|
+
|
|
588
|
+
def visit_propagate_attrs(
|
|
589
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
590
|
+
):
|
|
591
|
+
return self.compare_inner(
|
|
592
|
+
left.get("plugin_subject", None), right.get("plugin_subject", None)
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
def visit_has_cache_key_list(
|
|
596
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
597
|
+
):
|
|
598
|
+
for l, r in zip_longest(left, right, fillvalue=None):
|
|
599
|
+
if l is None:
|
|
600
|
+
if r is not None:
|
|
601
|
+
return COMPARE_FAILED
|
|
602
|
+
else:
|
|
603
|
+
continue
|
|
604
|
+
elif r is None:
|
|
605
|
+
return COMPARE_FAILED
|
|
606
|
+
|
|
607
|
+
if l._gen_cache_key(self.anon_map[0], []) != r._gen_cache_key(
|
|
608
|
+
self.anon_map[1], []
|
|
609
|
+
):
|
|
610
|
+
return COMPARE_FAILED
|
|
611
|
+
|
|
612
|
+
def visit_executable_options(
|
|
613
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
614
|
+
):
|
|
615
|
+
for l, r in zip_longest(left, right, fillvalue=None):
|
|
616
|
+
if l is None:
|
|
617
|
+
if r is not None:
|
|
618
|
+
return COMPARE_FAILED
|
|
619
|
+
else:
|
|
620
|
+
continue
|
|
621
|
+
elif r is None:
|
|
622
|
+
return COMPARE_FAILED
|
|
623
|
+
|
|
624
|
+
if (
|
|
625
|
+
l._gen_cache_key(self.anon_map[0], [])
|
|
626
|
+
if l._is_has_cache_key
|
|
627
|
+
else l
|
|
628
|
+
) != (
|
|
629
|
+
r._gen_cache_key(self.anon_map[1], [])
|
|
630
|
+
if r._is_has_cache_key
|
|
631
|
+
else r
|
|
632
|
+
):
|
|
633
|
+
return COMPARE_FAILED
|
|
634
|
+
|
|
635
|
+
def visit_clauseelement(
|
|
636
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
637
|
+
):
|
|
638
|
+
self.stack.append((left, right))
|
|
639
|
+
|
|
640
|
+
def visit_fromclause_canonical_column_collection(
|
|
641
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
642
|
+
):
|
|
643
|
+
for lcol, rcol in zip_longest(left, right, fillvalue=None):
|
|
644
|
+
self.stack.append((lcol, rcol))
|
|
645
|
+
|
|
646
|
+
def visit_fromclause_derived_column_collection(
|
|
647
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
648
|
+
):
|
|
649
|
+
pass
|
|
650
|
+
|
|
651
|
+
def visit_string_clauseelement_dict(
|
|
652
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
653
|
+
):
|
|
654
|
+
for lstr, rstr in zip_longest(
|
|
655
|
+
sorted(left), sorted(right), fillvalue=None
|
|
656
|
+
):
|
|
657
|
+
if lstr != rstr:
|
|
658
|
+
return COMPARE_FAILED
|
|
659
|
+
self.stack.append((left[lstr], right[rstr]))
|
|
660
|
+
|
|
661
|
+
def visit_clauseelement_tuples(
|
|
662
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
663
|
+
):
|
|
664
|
+
for ltup, rtup in zip_longest(left, right, fillvalue=None):
|
|
665
|
+
if ltup is None or rtup is None:
|
|
666
|
+
return COMPARE_FAILED
|
|
667
|
+
|
|
668
|
+
for l, r in zip_longest(ltup, rtup, fillvalue=None):
|
|
669
|
+
self.stack.append((l, r))
|
|
670
|
+
|
|
671
|
+
def visit_clauseelement_list(
|
|
672
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
673
|
+
):
|
|
674
|
+
for l, r in zip_longest(left, right, fillvalue=None):
|
|
675
|
+
self.stack.append((l, r))
|
|
676
|
+
|
|
677
|
+
def visit_clauseelement_tuple(
|
|
678
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
679
|
+
):
|
|
680
|
+
for l, r in zip_longest(left, right, fillvalue=None):
|
|
681
|
+
self.stack.append((l, r))
|
|
682
|
+
|
|
683
|
+
def _compare_unordered_sequences(self, seq1, seq2, **kw):
|
|
684
|
+
if seq1 is None:
|
|
685
|
+
return seq2 is None
|
|
686
|
+
|
|
687
|
+
completed: Set[object] = set()
|
|
688
|
+
for clause in seq1:
|
|
689
|
+
for other_clause in set(seq2).difference(completed):
|
|
690
|
+
if self.compare_inner(clause, other_clause, **kw):
|
|
691
|
+
completed.add(other_clause)
|
|
692
|
+
break
|
|
693
|
+
return len(completed) == len(seq1) == len(seq2)
|
|
694
|
+
|
|
695
|
+
def visit_clauseelement_unordered_set(
|
|
696
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
697
|
+
):
|
|
698
|
+
return self._compare_unordered_sequences(left, right, **kw)
|
|
699
|
+
|
|
700
|
+
def visit_fromclause_ordered_set(
|
|
701
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
702
|
+
):
|
|
703
|
+
for l, r in zip_longest(left, right, fillvalue=None):
|
|
704
|
+
self.stack.append((l, r))
|
|
705
|
+
|
|
706
|
+
def visit_string(
|
|
707
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
708
|
+
):
|
|
709
|
+
return left == right
|
|
710
|
+
|
|
711
|
+
def visit_string_list(
|
|
712
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
713
|
+
):
|
|
714
|
+
return left == right
|
|
715
|
+
|
|
716
|
+
def visit_string_multi_dict(
|
|
717
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
718
|
+
):
|
|
719
|
+
for lk, rk in zip_longest(
|
|
720
|
+
sorted(left.keys()), sorted(right.keys()), fillvalue=(None, None)
|
|
721
|
+
):
|
|
722
|
+
if lk != rk:
|
|
723
|
+
return COMPARE_FAILED
|
|
724
|
+
|
|
725
|
+
lv, rv = left[lk], right[rk]
|
|
726
|
+
|
|
727
|
+
lhc = isinstance(left, HasCacheKey)
|
|
728
|
+
rhc = isinstance(right, HasCacheKey)
|
|
729
|
+
if lhc and rhc:
|
|
730
|
+
if lv._gen_cache_key(
|
|
731
|
+
self.anon_map[0], []
|
|
732
|
+
) != rv._gen_cache_key(self.anon_map[1], []):
|
|
733
|
+
return COMPARE_FAILED
|
|
734
|
+
elif lhc != rhc:
|
|
735
|
+
return COMPARE_FAILED
|
|
736
|
+
elif lv != rv:
|
|
737
|
+
return COMPARE_FAILED
|
|
738
|
+
|
|
739
|
+
def visit_multi(
|
|
740
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
741
|
+
):
|
|
742
|
+
lhc = isinstance(left, HasCacheKey)
|
|
743
|
+
rhc = isinstance(right, HasCacheKey)
|
|
744
|
+
if lhc and rhc:
|
|
745
|
+
if left._gen_cache_key(
|
|
746
|
+
self.anon_map[0], []
|
|
747
|
+
) != right._gen_cache_key(self.anon_map[1], []):
|
|
748
|
+
return COMPARE_FAILED
|
|
749
|
+
elif lhc != rhc:
|
|
750
|
+
return COMPARE_FAILED
|
|
751
|
+
else:
|
|
752
|
+
return left == right
|
|
753
|
+
|
|
754
|
+
def visit_anon_name(
|
|
755
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
756
|
+
):
|
|
757
|
+
return _resolve_name_for_compare(
|
|
758
|
+
left_parent, left, self.anon_map[0], **kw
|
|
759
|
+
) == _resolve_name_for_compare(
|
|
760
|
+
right_parent, right, self.anon_map[1], **kw
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
def visit_boolean(
|
|
764
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
765
|
+
):
|
|
766
|
+
return left == right
|
|
767
|
+
|
|
768
|
+
def visit_operator(
|
|
769
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
770
|
+
):
|
|
771
|
+
return left == right
|
|
772
|
+
|
|
773
|
+
def visit_type(
|
|
774
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
775
|
+
):
|
|
776
|
+
return left._compare_type_affinity(right)
|
|
777
|
+
|
|
778
|
+
def visit_plain_dict(
|
|
779
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
780
|
+
):
|
|
781
|
+
return left == right
|
|
782
|
+
|
|
783
|
+
def visit_dialect_options(
|
|
784
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
785
|
+
):
|
|
786
|
+
return left == right
|
|
787
|
+
|
|
788
|
+
def visit_annotations_key(
|
|
789
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
790
|
+
):
|
|
791
|
+
if left and right:
|
|
792
|
+
return (
|
|
793
|
+
left_parent._annotations_cache_key
|
|
794
|
+
== right_parent._annotations_cache_key
|
|
795
|
+
)
|
|
796
|
+
else:
|
|
797
|
+
return left == right
|
|
798
|
+
|
|
799
|
+
def visit_with_context_options(
|
|
800
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
801
|
+
):
|
|
802
|
+
return tuple((fn.__code__, c_key) for fn, c_key in left) == tuple(
|
|
803
|
+
(fn.__code__, c_key) for fn, c_key in right
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
def visit_plain_obj(
|
|
807
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
808
|
+
):
|
|
809
|
+
return left == right
|
|
810
|
+
|
|
811
|
+
def visit_named_ddl_element(
|
|
812
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
813
|
+
):
|
|
814
|
+
if left is None:
|
|
815
|
+
if right is not None:
|
|
816
|
+
return COMPARE_FAILED
|
|
817
|
+
|
|
818
|
+
return left.name == right.name
|
|
819
|
+
|
|
820
|
+
def visit_prefix_sequence(
|
|
821
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
822
|
+
):
|
|
823
|
+
for (l_clause, l_str), (r_clause, r_str) in zip_longest(
|
|
824
|
+
left, right, fillvalue=(None, None)
|
|
825
|
+
):
|
|
826
|
+
if l_str != r_str:
|
|
827
|
+
return COMPARE_FAILED
|
|
828
|
+
else:
|
|
829
|
+
self.stack.append((l_clause, r_clause))
|
|
830
|
+
|
|
831
|
+
def visit_setup_join_tuple(
|
|
832
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
833
|
+
):
|
|
834
|
+
# TODO: look at attrname for "legacy_join" and use different structure
|
|
835
|
+
for (
|
|
836
|
+
(l_target, l_onclause, l_from, l_flags),
|
|
837
|
+
(r_target, r_onclause, r_from, r_flags),
|
|
838
|
+
) in zip_longest(left, right, fillvalue=(None, None, None, None)):
|
|
839
|
+
if l_flags != r_flags:
|
|
840
|
+
return COMPARE_FAILED
|
|
841
|
+
self.stack.append((l_target, r_target))
|
|
842
|
+
self.stack.append((l_onclause, r_onclause))
|
|
843
|
+
self.stack.append((l_from, r_from))
|
|
844
|
+
|
|
845
|
+
def visit_memoized_select_entities(
|
|
846
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
847
|
+
):
|
|
848
|
+
return self.visit_clauseelement_tuple(
|
|
849
|
+
attrname, left_parent, left, right_parent, right, **kw
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
def visit_table_hint_list(
|
|
853
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
854
|
+
):
|
|
855
|
+
left_keys = sorted(left, key=lambda elem: (elem[0].fullname, elem[1]))
|
|
856
|
+
right_keys = sorted(
|
|
857
|
+
right, key=lambda elem: (elem[0].fullname, elem[1])
|
|
858
|
+
)
|
|
859
|
+
for (ltable, ldialect), (rtable, rdialect) in zip_longest(
|
|
860
|
+
left_keys, right_keys, fillvalue=(None, None)
|
|
861
|
+
):
|
|
862
|
+
if ldialect != rdialect:
|
|
863
|
+
return COMPARE_FAILED
|
|
864
|
+
elif left[(ltable, ldialect)] != right[(rtable, rdialect)]:
|
|
865
|
+
return COMPARE_FAILED
|
|
866
|
+
else:
|
|
867
|
+
self.stack.append((ltable, rtable))
|
|
868
|
+
|
|
869
|
+
def visit_statement_hint_list(
|
|
870
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
871
|
+
):
|
|
872
|
+
return left == right
|
|
873
|
+
|
|
874
|
+
def visit_unknown_structure(
|
|
875
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
876
|
+
):
|
|
877
|
+
raise NotImplementedError()
|
|
878
|
+
|
|
879
|
+
def visit_dml_ordered_values(
|
|
880
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
881
|
+
):
|
|
882
|
+
# sequence of tuple pairs
|
|
883
|
+
|
|
884
|
+
for (lk, lv), (rk, rv) in zip_longest(
|
|
885
|
+
left, right, fillvalue=(None, None)
|
|
886
|
+
):
|
|
887
|
+
if not self._compare_dml_values_or_ce(lk, rk, **kw):
|
|
888
|
+
return COMPARE_FAILED
|
|
889
|
+
|
|
890
|
+
def _compare_dml_values_or_ce(self, lv, rv, **kw):
|
|
891
|
+
lvce = hasattr(lv, "__clause_element__")
|
|
892
|
+
rvce = hasattr(rv, "__clause_element__")
|
|
893
|
+
if lvce != rvce:
|
|
894
|
+
return False
|
|
895
|
+
elif lvce and not self.compare_inner(lv, rv, **kw):
|
|
896
|
+
return False
|
|
897
|
+
elif not lvce and lv != rv:
|
|
898
|
+
return False
|
|
899
|
+
elif not self.compare_inner(lv, rv, **kw):
|
|
900
|
+
return False
|
|
901
|
+
|
|
902
|
+
return True
|
|
903
|
+
|
|
904
|
+
def visit_dml_values(
|
|
905
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
906
|
+
):
|
|
907
|
+
if left is None or right is None or len(left) != len(right):
|
|
908
|
+
return COMPARE_FAILED
|
|
909
|
+
|
|
910
|
+
if isinstance(left, collections_abc.Sequence):
|
|
911
|
+
for lv, rv in zip(left, right):
|
|
912
|
+
if not self._compare_dml_values_or_ce(lv, rv, **kw):
|
|
913
|
+
return COMPARE_FAILED
|
|
914
|
+
elif isinstance(right, collections_abc.Sequence):
|
|
915
|
+
return COMPARE_FAILED
|
|
916
|
+
else:
|
|
917
|
+
# dictionaries guaranteed to support insert ordering in
|
|
918
|
+
# py37 so that we can compare the keys in order. without
|
|
919
|
+
# this, we can't compare SQL expression keys because we don't
|
|
920
|
+
# know which key is which
|
|
921
|
+
for (lk, lv), (rk, rv) in zip(left.items(), right.items()):
|
|
922
|
+
if not self._compare_dml_values_or_ce(lk, rk, **kw):
|
|
923
|
+
return COMPARE_FAILED
|
|
924
|
+
if not self._compare_dml_values_or_ce(lv, rv, **kw):
|
|
925
|
+
return COMPARE_FAILED
|
|
926
|
+
|
|
927
|
+
def visit_dml_multi_values(
|
|
928
|
+
self, attrname, left_parent, left, right_parent, right, **kw
|
|
929
|
+
):
|
|
930
|
+
for lseq, rseq in zip_longest(left, right, fillvalue=None):
|
|
931
|
+
if lseq is None or rseq is None:
|
|
932
|
+
return COMPARE_FAILED
|
|
933
|
+
|
|
934
|
+
for ld, rd in zip_longest(lseq, rseq, fillvalue=None):
|
|
935
|
+
if (
|
|
936
|
+
self.visit_dml_values(
|
|
937
|
+
attrname, left_parent, ld, right_parent, rd, **kw
|
|
938
|
+
)
|
|
939
|
+
is COMPARE_FAILED
|
|
940
|
+
):
|
|
941
|
+
return COMPARE_FAILED
|
|
942
|
+
|
|
943
|
+
def compare_expression_clauselist(self, left, right, **kw):
|
|
944
|
+
if left.operator is right.operator:
|
|
945
|
+
if operators.is_associative(left.operator):
|
|
946
|
+
if self._compare_unordered_sequences(
|
|
947
|
+
left.clauses, right.clauses, **kw
|
|
948
|
+
):
|
|
949
|
+
return ["operator", "clauses"]
|
|
950
|
+
else:
|
|
951
|
+
return COMPARE_FAILED
|
|
952
|
+
else:
|
|
953
|
+
return ["operator"]
|
|
954
|
+
else:
|
|
955
|
+
return COMPARE_FAILED
|
|
956
|
+
|
|
957
|
+
def compare_clauselist(self, left, right, **kw):
|
|
958
|
+
return self.compare_expression_clauselist(left, right, **kw)
|
|
959
|
+
|
|
960
|
+
def compare_binary(self, left, right, **kw):
|
|
961
|
+
if left.operator == right.operator:
|
|
962
|
+
if operators.is_commutative(left.operator):
|
|
963
|
+
if (
|
|
964
|
+
self.compare_inner(left.left, right.left, **kw)
|
|
965
|
+
and self.compare_inner(left.right, right.right, **kw)
|
|
966
|
+
) or (
|
|
967
|
+
self.compare_inner(left.left, right.right, **kw)
|
|
968
|
+
and self.compare_inner(left.right, right.left, **kw)
|
|
969
|
+
):
|
|
970
|
+
return ["operator", "negate", "left", "right"]
|
|
971
|
+
else:
|
|
972
|
+
return COMPARE_FAILED
|
|
973
|
+
else:
|
|
974
|
+
return ["operator", "negate"]
|
|
975
|
+
else:
|
|
976
|
+
return COMPARE_FAILED
|
|
977
|
+
|
|
978
|
+
def compare_bindparam(self, left, right, **kw):
|
|
979
|
+
compare_keys = kw.pop("compare_keys", True)
|
|
980
|
+
compare_values = kw.pop("compare_values", True)
|
|
981
|
+
|
|
982
|
+
if compare_values:
|
|
983
|
+
omit = []
|
|
984
|
+
else:
|
|
985
|
+
# this means, "skip these, we already compared"
|
|
986
|
+
omit = ["callable", "value"]
|
|
987
|
+
|
|
988
|
+
if not compare_keys:
|
|
989
|
+
omit.append("key")
|
|
990
|
+
|
|
991
|
+
return omit
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
class ColIdentityComparatorStrategy(TraversalComparatorStrategy):
|
|
995
|
+
def compare_column_element(
|
|
996
|
+
self, left, right, use_proxies=True, equivalents=(), **kw
|
|
997
|
+
):
|
|
998
|
+
"""Compare ColumnElements using proxies and equivalent collections.
|
|
999
|
+
|
|
1000
|
+
This is a comparison strategy specific to the ORM.
|
|
1001
|
+
"""
|
|
1002
|
+
|
|
1003
|
+
to_compare = (right,)
|
|
1004
|
+
if equivalents and right in equivalents:
|
|
1005
|
+
to_compare = equivalents[right].union(to_compare)
|
|
1006
|
+
|
|
1007
|
+
for oth in to_compare:
|
|
1008
|
+
if use_proxies and left.shares_lineage(oth):
|
|
1009
|
+
return SKIP_TRAVERSE
|
|
1010
|
+
elif hash(left) == hash(right):
|
|
1011
|
+
return SKIP_TRAVERSE
|
|
1012
|
+
else:
|
|
1013
|
+
return COMPARE_FAILED
|
|
1014
|
+
|
|
1015
|
+
def compare_column(self, left, right, **kw):
|
|
1016
|
+
return self.compare_column_element(left, right, **kw)
|
|
1017
|
+
|
|
1018
|
+
def compare_label(self, left, right, **kw):
|
|
1019
|
+
return self.compare_column_element(left, right, **kw)
|
|
1020
|
+
|
|
1021
|
+
def compare_table(self, left, right, **kw):
|
|
1022
|
+
# tables compare on identity, since it's not really feasible to
|
|
1023
|
+
# compare them column by column with the above rules
|
|
1024
|
+
return SKIP_TRAVERSE if left is right else COMPARE_FAILED
|