SQLAlchemy 2.1.0b2__cp313-cp313t-win_arm64.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 +298 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +171 -0
- sqlalchemy/connectors/asyncio.py +476 -0
- sqlalchemy/connectors/pyodbc.py +250 -0
- sqlalchemy/dialects/__init__.py +62 -0
- sqlalchemy/dialects/_typing.py +30 -0
- sqlalchemy/dialects/mssql/__init__.py +89 -0
- sqlalchemy/dialects/mssql/aioodbc.py +63 -0
- sqlalchemy/dialects/mssql/base.py +4166 -0
- sqlalchemy/dialects/mssql/information_schema.py +285 -0
- sqlalchemy/dialects/mssql/json.py +140 -0
- sqlalchemy/dialects/mssql/mssqlpython.py +220 -0
- sqlalchemy/dialects/mssql/provision.py +196 -0
- sqlalchemy/dialects/mssql/pymssql.py +126 -0
- sqlalchemy/dialects/mssql/pyodbc.py +698 -0
- sqlalchemy/dialects/mysql/__init__.py +106 -0
- sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
- sqlalchemy/dialects/mysql/aiomysql.py +226 -0
- sqlalchemy/dialects/mysql/asyncmy.py +214 -0
- sqlalchemy/dialects/mysql/base.py +3877 -0
- sqlalchemy/dialects/mysql/cymysql.py +106 -0
- sqlalchemy/dialects/mysql/dml.py +279 -0
- sqlalchemy/dialects/mysql/enumerated.py +277 -0
- sqlalchemy/dialects/mysql/expression.py +146 -0
- sqlalchemy/dialects/mysql/json.py +92 -0
- sqlalchemy/dialects/mysql/mariadb.py +67 -0
- sqlalchemy/dialects/mysql/mariadbconnector.py +330 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +296 -0
- sqlalchemy/dialects/mysql/mysqldb.py +312 -0
- sqlalchemy/dialects/mysql/provision.py +153 -0
- sqlalchemy/dialects/mysql/pymysql.py +157 -0
- sqlalchemy/dialects/mysql/pyodbc.py +156 -0
- sqlalchemy/dialects/mysql/reflection.py +724 -0
- sqlalchemy/dialects/mysql/reserved_words.py +570 -0
- sqlalchemy/dialects/mysql/types.py +845 -0
- sqlalchemy/dialects/oracle/__init__.py +85 -0
- sqlalchemy/dialects/oracle/base.py +3977 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1601 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/json.py +158 -0
- sqlalchemy/dialects/oracle/oracledb.py +909 -0
- sqlalchemy/dialects/oracle/provision.py +288 -0
- sqlalchemy/dialects/oracle/types.py +367 -0
- sqlalchemy/dialects/oracle/vector.py +368 -0
- sqlalchemy/dialects/postgresql/__init__.py +171 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +229 -0
- sqlalchemy/dialects/postgresql/array.py +534 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1323 -0
- sqlalchemy/dialects/postgresql/base.py +5789 -0
- sqlalchemy/dialects/postgresql/bitstring.py +327 -0
- sqlalchemy/dialects/postgresql/dml.py +360 -0
- sqlalchemy/dialects/postgresql/ext.py +593 -0
- sqlalchemy/dialects/postgresql/hstore.py +423 -0
- sqlalchemy/dialects/postgresql/json.py +408 -0
- sqlalchemy/dialects/postgresql/named_types.py +521 -0
- sqlalchemy/dialects/postgresql/operators.py +130 -0
- sqlalchemy/dialects/postgresql/pg8000.py +670 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +344 -0
- sqlalchemy/dialects/postgresql/provision.py +184 -0
- sqlalchemy/dialects/postgresql/psycopg.py +799 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
- sqlalchemy/dialects/postgresql/ranges.py +1002 -0
- sqlalchemy/dialects/postgresql/types.py +388 -0
- sqlalchemy/dialects/sqlite/__init__.py +57 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +321 -0
- sqlalchemy/dialects/sqlite/base.py +3063 -0
- sqlalchemy/dialects/sqlite/dml.py +279 -0
- sqlalchemy/dialects/sqlite/json.py +100 -0
- sqlalchemy/dialects/sqlite/provision.py +229 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +161 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +754 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_processors_cy.cp313t-win_arm64.pyd +0 -0
- sqlalchemy/engine/_processors_cy.py +92 -0
- sqlalchemy/engine/_result_cy.cp313t-win_arm64.pyd +0 -0
- sqlalchemy/engine/_result_cy.py +633 -0
- sqlalchemy/engine/_row_cy.cp313t-win_arm64.pyd +0 -0
- sqlalchemy/engine/_row_cy.py +232 -0
- sqlalchemy/engine/_util_cy.cp313t-win_arm64.pyd +0 -0
- sqlalchemy/engine/_util_cy.py +136 -0
- sqlalchemy/engine/base.py +3354 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +877 -0
- sqlalchemy/engine/cursor.py +2421 -0
- sqlalchemy/engine/default.py +2402 -0
- sqlalchemy/engine/events.py +965 -0
- sqlalchemy/engine/interfaces.py +3495 -0
- sqlalchemy/engine/mock.py +134 -0
- sqlalchemy/engine/processors.py +82 -0
- sqlalchemy/engine/reflection.py +2100 -0
- sqlalchemy/engine/result.py +1966 -0
- sqlalchemy/engine/row.py +397 -0
- sqlalchemy/engine/strategies.py +16 -0
- sqlalchemy/engine/url.py +922 -0
- sqlalchemy/engine/util.py +156 -0
- sqlalchemy/event/__init__.py +26 -0
- sqlalchemy/event/api.py +220 -0
- sqlalchemy/event/attr.py +674 -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 +922 -0
- sqlalchemy/ext/__init__.py +11 -0
- sqlalchemy/ext/associationproxy.py +2072 -0
- sqlalchemy/ext/asyncio/__init__.py +29 -0
- sqlalchemy/ext/asyncio/base.py +281 -0
- sqlalchemy/ext/asyncio/engine.py +1487 -0
- sqlalchemy/ext/asyncio/exc.py +21 -0
- sqlalchemy/ext/asyncio/result.py +994 -0
- sqlalchemy/ext/asyncio/scoping.py +1679 -0
- sqlalchemy/ext/asyncio/session.py +2007 -0
- sqlalchemy/ext/automap.py +1701 -0
- sqlalchemy/ext/baked.py +559 -0
- sqlalchemy/ext/compiler.py +600 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +560 -0
- sqlalchemy/ext/horizontal_shard.py +481 -0
- sqlalchemy/ext/hybrid.py +1877 -0
- sqlalchemy/ext/indexable.py +364 -0
- sqlalchemy/ext/instrumentation.py +450 -0
- sqlalchemy/ext/mutable.py +1081 -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 +283 -0
- sqlalchemy/orm/__init__.py +176 -0
- sqlalchemy/orm/_orm_constructors.py +2694 -0
- sqlalchemy/orm/_typing.py +179 -0
- sqlalchemy/orm/attributes.py +2868 -0
- sqlalchemy/orm/base.py +976 -0
- sqlalchemy/orm/bulk_persistence.py +2152 -0
- sqlalchemy/orm/clsregistry.py +582 -0
- sqlalchemy/orm/collections.py +1568 -0
- sqlalchemy/orm/context.py +3471 -0
- sqlalchemy/orm/decl_api.py +2280 -0
- sqlalchemy/orm/decl_base.py +2309 -0
- sqlalchemy/orm/dependency.py +1306 -0
- sqlalchemy/orm/descriptor_props.py +1183 -0
- sqlalchemy/orm/dynamic.py +307 -0
- sqlalchemy/orm/evaluator.py +379 -0
- sqlalchemy/orm/events.py +3386 -0
- sqlalchemy/orm/exc.py +237 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +746 -0
- sqlalchemy/orm/interfaces.py +1589 -0
- sqlalchemy/orm/loading.py +1684 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4411 -0
- sqlalchemy/orm/path_registry.py +829 -0
- sqlalchemy/orm/persistence.py +1789 -0
- sqlalchemy/orm/properties.py +973 -0
- sqlalchemy/orm/query.py +3528 -0
- sqlalchemy/orm/relationships.py +3570 -0
- sqlalchemy/orm/scoping.py +2232 -0
- sqlalchemy/orm/session.py +5403 -0
- sqlalchemy/orm/state.py +1175 -0
- sqlalchemy/orm/state_changes.py +196 -0
- sqlalchemy/orm/strategies.py +3492 -0
- sqlalchemy/orm/strategy_options.py +2562 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +798 -0
- sqlalchemy/orm/util.py +2438 -0
- sqlalchemy/orm/writeonly.py +694 -0
- sqlalchemy/pool/__init__.py +41 -0
- sqlalchemy/pool/base.py +1522 -0
- sqlalchemy/pool/events.py +375 -0
- sqlalchemy/pool/impl.py +582 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +74 -0
- sqlalchemy/sql/__init__.py +156 -0
- sqlalchemy/sql/_annotated_cols.py +397 -0
- sqlalchemy/sql/_dml_constructors.py +132 -0
- sqlalchemy/sql/_elements_constructors.py +2164 -0
- sqlalchemy/sql/_orm_types.py +20 -0
- sqlalchemy/sql/_selectable_constructors.py +840 -0
- sqlalchemy/sql/_typing.py +487 -0
- sqlalchemy/sql/_util_cy.cp313t-win_arm64.pyd +0 -0
- sqlalchemy/sql/_util_cy.py +127 -0
- sqlalchemy/sql/annotation.py +590 -0
- sqlalchemy/sql/base.py +2699 -0
- sqlalchemy/sql/cache_key.py +1066 -0
- sqlalchemy/sql/coercions.py +1373 -0
- sqlalchemy/sql/compiler.py +8327 -0
- sqlalchemy/sql/crud.py +1815 -0
- sqlalchemy/sql/ddl.py +1928 -0
- sqlalchemy/sql/default_comparator.py +654 -0
- sqlalchemy/sql/dml.py +1977 -0
- sqlalchemy/sql/elements.py +6033 -0
- sqlalchemy/sql/events.py +458 -0
- sqlalchemy/sql/expression.py +172 -0
- sqlalchemy/sql/functions.py +2305 -0
- sqlalchemy/sql/lambdas.py +1443 -0
- sqlalchemy/sql/naming.py +209 -0
- sqlalchemy/sql/operators.py +2897 -0
- sqlalchemy/sql/roles.py +332 -0
- sqlalchemy/sql/schema.py +6703 -0
- sqlalchemy/sql/selectable.py +7553 -0
- sqlalchemy/sql/sqltypes.py +4093 -0
- sqlalchemy/sql/traversals.py +1042 -0
- sqlalchemy/sql/type_api.py +2446 -0
- sqlalchemy/sql/util.py +1495 -0
- sqlalchemy/sql/visitors.py +1157 -0
- sqlalchemy/testing/__init__.py +96 -0
- sqlalchemy/testing/assertions.py +1007 -0
- sqlalchemy/testing/assertsql.py +519 -0
- sqlalchemy/testing/asyncio.py +128 -0
- sqlalchemy/testing/config.py +440 -0
- sqlalchemy/testing/engines.py +483 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +476 -0
- sqlalchemy/testing/fixtures/__init__.py +30 -0
- sqlalchemy/testing/fixtures/base.py +384 -0
- sqlalchemy/testing/fixtures/mypy.py +247 -0
- sqlalchemy/testing/fixtures/orm.py +227 -0
- sqlalchemy/testing/fixtures/sql.py +538 -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 +613 -0
- sqlalchemy/testing/requirements.py +1978 -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 +420 -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 +660 -0
- sqlalchemy/testing/suite/test_rowcount.py +258 -0
- sqlalchemy/testing/suite/test_select.py +2112 -0
- sqlalchemy/testing/suite/test_sequence.py +317 -0
- sqlalchemy/testing/suite/test_table_via_select.py +686 -0
- sqlalchemy/testing/suite/test_types.py +2271 -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 +76 -0
- sqlalchemy/util/__init__.py +158 -0
- sqlalchemy/util/_collections.py +688 -0
- sqlalchemy/util/_collections_cy.cp313t-win_arm64.pyd +0 -0
- sqlalchemy/util/_collections_cy.pxd +8 -0
- sqlalchemy/util/_collections_cy.py +516 -0
- sqlalchemy/util/_has_cython.py +46 -0
- sqlalchemy/util/_immutabledict_cy.cp313t-win_arm64.pyd +0 -0
- sqlalchemy/util/_immutabledict_cy.py +240 -0
- sqlalchemy/util/compat.py +299 -0
- sqlalchemy/util/concurrency.py +322 -0
- sqlalchemy/util/cython.py +79 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2320 -0
- sqlalchemy/util/preloaded.py +152 -0
- sqlalchemy/util/queue.py +304 -0
- sqlalchemy/util/tool_support.py +201 -0
- sqlalchemy/util/topological.py +120 -0
- sqlalchemy/util/typing.py +711 -0
- sqlalchemy-2.1.0b2.dist-info/METADATA +269 -0
- sqlalchemy-2.1.0b2.dist-info/RECORD +270 -0
- sqlalchemy-2.1.0b2.dist-info/WHEEL +5 -0
- sqlalchemy-2.1.0b2.dist-info/licenses/LICENSE +19 -0
- sqlalchemy-2.1.0b2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# orm/dynamic.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
|
+
|
|
9
|
+
"""Dynamic collection API.
|
|
10
|
+
|
|
11
|
+
Dynamic collections act like Query() objects for read operations and support
|
|
12
|
+
basic add/delete mutation.
|
|
13
|
+
|
|
14
|
+
.. legacy:: the "dynamic" loader is a legacy feature, superseded by the
|
|
15
|
+
"write_only" loader.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from typing import Any
|
|
23
|
+
from typing import Iterable
|
|
24
|
+
from typing import Iterator
|
|
25
|
+
from typing import List
|
|
26
|
+
from typing import Optional
|
|
27
|
+
from typing import overload
|
|
28
|
+
from typing import Tuple
|
|
29
|
+
from typing import Type
|
|
30
|
+
from typing import TYPE_CHECKING
|
|
31
|
+
from typing import TypeVar
|
|
32
|
+
from typing import Union
|
|
33
|
+
|
|
34
|
+
from . import attributes
|
|
35
|
+
from . import exc as orm_exc
|
|
36
|
+
from . import relationships
|
|
37
|
+
from . import util as orm_util
|
|
38
|
+
from .base import PassiveFlag
|
|
39
|
+
from .query import Query
|
|
40
|
+
from .session import object_session
|
|
41
|
+
from .writeonly import _AbstractCollectionWriter
|
|
42
|
+
from .writeonly import _WriteOnlyAttributeImpl
|
|
43
|
+
from .writeonly import _WriteOnlyLoader
|
|
44
|
+
from .writeonly import WriteOnlyHistory
|
|
45
|
+
from .. import util
|
|
46
|
+
from ..engine import result
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
from . import QueryableAttribute
|
|
51
|
+
from .mapper import Mapper
|
|
52
|
+
from .relationships import _RelationshipOrderByArg
|
|
53
|
+
from .session import Session
|
|
54
|
+
from .state import InstanceState
|
|
55
|
+
from .util import AliasedClass
|
|
56
|
+
from ..event import _Dispatch
|
|
57
|
+
from ..sql.elements import ColumnElement
|
|
58
|
+
|
|
59
|
+
_T = TypeVar("_T", bound=Any)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class DynamicCollectionHistory(WriteOnlyHistory[_T]):
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
attr: _DynamicAttributeImpl,
|
|
66
|
+
state: InstanceState[_T],
|
|
67
|
+
passive: PassiveFlag,
|
|
68
|
+
apply_to: Optional[DynamicCollectionHistory[_T]] = None,
|
|
69
|
+
) -> None:
|
|
70
|
+
if apply_to:
|
|
71
|
+
coll = AppenderQuery(attr, state).autoflush(False)
|
|
72
|
+
self.unchanged_items = util.OrderedIdentitySet(coll)
|
|
73
|
+
self.added_items = apply_to.added_items
|
|
74
|
+
self.deleted_items = apply_to.deleted_items
|
|
75
|
+
self._reconcile_collection = True
|
|
76
|
+
else:
|
|
77
|
+
self.deleted_items = util.OrderedIdentitySet()
|
|
78
|
+
self.added_items = util.OrderedIdentitySet()
|
|
79
|
+
self.unchanged_items = util.OrderedIdentitySet()
|
|
80
|
+
self._reconcile_collection = False
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class _DynamicAttributeImpl(_WriteOnlyAttributeImpl):
|
|
84
|
+
_supports_dynamic_iteration = True
|
|
85
|
+
collection_history_cls = DynamicCollectionHistory[Any]
|
|
86
|
+
query_class: Type[_AppenderMixin[Any]] # type: ignore[assignment]
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
class_: Union[Type[Any], AliasedClass[Any]],
|
|
91
|
+
key: str,
|
|
92
|
+
dispatch: _Dispatch[QueryableAttribute[Any]],
|
|
93
|
+
target_mapper: Mapper[_T],
|
|
94
|
+
order_by: _RelationshipOrderByArg,
|
|
95
|
+
query_class: Optional[Type[_AppenderMixin[_T]]] = None,
|
|
96
|
+
**kw: Any,
|
|
97
|
+
) -> None:
|
|
98
|
+
attributes._AttributeImpl.__init__(
|
|
99
|
+
self, class_, key, None, dispatch, **kw
|
|
100
|
+
)
|
|
101
|
+
self.target_mapper = target_mapper
|
|
102
|
+
if order_by:
|
|
103
|
+
self.order_by = tuple(order_by)
|
|
104
|
+
if not query_class:
|
|
105
|
+
self.query_class = AppenderQuery
|
|
106
|
+
elif _AppenderMixin in query_class.mro():
|
|
107
|
+
self.query_class = query_class
|
|
108
|
+
else:
|
|
109
|
+
self.query_class = mixin_user_query(query_class)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@relationships.RelationshipProperty.strategy_for(lazy="dynamic")
|
|
113
|
+
class _DynaLoader(_WriteOnlyLoader):
|
|
114
|
+
impl_class = _DynamicAttributeImpl
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class _AppenderMixin(_AbstractCollectionWriter[_T]):
|
|
118
|
+
"""A mixin that expects to be mixing in a Query class with
|
|
119
|
+
AbstractAppender.
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
query_class: Optional[Type[Query[_T]]] = None
|
|
125
|
+
_order_by_clauses: Tuple[ColumnElement[Any], ...]
|
|
126
|
+
|
|
127
|
+
def __init__(
|
|
128
|
+
self, attr: _DynamicAttributeImpl, state: InstanceState[_T]
|
|
129
|
+
) -> None:
|
|
130
|
+
Query.__init__(
|
|
131
|
+
self, # type: ignore[arg-type]
|
|
132
|
+
attr.target_mapper,
|
|
133
|
+
None,
|
|
134
|
+
)
|
|
135
|
+
super().__init__(attr, state)
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def session(self) -> Optional[Session]:
|
|
139
|
+
sess = object_session(self.instance)
|
|
140
|
+
if sess is not None and sess.autoflush and self.instance in sess:
|
|
141
|
+
sess.flush()
|
|
142
|
+
if not orm_util.has_identity(self.instance):
|
|
143
|
+
return None
|
|
144
|
+
else:
|
|
145
|
+
return sess
|
|
146
|
+
|
|
147
|
+
@session.setter
|
|
148
|
+
def session(self, session: Session) -> None:
|
|
149
|
+
self.sess = session
|
|
150
|
+
|
|
151
|
+
def _iter(self) -> Union[result.ScalarResult[_T], result.Result[_T]]:
|
|
152
|
+
sess = self.session
|
|
153
|
+
if sess is None:
|
|
154
|
+
state = attributes.instance_state(self.instance)
|
|
155
|
+
if state.detached:
|
|
156
|
+
util.warn(
|
|
157
|
+
"Instance %s is detached, dynamic relationship cannot "
|
|
158
|
+
"return a correct result. This warning will become "
|
|
159
|
+
"a DetachedInstanceError in a future release."
|
|
160
|
+
% (orm_util.state_str(state))
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return result.IteratorResult(
|
|
164
|
+
result.SimpleResultMetaData([self.attr.class_.__name__]),
|
|
165
|
+
iter(
|
|
166
|
+
self.attr._get_collection_history(
|
|
167
|
+
attributes.instance_state(self.instance),
|
|
168
|
+
PassiveFlag.PASSIVE_NO_INITIALIZE,
|
|
169
|
+
).added_items
|
|
170
|
+
),
|
|
171
|
+
_source_supports_scalars=True,
|
|
172
|
+
).scalars()
|
|
173
|
+
else:
|
|
174
|
+
return self._generate(sess)._iter()
|
|
175
|
+
|
|
176
|
+
if TYPE_CHECKING:
|
|
177
|
+
|
|
178
|
+
def __iter__(self) -> Iterator[_T]: ...
|
|
179
|
+
|
|
180
|
+
@overload
|
|
181
|
+
def __getitem__(self, index: int) -> _T: ...
|
|
182
|
+
|
|
183
|
+
@overload
|
|
184
|
+
def __getitem__(self, index: slice) -> List[_T]: ...
|
|
185
|
+
|
|
186
|
+
def __getitem__(self, index: Union[int, slice]) -> Union[_T, List[_T]]:
|
|
187
|
+
sess = self.session
|
|
188
|
+
if sess is None:
|
|
189
|
+
return self.attr._get_collection_history(
|
|
190
|
+
attributes.instance_state(self.instance),
|
|
191
|
+
PassiveFlag.PASSIVE_NO_INITIALIZE,
|
|
192
|
+
).indexed(index)
|
|
193
|
+
else:
|
|
194
|
+
return self._generate(sess).__getitem__(index)
|
|
195
|
+
|
|
196
|
+
def count(self) -> int:
|
|
197
|
+
sess = self.session
|
|
198
|
+
if sess is None:
|
|
199
|
+
return len(
|
|
200
|
+
self.attr._get_collection_history(
|
|
201
|
+
attributes.instance_state(self.instance),
|
|
202
|
+
PassiveFlag.PASSIVE_NO_INITIALIZE,
|
|
203
|
+
).added_items
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
return self._generate(sess).count()
|
|
207
|
+
|
|
208
|
+
def _generate(
|
|
209
|
+
self,
|
|
210
|
+
sess: Optional[Session] = None,
|
|
211
|
+
) -> Query[_T]:
|
|
212
|
+
# note we're returning an entirely new Query class instance
|
|
213
|
+
# here without any assignment capabilities; the class of this
|
|
214
|
+
# query is determined by the session.
|
|
215
|
+
instance = self.instance
|
|
216
|
+
if sess is None:
|
|
217
|
+
sess = object_session(instance)
|
|
218
|
+
if sess is None:
|
|
219
|
+
raise orm_exc.DetachedInstanceError(
|
|
220
|
+
"Parent instance %s is not bound to a Session, and no "
|
|
221
|
+
"contextual session is established; lazy load operation "
|
|
222
|
+
"of attribute '%s' cannot proceed"
|
|
223
|
+
% (orm_util.instance_str(instance), self.attr.key)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if self.query_class:
|
|
227
|
+
query = self.query_class(self.attr.target_mapper, session=sess)
|
|
228
|
+
else:
|
|
229
|
+
query = sess.query(self.attr.target_mapper)
|
|
230
|
+
|
|
231
|
+
query._where_criteria = self._where_criteria
|
|
232
|
+
query._from_obj = self._from_obj
|
|
233
|
+
query._order_by_clauses = self._order_by_clauses
|
|
234
|
+
|
|
235
|
+
return query
|
|
236
|
+
|
|
237
|
+
def add_all(self, iterator: Iterable[_T]) -> None:
|
|
238
|
+
"""Add an iterable of items to this :class:`_orm.AppenderQuery`.
|
|
239
|
+
|
|
240
|
+
The given items will be persisted to the database in terms of
|
|
241
|
+
the parent instance's collection on the next flush.
|
|
242
|
+
|
|
243
|
+
This method is provided to assist in delivering forwards-compatibility
|
|
244
|
+
with the :class:`_orm.WriteOnlyCollection` collection class.
|
|
245
|
+
|
|
246
|
+
.. versionadded:: 2.0
|
|
247
|
+
|
|
248
|
+
"""
|
|
249
|
+
self._add_all_impl(iterator)
|
|
250
|
+
|
|
251
|
+
def add(self, item: _T) -> None:
|
|
252
|
+
"""Add an item to this :class:`_orm.AppenderQuery`.
|
|
253
|
+
|
|
254
|
+
The given item will be persisted to the database in terms of
|
|
255
|
+
the parent instance's collection on the next flush.
|
|
256
|
+
|
|
257
|
+
This method is provided to assist in delivering forwards-compatibility
|
|
258
|
+
with the :class:`_orm.WriteOnlyCollection` collection class.
|
|
259
|
+
|
|
260
|
+
.. versionadded:: 2.0
|
|
261
|
+
|
|
262
|
+
"""
|
|
263
|
+
self._add_all_impl([item])
|
|
264
|
+
|
|
265
|
+
def extend(self, iterator: Iterable[_T]) -> None:
|
|
266
|
+
"""Add an iterable of items to this :class:`_orm.AppenderQuery`.
|
|
267
|
+
|
|
268
|
+
The given items will be persisted to the database in terms of
|
|
269
|
+
the parent instance's collection on the next flush.
|
|
270
|
+
|
|
271
|
+
"""
|
|
272
|
+
self._add_all_impl(iterator)
|
|
273
|
+
|
|
274
|
+
def append(self, item: _T) -> None:
|
|
275
|
+
"""Append an item to this :class:`_orm.AppenderQuery`.
|
|
276
|
+
|
|
277
|
+
The given item will be persisted to the database in terms of
|
|
278
|
+
the parent instance's collection on the next flush.
|
|
279
|
+
|
|
280
|
+
"""
|
|
281
|
+
self._add_all_impl([item])
|
|
282
|
+
|
|
283
|
+
def remove(self, item: _T) -> None:
|
|
284
|
+
"""Remove an item from this :class:`_orm.AppenderQuery`.
|
|
285
|
+
|
|
286
|
+
The given item will be removed from the parent instance's collection on
|
|
287
|
+
the next flush.
|
|
288
|
+
|
|
289
|
+
"""
|
|
290
|
+
self._remove_impl(item)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class AppenderQuery(_AppenderMixin[_T], Query[_T]): # type: ignore[misc]
|
|
294
|
+
"""A dynamic query that supports basic collection storage operations.
|
|
295
|
+
|
|
296
|
+
Methods on :class:`.AppenderQuery` include all methods of
|
|
297
|
+
:class:`_orm.Query`, plus additional methods used for collection
|
|
298
|
+
persistence.
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def mixin_user_query(cls: Any) -> type[_AppenderMixin[Any]]:
|
|
305
|
+
"""Return a new class with AppenderQuery functionality layered over."""
|
|
306
|
+
name = "Appender" + cls.__name__
|
|
307
|
+
return type(name, (_AppenderMixin, cls), {"query_class": cls})
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
# orm/evaluator.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: ignore-errors
|
|
8
|
+
|
|
9
|
+
"""Evaluation functions used **INTERNALLY** by ORM DML use cases.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
This module is **private, for internal use by SQLAlchemy**.
|
|
13
|
+
|
|
14
|
+
.. versionchanged:: 2.0.4 renamed ``EvaluatorCompiler`` to
|
|
15
|
+
``_EvaluatorCompiler``.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from typing import Type
|
|
23
|
+
|
|
24
|
+
from . import exc as orm_exc
|
|
25
|
+
from .base import LoaderCallableStatus
|
|
26
|
+
from .base import PassiveFlag
|
|
27
|
+
from .. import exc
|
|
28
|
+
from .. import inspect
|
|
29
|
+
from ..sql import and_
|
|
30
|
+
from ..sql import operators
|
|
31
|
+
from ..sql.sqltypes import Concatenable
|
|
32
|
+
from ..sql.sqltypes import Integer
|
|
33
|
+
from ..sql.sqltypes import Numeric
|
|
34
|
+
from ..util import warn_deprecated
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class UnevaluatableError(exc.InvalidRequestError):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class _NoObject(operators.ColumnOperators):
|
|
42
|
+
def operate(self, *arg, **kw):
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
def reverse_operate(self, *arg, **kw):
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class _ExpiredObject(operators.ColumnOperators):
|
|
50
|
+
def operate(self, *arg, **kw):
|
|
51
|
+
return self
|
|
52
|
+
|
|
53
|
+
def reverse_operate(self, *arg, **kw):
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_NO_OBJECT = _NoObject()
|
|
58
|
+
_EXPIRED_OBJECT = _ExpiredObject()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class _EvaluatorCompiler:
|
|
62
|
+
def __init__(self, target_cls=None):
|
|
63
|
+
self.target_cls = target_cls
|
|
64
|
+
|
|
65
|
+
def process(self, clause, *clauses):
|
|
66
|
+
if clauses:
|
|
67
|
+
clause = and_(clause, *clauses)
|
|
68
|
+
|
|
69
|
+
meth = getattr(self, f"visit_{clause.__visit_name__}", None)
|
|
70
|
+
if not meth:
|
|
71
|
+
raise UnevaluatableError(
|
|
72
|
+
f"Cannot evaluate {type(clause).__name__}"
|
|
73
|
+
)
|
|
74
|
+
return meth(clause)
|
|
75
|
+
|
|
76
|
+
def visit_grouping(self, clause):
|
|
77
|
+
return self.process(clause.element)
|
|
78
|
+
|
|
79
|
+
def visit_null(self, clause):
|
|
80
|
+
return lambda obj: None
|
|
81
|
+
|
|
82
|
+
def visit_false(self, clause):
|
|
83
|
+
return lambda obj: False
|
|
84
|
+
|
|
85
|
+
def visit_true(self, clause):
|
|
86
|
+
return lambda obj: True
|
|
87
|
+
|
|
88
|
+
def visit_column(self, clause):
|
|
89
|
+
try:
|
|
90
|
+
parentmapper = clause._annotations["parentmapper"]
|
|
91
|
+
except KeyError as ke:
|
|
92
|
+
raise UnevaluatableError(
|
|
93
|
+
f"Cannot evaluate column: {clause}"
|
|
94
|
+
) from ke
|
|
95
|
+
|
|
96
|
+
if self.target_cls and not issubclass(
|
|
97
|
+
self.target_cls, parentmapper.class_
|
|
98
|
+
):
|
|
99
|
+
raise UnevaluatableError(
|
|
100
|
+
"Can't evaluate criteria against "
|
|
101
|
+
f"alternate class {parentmapper.class_}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
parentmapper._check_configure()
|
|
105
|
+
|
|
106
|
+
# we'd like to use "proxy_key" annotation to get the "key", however
|
|
107
|
+
# in relationship primaryjoin cases proxy_key is sometimes deannotated
|
|
108
|
+
# and sometimes apparently not present in the first place (?).
|
|
109
|
+
# While I can stop it from being deannotated (though need to see if
|
|
110
|
+
# this breaks other things), not sure right now about cases where it's
|
|
111
|
+
# not there in the first place. can fix at some later point.
|
|
112
|
+
# key = clause._annotations["proxy_key"]
|
|
113
|
+
|
|
114
|
+
# for now, use the old way
|
|
115
|
+
try:
|
|
116
|
+
key = parentmapper._columntoproperty[clause].key
|
|
117
|
+
except orm_exc.UnmappedColumnError as err:
|
|
118
|
+
raise UnevaluatableError(
|
|
119
|
+
f"Cannot evaluate expression: {err}"
|
|
120
|
+
) from err
|
|
121
|
+
|
|
122
|
+
# note this used to fall back to a simple `getattr(obj, key)` evaluator
|
|
123
|
+
# if impl was None; as of #8656, we ensure mappers are configured
|
|
124
|
+
# so that impl is available
|
|
125
|
+
impl = parentmapper.class_manager[key].impl
|
|
126
|
+
|
|
127
|
+
def get_corresponding_attr(obj):
|
|
128
|
+
if obj is None:
|
|
129
|
+
return _NO_OBJECT
|
|
130
|
+
state = inspect(obj)
|
|
131
|
+
dict_ = state.dict
|
|
132
|
+
|
|
133
|
+
value = impl.get(
|
|
134
|
+
state, dict_, passive=PassiveFlag.PASSIVE_NO_FETCH
|
|
135
|
+
)
|
|
136
|
+
if value is LoaderCallableStatus.PASSIVE_NO_RESULT:
|
|
137
|
+
return _EXPIRED_OBJECT
|
|
138
|
+
return value
|
|
139
|
+
|
|
140
|
+
return get_corresponding_attr
|
|
141
|
+
|
|
142
|
+
def visit_tuple(self, clause):
|
|
143
|
+
return self.visit_clauselist(clause)
|
|
144
|
+
|
|
145
|
+
def visit_expression_clauselist(self, clause):
|
|
146
|
+
return self.visit_clauselist(clause)
|
|
147
|
+
|
|
148
|
+
def visit_clauselist(self, clause):
|
|
149
|
+
evaluators = [self.process(clause) for clause in clause.clauses]
|
|
150
|
+
|
|
151
|
+
dispatch = (
|
|
152
|
+
f"visit_{clause.operator.__name__.rstrip('_')}_clauselist_op"
|
|
153
|
+
)
|
|
154
|
+
meth = getattr(self, dispatch, None)
|
|
155
|
+
if meth:
|
|
156
|
+
return meth(clause.operator, evaluators, clause)
|
|
157
|
+
else:
|
|
158
|
+
raise UnevaluatableError(
|
|
159
|
+
f"Cannot evaluate clauselist with operator {clause.operator}"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def visit_binary(self, clause):
|
|
163
|
+
eval_left = self.process(clause.left)
|
|
164
|
+
eval_right = self.process(clause.right)
|
|
165
|
+
|
|
166
|
+
dispatch = f"visit_{clause.operator.__name__.rstrip('_')}_binary_op"
|
|
167
|
+
meth = getattr(self, dispatch, None)
|
|
168
|
+
if meth:
|
|
169
|
+
return meth(clause.operator, eval_left, eval_right, clause)
|
|
170
|
+
else:
|
|
171
|
+
raise UnevaluatableError(
|
|
172
|
+
f"Cannot evaluate {type(clause).__name__} with "
|
|
173
|
+
f"operator {clause.operator}"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def visit_or_clauselist_op(self, operator, evaluators, clause):
|
|
177
|
+
def evaluate(obj):
|
|
178
|
+
has_null = False
|
|
179
|
+
for sub_evaluate in evaluators:
|
|
180
|
+
value = sub_evaluate(obj)
|
|
181
|
+
if value is _EXPIRED_OBJECT:
|
|
182
|
+
return _EXPIRED_OBJECT
|
|
183
|
+
elif value:
|
|
184
|
+
return True
|
|
185
|
+
has_null = has_null or value is None
|
|
186
|
+
if has_null:
|
|
187
|
+
return None
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
return evaluate
|
|
191
|
+
|
|
192
|
+
def visit_and_clauselist_op(self, operator, evaluators, clause):
|
|
193
|
+
def evaluate(obj):
|
|
194
|
+
for sub_evaluate in evaluators:
|
|
195
|
+
value = sub_evaluate(obj)
|
|
196
|
+
if value is _EXPIRED_OBJECT:
|
|
197
|
+
return _EXPIRED_OBJECT
|
|
198
|
+
|
|
199
|
+
if not value:
|
|
200
|
+
if value is None or value is _NO_OBJECT:
|
|
201
|
+
return None
|
|
202
|
+
return False
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
return evaluate
|
|
206
|
+
|
|
207
|
+
def visit_comma_op_clauselist_op(self, operator, evaluators, clause):
|
|
208
|
+
def evaluate(obj):
|
|
209
|
+
values = []
|
|
210
|
+
for sub_evaluate in evaluators:
|
|
211
|
+
value = sub_evaluate(obj)
|
|
212
|
+
if value is _EXPIRED_OBJECT:
|
|
213
|
+
return _EXPIRED_OBJECT
|
|
214
|
+
elif value is None or value is _NO_OBJECT:
|
|
215
|
+
return None
|
|
216
|
+
values.append(value)
|
|
217
|
+
return tuple(values)
|
|
218
|
+
|
|
219
|
+
return evaluate
|
|
220
|
+
|
|
221
|
+
def visit_custom_op_binary_op(
|
|
222
|
+
self, operator, eval_left, eval_right, clause
|
|
223
|
+
):
|
|
224
|
+
if operator.python_impl:
|
|
225
|
+
return self._straight_evaluate(
|
|
226
|
+
operator, eval_left, eval_right, clause
|
|
227
|
+
)
|
|
228
|
+
else:
|
|
229
|
+
raise UnevaluatableError(
|
|
230
|
+
f"Custom operator {operator.opstring!r} can't be evaluated "
|
|
231
|
+
"in Python unless it specifies a callable using "
|
|
232
|
+
"`.python_impl`."
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def visit_is_binary_op(self, operator, eval_left, eval_right, clause):
|
|
236
|
+
def evaluate(obj):
|
|
237
|
+
left_val = eval_left(obj)
|
|
238
|
+
right_val = eval_right(obj)
|
|
239
|
+
if left_val is _EXPIRED_OBJECT or right_val is _EXPIRED_OBJECT:
|
|
240
|
+
return _EXPIRED_OBJECT
|
|
241
|
+
return left_val == right_val
|
|
242
|
+
|
|
243
|
+
return evaluate
|
|
244
|
+
|
|
245
|
+
def visit_is_not_binary_op(self, operator, eval_left, eval_right, clause):
|
|
246
|
+
def evaluate(obj):
|
|
247
|
+
left_val = eval_left(obj)
|
|
248
|
+
right_val = eval_right(obj)
|
|
249
|
+
if left_val is _EXPIRED_OBJECT or right_val is _EXPIRED_OBJECT:
|
|
250
|
+
return _EXPIRED_OBJECT
|
|
251
|
+
return left_val != right_val
|
|
252
|
+
|
|
253
|
+
return evaluate
|
|
254
|
+
|
|
255
|
+
def _straight_evaluate(self, operator, eval_left, eval_right, clause):
|
|
256
|
+
def evaluate(obj):
|
|
257
|
+
left_val = eval_left(obj)
|
|
258
|
+
right_val = eval_right(obj)
|
|
259
|
+
if left_val is _EXPIRED_OBJECT or right_val is _EXPIRED_OBJECT:
|
|
260
|
+
return _EXPIRED_OBJECT
|
|
261
|
+
elif left_val is None or right_val is None:
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
return operator(eval_left(obj), eval_right(obj))
|
|
265
|
+
|
|
266
|
+
return evaluate
|
|
267
|
+
|
|
268
|
+
def _straight_evaluate_numeric_only(
|
|
269
|
+
self, operator, eval_left, eval_right, clause
|
|
270
|
+
):
|
|
271
|
+
if clause.left.type._type_affinity not in (
|
|
272
|
+
Numeric,
|
|
273
|
+
Integer,
|
|
274
|
+
) or clause.right.type._type_affinity not in (Numeric, Integer):
|
|
275
|
+
raise UnevaluatableError(
|
|
276
|
+
f'Cannot evaluate math operator "{operator.__name__}" for '
|
|
277
|
+
f"datatypes {clause.left.type}, {clause.right.type}"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
return self._straight_evaluate(operator, eval_left, eval_right, clause)
|
|
281
|
+
|
|
282
|
+
visit_add_binary_op = _straight_evaluate_numeric_only
|
|
283
|
+
visit_mul_binary_op = _straight_evaluate_numeric_only
|
|
284
|
+
visit_sub_binary_op = _straight_evaluate_numeric_only
|
|
285
|
+
visit_mod_binary_op = _straight_evaluate_numeric_only
|
|
286
|
+
visit_truediv_binary_op = _straight_evaluate_numeric_only
|
|
287
|
+
visit_lt_binary_op = _straight_evaluate
|
|
288
|
+
visit_le_binary_op = _straight_evaluate
|
|
289
|
+
visit_ne_binary_op = _straight_evaluate
|
|
290
|
+
visit_gt_binary_op = _straight_evaluate
|
|
291
|
+
visit_ge_binary_op = _straight_evaluate
|
|
292
|
+
visit_eq_binary_op = _straight_evaluate
|
|
293
|
+
|
|
294
|
+
def visit_in_op_binary_op(self, operator, eval_left, eval_right, clause):
|
|
295
|
+
return self._straight_evaluate(
|
|
296
|
+
lambda a, b: a in b if a is not _NO_OBJECT else None,
|
|
297
|
+
eval_left,
|
|
298
|
+
eval_right,
|
|
299
|
+
clause,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
def visit_not_in_op_binary_op(
|
|
303
|
+
self, operator, eval_left, eval_right, clause
|
|
304
|
+
):
|
|
305
|
+
return self._straight_evaluate(
|
|
306
|
+
lambda a, b: a not in b if a is not _NO_OBJECT else None,
|
|
307
|
+
eval_left,
|
|
308
|
+
eval_right,
|
|
309
|
+
clause,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
def visit_concat_op_binary_op(
|
|
313
|
+
self, operator, eval_left, eval_right, clause
|
|
314
|
+
):
|
|
315
|
+
|
|
316
|
+
if not issubclass(
|
|
317
|
+
clause.left.type._type_affinity, Concatenable
|
|
318
|
+
) or not issubclass(clause.right.type._type_affinity, Concatenable):
|
|
319
|
+
raise UnevaluatableError(
|
|
320
|
+
f"Cannot evaluate concatenate operator "
|
|
321
|
+
f'"{operator.__name__}" for '
|
|
322
|
+
f"datatypes {clause.left.type}, {clause.right.type}"
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
return self._straight_evaluate(
|
|
326
|
+
lambda a, b: a + b, eval_left, eval_right, clause
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
def visit_startswith_op_binary_op(
|
|
330
|
+
self, operator, eval_left, eval_right, clause
|
|
331
|
+
):
|
|
332
|
+
return self._straight_evaluate(
|
|
333
|
+
lambda a, b: a.startswith(b), eval_left, eval_right, clause
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def visit_endswith_op_binary_op(
|
|
337
|
+
self, operator, eval_left, eval_right, clause
|
|
338
|
+
):
|
|
339
|
+
return self._straight_evaluate(
|
|
340
|
+
lambda a, b: a.endswith(b), eval_left, eval_right, clause
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
def visit_unary(self, clause):
|
|
344
|
+
eval_inner = self.process(clause.element)
|
|
345
|
+
if clause.operator is operators.inv:
|
|
346
|
+
|
|
347
|
+
def evaluate(obj):
|
|
348
|
+
value = eval_inner(obj)
|
|
349
|
+
if value is _EXPIRED_OBJECT:
|
|
350
|
+
return _EXPIRED_OBJECT
|
|
351
|
+
elif value is None:
|
|
352
|
+
return None
|
|
353
|
+
return not value
|
|
354
|
+
|
|
355
|
+
return evaluate
|
|
356
|
+
raise UnevaluatableError(
|
|
357
|
+
f"Cannot evaluate {type(clause).__name__} "
|
|
358
|
+
f"with operator {clause.operator}"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
def visit_bindparam(self, clause):
|
|
362
|
+
if clause.callable:
|
|
363
|
+
val = clause.callable()
|
|
364
|
+
else:
|
|
365
|
+
val = clause.value
|
|
366
|
+
return lambda obj: val
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def __getattr__(name: str) -> Type[_EvaluatorCompiler]:
|
|
370
|
+
if name == "EvaluatorCompiler":
|
|
371
|
+
warn_deprecated(
|
|
372
|
+
"Direct use of 'EvaluatorCompiler' is not supported, and this "
|
|
373
|
+
"name will be removed in a future release. "
|
|
374
|
+
"'_EvaluatorCompiler' is for internal use only",
|
|
375
|
+
"2.0",
|
|
376
|
+
)
|
|
377
|
+
return _EvaluatorCompiler
|
|
378
|
+
else:
|
|
379
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|