Загрузить файлы в «venv/Lib/site-packages/sqlalchemy/orm»
This commit is contained in:
2192
venv/Lib/site-packages/sqlalchemy/orm/decl_base.py
Normal file
2192
venv/Lib/site-packages/sqlalchemy/orm/decl_base.py
Normal file
File diff suppressed because it is too large
Load Diff
1302
venv/Lib/site-packages/sqlalchemy/orm/dependency.py
Normal file
1302
venv/Lib/site-packages/sqlalchemy/orm/dependency.py
Normal file
File diff suppressed because it is too large
Load Diff
1092
venv/Lib/site-packages/sqlalchemy/orm/descriptor_props.py
Normal file
1092
venv/Lib/site-packages/sqlalchemy/orm/descriptor_props.py
Normal file
File diff suppressed because it is too large
Load Diff
306
venv/Lib/site-packages/sqlalchemy/orm/dynamic.py
Normal file
306
venv/Lib/site-packages/sqlalchemy/orm/dynamic.py
Normal file
@@ -0,0 +1,306 @@
|
||||
# orm/dynamic.py
|
||||
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
|
||||
"""Dynamic collection API.
|
||||
|
||||
Dynamic collections act like Query() objects for read operations and support
|
||||
basic add/delete mutation.
|
||||
|
||||
.. legacy:: the "dynamic" loader is a legacy feature, superseded by the
|
||||
"write_only" loader.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from . import attributes
|
||||
from . import exc as orm_exc
|
||||
from . import relationships
|
||||
from . import util as orm_util
|
||||
from .base import PassiveFlag
|
||||
from .query import Query
|
||||
from .session import object_session
|
||||
from .writeonly import AbstractCollectionWriter
|
||||
from .writeonly import WriteOnlyAttributeImpl
|
||||
from .writeonly import WriteOnlyHistory
|
||||
from .writeonly import WriteOnlyLoader
|
||||
from .. import util
|
||||
from ..engine import result
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import QueryableAttribute
|
||||
from .mapper import Mapper
|
||||
from .relationships import _RelationshipOrderByArg
|
||||
from .session import Session
|
||||
from .state import InstanceState
|
||||
from .util import AliasedClass
|
||||
from ..event import _Dispatch
|
||||
from ..sql.elements import ColumnElement
|
||||
|
||||
_T = TypeVar("_T", bound=Any)
|
||||
|
||||
|
||||
class DynamicCollectionHistory(WriteOnlyHistory[_T]):
|
||||
def __init__(
|
||||
self,
|
||||
attr: DynamicAttributeImpl,
|
||||
state: InstanceState[_T],
|
||||
passive: PassiveFlag,
|
||||
apply_to: Optional[DynamicCollectionHistory[_T]] = None,
|
||||
) -> None:
|
||||
if apply_to:
|
||||
coll = AppenderQuery(attr, state).autoflush(False)
|
||||
self.unchanged_items = util.OrderedIdentitySet(coll)
|
||||
self.added_items = apply_to.added_items
|
||||
self.deleted_items = apply_to.deleted_items
|
||||
self._reconcile_collection = True
|
||||
else:
|
||||
self.deleted_items = util.OrderedIdentitySet()
|
||||
self.added_items = util.OrderedIdentitySet()
|
||||
self.unchanged_items = util.OrderedIdentitySet()
|
||||
self._reconcile_collection = False
|
||||
|
||||
|
||||
class DynamicAttributeImpl(WriteOnlyAttributeImpl):
|
||||
_supports_dynamic_iteration = True
|
||||
collection_history_cls = DynamicCollectionHistory[Any]
|
||||
query_class: Type[AppenderMixin[Any]] # type: ignore[assignment]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
class_: Union[Type[Any], AliasedClass[Any]],
|
||||
key: str,
|
||||
dispatch: _Dispatch[QueryableAttribute[Any]],
|
||||
target_mapper: Mapper[_T],
|
||||
order_by: _RelationshipOrderByArg,
|
||||
query_class: Optional[Type[AppenderMixin[_T]]] = None,
|
||||
**kw: Any,
|
||||
) -> None:
|
||||
attributes.AttributeImpl.__init__(
|
||||
self, class_, key, None, dispatch, **kw
|
||||
)
|
||||
self.target_mapper = target_mapper
|
||||
if order_by:
|
||||
self.order_by = tuple(order_by)
|
||||
if not query_class:
|
||||
self.query_class = AppenderQuery
|
||||
elif AppenderMixin in query_class.mro():
|
||||
self.query_class = query_class
|
||||
else:
|
||||
self.query_class = mixin_user_query(query_class)
|
||||
|
||||
|
||||
@relationships.RelationshipProperty.strategy_for(lazy="dynamic")
|
||||
class DynaLoader(WriteOnlyLoader):
|
||||
impl_class = DynamicAttributeImpl
|
||||
|
||||
|
||||
class AppenderMixin(AbstractCollectionWriter[_T]):
|
||||
"""A mixin that expects to be mixing in a Query class with
|
||||
AbstractAppender.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
query_class: Optional[Type[Query[_T]]] = None
|
||||
_order_by_clauses: Tuple[ColumnElement[Any], ...]
|
||||
|
||||
def __init__(
|
||||
self, attr: DynamicAttributeImpl, state: InstanceState[_T]
|
||||
) -> None:
|
||||
Query.__init__(
|
||||
self, # type: ignore[arg-type]
|
||||
attr.target_mapper,
|
||||
None,
|
||||
)
|
||||
super().__init__(attr, state)
|
||||
|
||||
@property
|
||||
def session(self) -> Optional[Session]:
|
||||
sess = object_session(self.instance)
|
||||
if sess is not None and sess.autoflush and self.instance in sess:
|
||||
sess.flush()
|
||||
if not orm_util.has_identity(self.instance):
|
||||
return None
|
||||
else:
|
||||
return sess
|
||||
|
||||
@session.setter
|
||||
def session(self, session: Session) -> None:
|
||||
self.sess = session
|
||||
|
||||
def _iter(self) -> Union[result.ScalarResult[_T], result.Result[_T]]:
|
||||
sess = self.session
|
||||
if sess is None:
|
||||
state = attributes.instance_state(self.instance)
|
||||
if state.detached:
|
||||
util.warn(
|
||||
"Instance %s is detached, dynamic relationship cannot "
|
||||
"return a correct result. This warning will become "
|
||||
"a DetachedInstanceError in a future release."
|
||||
% (orm_util.state_str(state))
|
||||
)
|
||||
|
||||
return result.IteratorResult(
|
||||
result.SimpleResultMetaData([self.attr.class_.__name__]),
|
||||
iter(
|
||||
self.attr._get_collection_history(
|
||||
attributes.instance_state(self.instance),
|
||||
PassiveFlag.PASSIVE_NO_INITIALIZE,
|
||||
).added_items
|
||||
),
|
||||
_source_supports_scalars=True,
|
||||
).scalars()
|
||||
else:
|
||||
return self._generate(sess)._iter()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
def __iter__(self) -> Iterator[_T]: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: int) -> _T: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: slice) -> List[_T]: ...
|
||||
|
||||
def __getitem__(self, index: Union[int, slice]) -> Union[_T, List[_T]]:
|
||||
sess = self.session
|
||||
if sess is None:
|
||||
return self.attr._get_collection_history(
|
||||
attributes.instance_state(self.instance),
|
||||
PassiveFlag.PASSIVE_NO_INITIALIZE,
|
||||
).indexed(index)
|
||||
else:
|
||||
return self._generate(sess).__getitem__(index)
|
||||
|
||||
def count(self) -> int:
|
||||
sess = self.session
|
||||
if sess is None:
|
||||
return len(
|
||||
self.attr._get_collection_history(
|
||||
attributes.instance_state(self.instance),
|
||||
PassiveFlag.PASSIVE_NO_INITIALIZE,
|
||||
).added_items
|
||||
)
|
||||
else:
|
||||
return self._generate(sess).count()
|
||||
|
||||
def _generate(
|
||||
self,
|
||||
sess: Optional[Session] = None,
|
||||
) -> Query[_T]:
|
||||
# note we're returning an entirely new Query class instance
|
||||
# here without any assignment capabilities; the class of this
|
||||
# query is determined by the session.
|
||||
instance = self.instance
|
||||
if sess is None:
|
||||
sess = object_session(instance)
|
||||
if sess is None:
|
||||
raise orm_exc.DetachedInstanceError(
|
||||
"Parent instance %s is not bound to a Session, and no "
|
||||
"contextual session is established; lazy load operation "
|
||||
"of attribute '%s' cannot proceed"
|
||||
% (orm_util.instance_str(instance), self.attr.key)
|
||||
)
|
||||
|
||||
if self.query_class:
|
||||
query = self.query_class(self.attr.target_mapper, session=sess)
|
||||
else:
|
||||
query = sess.query(self.attr.target_mapper)
|
||||
|
||||
query._where_criteria = self._where_criteria
|
||||
query._from_obj = self._from_obj
|
||||
query._order_by_clauses = self._order_by_clauses
|
||||
|
||||
return query
|
||||
|
||||
def add_all(self, iterator: Iterable[_T]) -> None:
|
||||
"""Add an iterable of items to this :class:`_orm.AppenderQuery`.
|
||||
|
||||
The given items will be persisted to the database in terms of
|
||||
the parent instance's collection on the next flush.
|
||||
|
||||
This method is provided to assist in delivering forwards-compatibility
|
||||
with the :class:`_orm.WriteOnlyCollection` collection class.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
"""
|
||||
self._add_all_impl(iterator)
|
||||
|
||||
def add(self, item: _T) -> None:
|
||||
"""Add an item to this :class:`_orm.AppenderQuery`.
|
||||
|
||||
The given item will be persisted to the database in terms of
|
||||
the parent instance's collection on the next flush.
|
||||
|
||||
This method is provided to assist in delivering forwards-compatibility
|
||||
with the :class:`_orm.WriteOnlyCollection` collection class.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
"""
|
||||
self._add_all_impl([item])
|
||||
|
||||
def extend(self, iterator: Iterable[_T]) -> None:
|
||||
"""Add an iterable of items to this :class:`_orm.AppenderQuery`.
|
||||
|
||||
The given items will be persisted to the database in terms of
|
||||
the parent instance's collection on the next flush.
|
||||
|
||||
"""
|
||||
self._add_all_impl(iterator)
|
||||
|
||||
def append(self, item: _T) -> None:
|
||||
"""Append an item to this :class:`_orm.AppenderQuery`.
|
||||
|
||||
The given item will be persisted to the database in terms of
|
||||
the parent instance's collection on the next flush.
|
||||
|
||||
"""
|
||||
self._add_all_impl([item])
|
||||
|
||||
def remove(self, item: _T) -> None:
|
||||
"""Remove an item from this :class:`_orm.AppenderQuery`.
|
||||
|
||||
The given item will be removed from the parent instance's collection on
|
||||
the next flush.
|
||||
|
||||
"""
|
||||
self._remove_impl(item)
|
||||
|
||||
|
||||
class AppenderQuery(AppenderMixin[_T], Query[_T]): # type: ignore[misc]
|
||||
"""A dynamic query that supports basic collection storage operations.
|
||||
|
||||
Methods on :class:`.AppenderQuery` include all methods of
|
||||
:class:`_orm.Query`, plus additional methods used for collection
|
||||
persistence.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def mixin_user_query(cls: Any) -> type[AppenderMixin[Any]]:
|
||||
"""Return a new class with AppenderQuery functionality layered over."""
|
||||
name = "Appender" + cls.__name__
|
||||
return type(name, (AppenderMixin, cls), {"query_class": cls})
|
||||
378
venv/Lib/site-packages/sqlalchemy/orm/evaluator.py
Normal file
378
venv/Lib/site-packages/sqlalchemy/orm/evaluator.py
Normal file
@@ -0,0 +1,378 @@
|
||||
# orm/evaluator.py
|
||||
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
# mypy: ignore-errors
|
||||
|
||||
"""Evaluation functions used **INTERNALLY** by ORM DML use cases.
|
||||
|
||||
|
||||
This module is **private, for internal use by SQLAlchemy**.
|
||||
|
||||
.. versionchanged:: 2.0.4 renamed ``EvaluatorCompiler`` to
|
||||
``_EvaluatorCompiler``.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Type
|
||||
|
||||
from . import exc as orm_exc
|
||||
from .base import LoaderCallableStatus
|
||||
from .base import PassiveFlag
|
||||
from .. import exc
|
||||
from .. import inspect
|
||||
from ..sql import and_
|
||||
from ..sql import operators
|
||||
from ..sql.sqltypes import Concatenable
|
||||
from ..sql.sqltypes import Integer
|
||||
from ..sql.sqltypes import Numeric
|
||||
from ..util import warn_deprecated
|
||||
|
||||
|
||||
class UnevaluatableError(exc.InvalidRequestError):
|
||||
pass
|
||||
|
||||
|
||||
class _NoObject(operators.ColumnOperators):
|
||||
def operate(self, *arg, **kw):
|
||||
return None
|
||||
|
||||
def reverse_operate(self, *arg, **kw):
|
||||
return None
|
||||
|
||||
|
||||
class _ExpiredObject(operators.ColumnOperators):
|
||||
def operate(self, *arg, **kw):
|
||||
return self
|
||||
|
||||
def reverse_operate(self, *arg, **kw):
|
||||
return self
|
||||
|
||||
|
||||
_NO_OBJECT = _NoObject()
|
||||
_EXPIRED_OBJECT = _ExpiredObject()
|
||||
|
||||
|
||||
class _EvaluatorCompiler:
|
||||
def __init__(self, target_cls=None):
|
||||
self.target_cls = target_cls
|
||||
|
||||
def process(self, clause, *clauses):
|
||||
if clauses:
|
||||
clause = and_(clause, *clauses)
|
||||
|
||||
meth = getattr(self, f"visit_{clause.__visit_name__}", None)
|
||||
if not meth:
|
||||
raise UnevaluatableError(
|
||||
f"Cannot evaluate {type(clause).__name__}"
|
||||
)
|
||||
return meth(clause)
|
||||
|
||||
def visit_grouping(self, clause):
|
||||
return self.process(clause.element)
|
||||
|
||||
def visit_null(self, clause):
|
||||
return lambda obj: None
|
||||
|
||||
def visit_false(self, clause):
|
||||
return lambda obj: False
|
||||
|
||||
def visit_true(self, clause):
|
||||
return lambda obj: True
|
||||
|
||||
def visit_column(self, clause):
|
||||
try:
|
||||
parentmapper = clause._annotations["parentmapper"]
|
||||
except KeyError as ke:
|
||||
raise UnevaluatableError(
|
||||
f"Cannot evaluate column: {clause}"
|
||||
) from ke
|
||||
|
||||
if self.target_cls and not issubclass(
|
||||
self.target_cls, parentmapper.class_
|
||||
):
|
||||
raise UnevaluatableError(
|
||||
"Can't evaluate criteria against "
|
||||
f"alternate class {parentmapper.class_}"
|
||||
)
|
||||
|
||||
parentmapper._check_configure()
|
||||
|
||||
# we'd like to use "proxy_key" annotation to get the "key", however
|
||||
# in relationship primaryjoin cases proxy_key is sometimes deannotated
|
||||
# and sometimes apparently not present in the first place (?).
|
||||
# While I can stop it from being deannotated (though need to see if
|
||||
# this breaks other things), not sure right now about cases where it's
|
||||
# not there in the first place. can fix at some later point.
|
||||
# key = clause._annotations["proxy_key"]
|
||||
|
||||
# for now, use the old way
|
||||
try:
|
||||
key = parentmapper._columntoproperty[clause].key
|
||||
except orm_exc.UnmappedColumnError as err:
|
||||
raise UnevaluatableError(
|
||||
f"Cannot evaluate expression: {err}"
|
||||
) from err
|
||||
|
||||
# note this used to fall back to a simple `getattr(obj, key)` evaluator
|
||||
# if impl was None; as of #8656, we ensure mappers are configured
|
||||
# so that impl is available
|
||||
impl = parentmapper.class_manager[key].impl
|
||||
|
||||
def get_corresponding_attr(obj):
|
||||
if obj is None:
|
||||
return _NO_OBJECT
|
||||
state = inspect(obj)
|
||||
dict_ = state.dict
|
||||
|
||||
value = impl.get(
|
||||
state, dict_, passive=PassiveFlag.PASSIVE_NO_FETCH
|
||||
)
|
||||
if value is LoaderCallableStatus.PASSIVE_NO_RESULT:
|
||||
return _EXPIRED_OBJECT
|
||||
return value
|
||||
|
||||
return get_corresponding_attr
|
||||
|
||||
def visit_tuple(self, clause):
|
||||
return self.visit_clauselist(clause)
|
||||
|
||||
def visit_expression_clauselist(self, clause):
|
||||
return self.visit_clauselist(clause)
|
||||
|
||||
def visit_clauselist(self, clause):
|
||||
evaluators = [self.process(clause) for clause in clause.clauses]
|
||||
|
||||
dispatch = (
|
||||
f"visit_{clause.operator.__name__.rstrip('_')}_clauselist_op"
|
||||
)
|
||||
meth = getattr(self, dispatch, None)
|
||||
if meth:
|
||||
return meth(clause.operator, evaluators, clause)
|
||||
else:
|
||||
raise UnevaluatableError(
|
||||
f"Cannot evaluate clauselist with operator {clause.operator}"
|
||||
)
|
||||
|
||||
def visit_binary(self, clause):
|
||||
eval_left = self.process(clause.left)
|
||||
eval_right = self.process(clause.right)
|
||||
|
||||
dispatch = f"visit_{clause.operator.__name__.rstrip('_')}_binary_op"
|
||||
meth = getattr(self, dispatch, None)
|
||||
if meth:
|
||||
return meth(clause.operator, eval_left, eval_right, clause)
|
||||
else:
|
||||
raise UnevaluatableError(
|
||||
f"Cannot evaluate {type(clause).__name__} with "
|
||||
f"operator {clause.operator}"
|
||||
)
|
||||
|
||||
def visit_or_clauselist_op(self, operator, evaluators, clause):
|
||||
def evaluate(obj):
|
||||
has_null = False
|
||||
for sub_evaluate in evaluators:
|
||||
value = sub_evaluate(obj)
|
||||
if value is _EXPIRED_OBJECT:
|
||||
return _EXPIRED_OBJECT
|
||||
elif value:
|
||||
return True
|
||||
has_null = has_null or value is None
|
||||
if has_null:
|
||||
return None
|
||||
return False
|
||||
|
||||
return evaluate
|
||||
|
||||
def visit_and_clauselist_op(self, operator, evaluators, clause):
|
||||
def evaluate(obj):
|
||||
for sub_evaluate in evaluators:
|
||||
value = sub_evaluate(obj)
|
||||
if value is _EXPIRED_OBJECT:
|
||||
return _EXPIRED_OBJECT
|
||||
|
||||
if not value:
|
||||
if value is None or value is _NO_OBJECT:
|
||||
return None
|
||||
return False
|
||||
return True
|
||||
|
||||
return evaluate
|
||||
|
||||
def visit_comma_op_clauselist_op(self, operator, evaluators, clause):
|
||||
def evaluate(obj):
|
||||
values = []
|
||||
for sub_evaluate in evaluators:
|
||||
value = sub_evaluate(obj)
|
||||
if value is _EXPIRED_OBJECT:
|
||||
return _EXPIRED_OBJECT
|
||||
elif value is None or value is _NO_OBJECT:
|
||||
return None
|
||||
values.append(value)
|
||||
return tuple(values)
|
||||
|
||||
return evaluate
|
||||
|
||||
def visit_custom_op_binary_op(
|
||||
self, operator, eval_left, eval_right, clause
|
||||
):
|
||||
if operator.python_impl:
|
||||
return self._straight_evaluate(
|
||||
operator, eval_left, eval_right, clause
|
||||
)
|
||||
else:
|
||||
raise UnevaluatableError(
|
||||
f"Custom operator {operator.opstring!r} can't be evaluated "
|
||||
"in Python unless it specifies a callable using "
|
||||
"`.python_impl`."
|
||||
)
|
||||
|
||||
def visit_is_binary_op(self, operator, eval_left, eval_right, clause):
|
||||
def evaluate(obj):
|
||||
left_val = eval_left(obj)
|
||||
right_val = eval_right(obj)
|
||||
if left_val is _EXPIRED_OBJECT or right_val is _EXPIRED_OBJECT:
|
||||
return _EXPIRED_OBJECT
|
||||
return left_val == right_val
|
||||
|
||||
return evaluate
|
||||
|
||||
def visit_is_not_binary_op(self, operator, eval_left, eval_right, clause):
|
||||
def evaluate(obj):
|
||||
left_val = eval_left(obj)
|
||||
right_val = eval_right(obj)
|
||||
if left_val is _EXPIRED_OBJECT or right_val is _EXPIRED_OBJECT:
|
||||
return _EXPIRED_OBJECT
|
||||
return left_val != right_val
|
||||
|
||||
return evaluate
|
||||
|
||||
def _straight_evaluate(self, operator, eval_left, eval_right, clause):
|
||||
def evaluate(obj):
|
||||
left_val = eval_left(obj)
|
||||
right_val = eval_right(obj)
|
||||
if left_val is _EXPIRED_OBJECT or right_val is _EXPIRED_OBJECT:
|
||||
return _EXPIRED_OBJECT
|
||||
elif left_val is None or right_val is None:
|
||||
return None
|
||||
|
||||
return operator(eval_left(obj), eval_right(obj))
|
||||
|
||||
return evaluate
|
||||
|
||||
def _straight_evaluate_numeric_only(
|
||||
self, operator, eval_left, eval_right, clause
|
||||
):
|
||||
if clause.left.type._type_affinity not in (
|
||||
Numeric,
|
||||
Integer,
|
||||
) or clause.right.type._type_affinity not in (Numeric, Integer):
|
||||
raise UnevaluatableError(
|
||||
f'Cannot evaluate math operator "{operator.__name__}" for '
|
||||
f"datatypes {clause.left.type}, {clause.right.type}"
|
||||
)
|
||||
|
||||
return self._straight_evaluate(operator, eval_left, eval_right, clause)
|
||||
|
||||
visit_add_binary_op = _straight_evaluate_numeric_only
|
||||
visit_mul_binary_op = _straight_evaluate_numeric_only
|
||||
visit_sub_binary_op = _straight_evaluate_numeric_only
|
||||
visit_mod_binary_op = _straight_evaluate_numeric_only
|
||||
visit_truediv_binary_op = _straight_evaluate_numeric_only
|
||||
visit_lt_binary_op = _straight_evaluate
|
||||
visit_le_binary_op = _straight_evaluate
|
||||
visit_ne_binary_op = _straight_evaluate
|
||||
visit_gt_binary_op = _straight_evaluate
|
||||
visit_ge_binary_op = _straight_evaluate
|
||||
visit_eq_binary_op = _straight_evaluate
|
||||
|
||||
def visit_in_op_binary_op(self, operator, eval_left, eval_right, clause):
|
||||
return self._straight_evaluate(
|
||||
lambda a, b: a in b if a is not _NO_OBJECT else None,
|
||||
eval_left,
|
||||
eval_right,
|
||||
clause,
|
||||
)
|
||||
|
||||
def visit_not_in_op_binary_op(
|
||||
self, operator, eval_left, eval_right, clause
|
||||
):
|
||||
return self._straight_evaluate(
|
||||
lambda a, b: a not in b if a is not _NO_OBJECT else None,
|
||||
eval_left,
|
||||
eval_right,
|
||||
clause,
|
||||
)
|
||||
|
||||
def visit_concat_op_binary_op(
|
||||
self, operator, eval_left, eval_right, clause
|
||||
):
|
||||
|
||||
if not issubclass(
|
||||
clause.left.type._type_affinity, Concatenable
|
||||
) or not issubclass(clause.right.type._type_affinity, Concatenable):
|
||||
raise UnevaluatableError(
|
||||
f"Cannot evaluate concatenate operator "
|
||||
f'"{operator.__name__}" for '
|
||||
f"datatypes {clause.left.type}, {clause.right.type}"
|
||||
)
|
||||
|
||||
return self._straight_evaluate(
|
||||
lambda a, b: a + b, eval_left, eval_right, clause
|
||||
)
|
||||
|
||||
def visit_startswith_op_binary_op(
|
||||
self, operator, eval_left, eval_right, clause
|
||||
):
|
||||
return self._straight_evaluate(
|
||||
lambda a, b: a.startswith(b), eval_left, eval_right, clause
|
||||
)
|
||||
|
||||
def visit_endswith_op_binary_op(
|
||||
self, operator, eval_left, eval_right, clause
|
||||
):
|
||||
return self._straight_evaluate(
|
||||
lambda a, b: a.endswith(b), eval_left, eval_right, clause
|
||||
)
|
||||
|
||||
def visit_unary(self, clause):
|
||||
eval_inner = self.process(clause.element)
|
||||
if clause.operator is operators.inv:
|
||||
|
||||
def evaluate(obj):
|
||||
value = eval_inner(obj)
|
||||
if value is _EXPIRED_OBJECT:
|
||||
return _EXPIRED_OBJECT
|
||||
elif value is None:
|
||||
return None
|
||||
return not value
|
||||
|
||||
return evaluate
|
||||
raise UnevaluatableError(
|
||||
f"Cannot evaluate {type(clause).__name__} "
|
||||
f"with operator {clause.operator}"
|
||||
)
|
||||
|
||||
def visit_bindparam(self, clause):
|
||||
if clause.callable:
|
||||
val = clause.callable()
|
||||
else:
|
||||
val = clause.value
|
||||
return lambda obj: val
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Type[_EvaluatorCompiler]:
|
||||
if name == "EvaluatorCompiler":
|
||||
warn_deprecated(
|
||||
"Direct use of 'EvaluatorCompiler' is not supported, and this "
|
||||
"name will be removed in a future release. "
|
||||
"'_EvaluatorCompiler' is for internal use only",
|
||||
"2.0",
|
||||
)
|
||||
return _EvaluatorCompiler
|
||||
else:
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
Reference in New Issue
Block a user