lol
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -114,7 +114,7 @@ def mapped_column(
|
||||
primary_key: Optional[bool] = False,
|
||||
deferred: Union[_NoArg, bool] = _NoArg.NO_ARG,
|
||||
deferred_group: Optional[str] = None,
|
||||
deferred_raiseload: bool = False,
|
||||
deferred_raiseload: Optional[bool] = None,
|
||||
use_existing_column: bool = False,
|
||||
name: Optional[str] = None,
|
||||
type_: Optional[_TypeEngineArgument[Any]] = None,
|
||||
@@ -132,7 +132,7 @@ def mapped_column(
|
||||
quote: Optional[bool] = None,
|
||||
system: bool = False,
|
||||
comment: Optional[str] = None,
|
||||
sort_order: int = 0,
|
||||
sort_order: Union[_NoArg, int] = _NoArg.NO_ARG,
|
||||
**kw: Any,
|
||||
) -> MappedColumn[Any]:
|
||||
r"""declare a new ORM-mapped :class:`_schema.Column` construct
|
||||
|
||||
@@ -93,6 +93,7 @@ class _OrmKnownExecutionOptions(_CoreKnownExecutionOptions, total=False):
|
||||
dml_strategy: DMLStrategyArgument
|
||||
is_delete_using: bool
|
||||
is_update_from: bool
|
||||
render_nulls: bool
|
||||
|
||||
|
||||
OrmExecuteOptionsParameter = Union[
|
||||
@@ -119,7 +120,7 @@ class _LoaderCallable(Protocol):
|
||||
def is_orm_option(
|
||||
opt: ExecutableOption,
|
||||
) -> TypeGuard[ORMOption]:
|
||||
return not opt._is_core # type: ignore
|
||||
return not opt._is_core
|
||||
|
||||
|
||||
def is_user_defined_option(
|
||||
|
||||
@@ -120,6 +120,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_T_co = TypeVar("_T_co", bound=Any, covariant=True)
|
||||
|
||||
|
||||
_AllPendingType = Sequence[
|
||||
@@ -132,10 +133,10 @@ _UNKNOWN_ATTR_KEY = object()
|
||||
|
||||
@inspection._self_inspects
|
||||
class QueryableAttribute(
|
||||
_DeclarativeMapped[_T],
|
||||
SQLORMExpression[_T],
|
||||
_DeclarativeMapped[_T_co],
|
||||
SQLORMExpression[_T_co],
|
||||
interfaces.InspectionAttr,
|
||||
interfaces.PropComparator[_T],
|
||||
interfaces.PropComparator[_T_co],
|
||||
roles.JoinTargetRole,
|
||||
roles.OnClauseRole,
|
||||
sql_base.Immutable,
|
||||
@@ -178,13 +179,13 @@ class QueryableAttribute(
|
||||
|
||||
is_attribute = True
|
||||
|
||||
dispatch: dispatcher[QueryableAttribute[_T]]
|
||||
dispatch: dispatcher[QueryableAttribute[_T_co]]
|
||||
|
||||
class_: _ExternalEntityType[Any]
|
||||
key: str
|
||||
parententity: _InternalEntityType[Any]
|
||||
impl: AttributeImpl
|
||||
comparator: interfaces.PropComparator[_T]
|
||||
comparator: interfaces.PropComparator[_T_co]
|
||||
_of_type: Optional[_InternalEntityType[Any]]
|
||||
_extra_criteria: Tuple[ColumnElement[bool], ...]
|
||||
_doc: Optional[str]
|
||||
@@ -198,7 +199,7 @@ class QueryableAttribute(
|
||||
class_: _ExternalEntityType[_O],
|
||||
key: str,
|
||||
parententity: _InternalEntityType[_O],
|
||||
comparator: interfaces.PropComparator[_T],
|
||||
comparator: interfaces.PropComparator[_T_co],
|
||||
impl: Optional[AttributeImpl] = None,
|
||||
of_type: Optional[_InternalEntityType[Any]] = None,
|
||||
extra_criteria: Tuple[ColumnElement[bool], ...] = (),
|
||||
@@ -314,7 +315,7 @@ class QueryableAttribute(
|
||||
|
||||
"""
|
||||
|
||||
expression: ColumnElement[_T]
|
||||
expression: ColumnElement[_T_co]
|
||||
"""The SQL expression object represented by this
|
||||
:class:`.QueryableAttribute`.
|
||||
|
||||
@@ -334,7 +335,7 @@ class QueryableAttribute(
|
||||
entity_namespace = self._entity_namespace
|
||||
assert isinstance(entity_namespace, HasCacheKey)
|
||||
|
||||
if self.key is _UNKNOWN_ATTR_KEY: # type: ignore[comparison-overlap]
|
||||
if self.key is _UNKNOWN_ATTR_KEY:
|
||||
annotations = {"entity_namespace": entity_namespace}
|
||||
else:
|
||||
annotations = {
|
||||
@@ -376,7 +377,7 @@ class QueryableAttribute(
|
||||
def _annotations(self) -> _AnnotationDict:
|
||||
return self.__clause_element__()._annotations
|
||||
|
||||
def __clause_element__(self) -> ColumnElement[_T]:
|
||||
def __clause_element__(self) -> ColumnElement[_T_co]:
|
||||
return self.expression
|
||||
|
||||
@property
|
||||
@@ -443,18 +444,18 @@ class QueryableAttribute(
|
||||
extra_criteria=self._extra_criteria,
|
||||
)
|
||||
|
||||
def label(self, name: Optional[str]) -> Label[_T]:
|
||||
def label(self, name: Optional[str]) -> Label[_T_co]:
|
||||
return self.__clause_element__().label(name)
|
||||
|
||||
def operate(
|
||||
self, op: OperatorType, *other: Any, **kwargs: Any
|
||||
) -> ColumnElement[Any]:
|
||||
return op(self.comparator, *other, **kwargs) # type: ignore[return-value,no-any-return] # noqa: E501
|
||||
return op(self.comparator, *other, **kwargs) # type: ignore[no-any-return] # noqa: E501
|
||||
|
||||
def reverse_operate(
|
||||
self, op: OperatorType, other: Any, **kwargs: Any
|
||||
) -> ColumnElement[Any]:
|
||||
return op(other, self.comparator, **kwargs) # type: ignore[return-value,no-any-return] # noqa: E501
|
||||
return op(other, self.comparator, **kwargs) # type: ignore[no-any-return] # noqa: E501
|
||||
|
||||
def hasparent(
|
||||
self, state: InstanceState[Any], optimistic: bool = False
|
||||
@@ -520,16 +521,16 @@ class InstrumentedAttribute(QueryableAttribute[_T]):
|
||||
# InstrumentedAttribute, while still keeping classlevel
|
||||
# __doc__ correct
|
||||
|
||||
@util.rw_hybridproperty # type: ignore
|
||||
def __doc__(self) -> Optional[str]: # type: ignore
|
||||
@util.rw_hybridproperty
|
||||
def __doc__(self) -> Optional[str]:
|
||||
return self._doc
|
||||
|
||||
@__doc__.setter # type: ignore
|
||||
def __doc__(self, value: Optional[str]) -> None: # type: ignore
|
||||
def __doc__(self, value: Optional[str]) -> None:
|
||||
self._doc = value
|
||||
|
||||
@__doc__.classlevel # type: ignore
|
||||
def __doc__(cls) -> Optional[str]: # type: ignore
|
||||
def __doc__(cls) -> Optional[str]:
|
||||
return super().__doc__
|
||||
|
||||
def __set__(self, instance: object, value: Any) -> None:
|
||||
@@ -790,7 +791,7 @@ class AttributeEventToken:
|
||||
|
||||
__slots__ = "impl", "op", "parent_token"
|
||||
|
||||
def __init__(self, attribute_impl, op):
|
||||
def __init__(self, attribute_impl: AttributeImpl, op: util.symbol):
|
||||
self.impl = attribute_impl
|
||||
self.op = op
|
||||
self.parent_token = self.impl.parent_token
|
||||
@@ -833,7 +834,7 @@ class AttributeImpl:
|
||||
self,
|
||||
class_: _ExternalEntityType[_O],
|
||||
key: str,
|
||||
callable_: _LoaderCallable,
|
||||
callable_: Optional[_LoaderCallable],
|
||||
dispatch: _Dispatch[QueryableAttribute[Any]],
|
||||
trackparent: bool = False,
|
||||
compare_function: Optional[Callable[..., bool]] = None,
|
||||
@@ -1940,7 +1941,7 @@ class CollectionAttributeImpl(HasCollectionAdapter, AttributeImpl):
|
||||
and "None"
|
||||
or iterable.__class__.__name__
|
||||
)
|
||||
wanted = self._duck_typed_as.__name__ # type: ignore
|
||||
wanted = self._duck_typed_as.__name__
|
||||
raise TypeError(
|
||||
"Incompatible collection type: %s is not %s-like"
|
||||
% (given, wanted)
|
||||
@@ -2617,7 +2618,7 @@ def register_attribute_impl(
|
||||
# TODO: this appears to be the WriteOnlyAttributeImpl /
|
||||
# DynamicAttributeImpl constructor which is hardcoded
|
||||
impl = cast("Type[WriteOnlyAttributeImpl]", impl_class)(
|
||||
class_, key, typecallable, dispatch, **kw
|
||||
class_, key, dispatch, **kw
|
||||
)
|
||||
elif uselist:
|
||||
impl = CollectionAttributeImpl(
|
||||
|
||||
@@ -56,6 +56,7 @@ if typing.TYPE_CHECKING:
|
||||
from ..sql.operators import OperatorType
|
||||
|
||||
_T = TypeVar("_T", bound=Any)
|
||||
_T_co = TypeVar("_T_co", bound=Any, covariant=True)
|
||||
|
||||
_O = TypeVar("_O", bound=object)
|
||||
|
||||
@@ -678,12 +679,12 @@ class InspectionAttrInfo(InspectionAttr):
|
||||
return {}
|
||||
|
||||
|
||||
class SQLORMOperations(SQLCoreOperations[_T], TypingOnly):
|
||||
class SQLORMOperations(SQLCoreOperations[_T_co], TypingOnly):
|
||||
__slots__ = ()
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
|
||||
def of_type(self, class_: _EntityType[Any]) -> PropComparator[_T]:
|
||||
def of_type(self, class_: _EntityType[Any]) -> PropComparator[_T_co]:
|
||||
...
|
||||
|
||||
def and_(
|
||||
@@ -706,7 +707,7 @@ class SQLORMOperations(SQLCoreOperations[_T], TypingOnly):
|
||||
...
|
||||
|
||||
|
||||
class ORMDescriptor(Generic[_T], TypingOnly):
|
||||
class ORMDescriptor(Generic[_T_co], TypingOnly):
|
||||
"""Represent any Python descriptor that provides a SQL expression
|
||||
construct at the class level."""
|
||||
|
||||
@@ -717,26 +718,26 @@ class ORMDescriptor(Generic[_T], TypingOnly):
|
||||
@overload
|
||||
def __get__(
|
||||
self, instance: Any, owner: Literal[None]
|
||||
) -> ORMDescriptor[_T]:
|
||||
) -> ORMDescriptor[_T_co]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __get__(
|
||||
self, instance: Literal[None], owner: Any
|
||||
) -> SQLCoreOperations[_T]:
|
||||
) -> SQLCoreOperations[_T_co]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __get__(self, instance: object, owner: Any) -> _T:
|
||||
def __get__(self, instance: object, owner: Any) -> _T_co:
|
||||
...
|
||||
|
||||
def __get__(
|
||||
self, instance: object, owner: Any
|
||||
) -> Union[ORMDescriptor[_T], SQLCoreOperations[_T], _T]:
|
||||
) -> Union[ORMDescriptor[_T_co], SQLCoreOperations[_T_co], _T_co]:
|
||||
...
|
||||
|
||||
|
||||
class _MappedAnnotationBase(Generic[_T], TypingOnly):
|
||||
class _MappedAnnotationBase(Generic[_T_co], TypingOnly):
|
||||
"""common class for Mapped and similar ORM container classes.
|
||||
|
||||
these are classes that can appear on the left side of an ORM declarative
|
||||
@@ -749,7 +750,7 @@ class _MappedAnnotationBase(Generic[_T], TypingOnly):
|
||||
|
||||
|
||||
class SQLORMExpression(
|
||||
SQLORMOperations[_T], SQLColumnExpression[_T], TypingOnly
|
||||
SQLORMOperations[_T_co], SQLColumnExpression[_T_co], TypingOnly
|
||||
):
|
||||
"""A type that may be used to indicate any ORM-level attribute or
|
||||
object that acts in place of one, in the context of SQL expression
|
||||
@@ -771,9 +772,9 @@ class SQLORMExpression(
|
||||
|
||||
|
||||
class Mapped(
|
||||
SQLORMExpression[_T],
|
||||
ORMDescriptor[_T],
|
||||
_MappedAnnotationBase[_T],
|
||||
SQLORMExpression[_T_co],
|
||||
ORMDescriptor[_T_co],
|
||||
_MappedAnnotationBase[_T_co],
|
||||
roles.DDLConstraintColumnRole,
|
||||
):
|
||||
"""Represent an ORM mapped attribute on a mapped class.
|
||||
@@ -819,24 +820,24 @@ class Mapped(
|
||||
@overload
|
||||
def __get__(
|
||||
self, instance: None, owner: Any
|
||||
) -> InstrumentedAttribute[_T]:
|
||||
) -> InstrumentedAttribute[_T_co]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __get__(self, instance: object, owner: Any) -> _T:
|
||||
def __get__(self, instance: object, owner: Any) -> _T_co:
|
||||
...
|
||||
|
||||
def __get__(
|
||||
self, instance: Optional[object], owner: Any
|
||||
) -> Union[InstrumentedAttribute[_T], _T]:
|
||||
) -> Union[InstrumentedAttribute[_T_co], _T_co]:
|
||||
...
|
||||
|
||||
@classmethod
|
||||
def _empty_constructor(cls, arg1: Any) -> Mapped[_T]:
|
||||
def _empty_constructor(cls, arg1: Any) -> Mapped[_T_co]:
|
||||
...
|
||||
|
||||
def __set__(
|
||||
self, instance: Any, value: Union[SQLCoreOperations[_T], _T]
|
||||
self, instance: Any, value: Union[SQLCoreOperations[_T_co], _T_co]
|
||||
) -> None:
|
||||
...
|
||||
|
||||
@@ -844,7 +845,7 @@ class Mapped(
|
||||
...
|
||||
|
||||
|
||||
class _MappedAttribute(Generic[_T], TypingOnly):
|
||||
class _MappedAttribute(Generic[_T_co], TypingOnly):
|
||||
"""Mixin for attributes which should be replaced by mapper-assigned
|
||||
attributes.
|
||||
|
||||
@@ -853,7 +854,7 @@ class _MappedAttribute(Generic[_T], TypingOnly):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class _DeclarativeMapped(Mapped[_T], _MappedAttribute[_T]):
|
||||
class _DeclarativeMapped(Mapped[_T_co], _MappedAttribute[_T_co]):
|
||||
"""Mixin for :class:`.MapperProperty` subclasses that allows them to
|
||||
be compatible with ORM-annotated declarative mappings.
|
||||
|
||||
@@ -878,7 +879,7 @@ class _DeclarativeMapped(Mapped[_T], _MappedAttribute[_T]):
|
||||
return NotImplemented
|
||||
|
||||
|
||||
class DynamicMapped(_MappedAnnotationBase[_T]):
|
||||
class DynamicMapped(_MappedAnnotationBase[_T_co]):
|
||||
"""Represent the ORM mapped attribute type for a "dynamic" relationship.
|
||||
|
||||
The :class:`_orm.DynamicMapped` type annotation may be used in an
|
||||
@@ -918,23 +919,27 @@ class DynamicMapped(_MappedAnnotationBase[_T]):
|
||||
@overload
|
||||
def __get__(
|
||||
self, instance: None, owner: Any
|
||||
) -> InstrumentedAttribute[_T]:
|
||||
) -> InstrumentedAttribute[_T_co]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __get__(self, instance: object, owner: Any) -> AppenderQuery[_T]:
|
||||
def __get__(
|
||||
self, instance: object, owner: Any
|
||||
) -> AppenderQuery[_T_co]:
|
||||
...
|
||||
|
||||
def __get__(
|
||||
self, instance: Optional[object], owner: Any
|
||||
) -> Union[InstrumentedAttribute[_T], AppenderQuery[_T]]:
|
||||
) -> Union[InstrumentedAttribute[_T_co], AppenderQuery[_T_co]]:
|
||||
...
|
||||
|
||||
def __set__(self, instance: Any, value: typing.Collection[_T]) -> None:
|
||||
def __set__(
|
||||
self, instance: Any, value: typing.Collection[_T_co]
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
class WriteOnlyMapped(_MappedAnnotationBase[_T]):
|
||||
class WriteOnlyMapped(_MappedAnnotationBase[_T_co]):
|
||||
"""Represent the ORM mapped attribute type for a "write only" relationship.
|
||||
|
||||
The :class:`_orm.WriteOnlyMapped` type annotation may be used in an
|
||||
@@ -970,19 +975,21 @@ class WriteOnlyMapped(_MappedAnnotationBase[_T]):
|
||||
@overload
|
||||
def __get__(
|
||||
self, instance: None, owner: Any
|
||||
) -> InstrumentedAttribute[_T]:
|
||||
) -> InstrumentedAttribute[_T_co]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __get__(
|
||||
self, instance: object, owner: Any
|
||||
) -> WriteOnlyCollection[_T]:
|
||||
) -> WriteOnlyCollection[_T_co]:
|
||||
...
|
||||
|
||||
def __get__(
|
||||
self, instance: Optional[object], owner: Any
|
||||
) -> Union[InstrumentedAttribute[_T], WriteOnlyCollection[_T]]:
|
||||
) -> Union[InstrumentedAttribute[_T_co], WriteOnlyCollection[_T_co]]:
|
||||
...
|
||||
|
||||
def __set__(self, instance: Any, value: typing.Collection[_T]) -> None:
|
||||
def __set__(
|
||||
self, instance: Any, value: typing.Collection[_T_co]
|
||||
) -> None:
|
||||
...
|
||||
|
||||
@@ -1158,7 +1158,7 @@ class BulkORMInsert(ORMDMLState, InsertDMLState):
|
||||
execution_options,
|
||||
) = BulkORMInsert.default_insert_options.from_execution_options(
|
||||
"_sa_orm_insert_options",
|
||||
{"dml_strategy", "autoflush", "populate_existing"},
|
||||
{"dml_strategy", "autoflush", "populate_existing", "render_nulls"},
|
||||
execution_options,
|
||||
statement._execution_options,
|
||||
)
|
||||
@@ -1438,7 +1438,6 @@ class BulkORMUpdate(BulkUDCompileState, UpdateDMLState):
|
||||
self._resolved_values = dict(self._resolved_values)
|
||||
|
||||
new_stmt = statement._clone()
|
||||
new_stmt.table = mapper.local_table
|
||||
|
||||
# note if the statement has _multi_values, these
|
||||
# are passed through to the new statement, which will then raise
|
||||
@@ -1499,9 +1498,7 @@ class BulkORMUpdate(BulkUDCompileState, UpdateDMLState):
|
||||
# over and over again. so perhaps if it could be RETURNING just
|
||||
# the elements that were based on a SQL expression and not
|
||||
# a constant. For now it doesn't quite seem worth it
|
||||
new_stmt = new_stmt.return_defaults(
|
||||
*(list(mapper.local_table.primary_key))
|
||||
)
|
||||
new_stmt = new_stmt.return_defaults(*new_stmt.table.primary_key)
|
||||
|
||||
if toplevel:
|
||||
new_stmt = self._setup_orm_returning(
|
||||
@@ -1860,7 +1857,6 @@ class BulkORMDelete(BulkUDCompileState, DeleteDMLState):
|
||||
)
|
||||
|
||||
new_stmt = statement._clone()
|
||||
new_stmt.table = mapper.local_table
|
||||
|
||||
new_crit = cls._adjust_for_extra_criteria(
|
||||
self.global_attributes, mapper
|
||||
|
||||
@@ -253,7 +253,7 @@ class _declared_attr_common:
|
||||
# which seems to help typing tools interpret the fn as a classmethod
|
||||
# for situations where needed
|
||||
if isinstance(fn, classmethod):
|
||||
fn = fn.__func__ # type: ignore
|
||||
fn = fn.__func__
|
||||
|
||||
self.fget = fn
|
||||
self._cascading = cascading
|
||||
@@ -281,11 +281,11 @@ class _declared_attr_common:
|
||||
"Unmanaged access of declarative attribute %s from "
|
||||
"non-mapped class %s" % (self.fget.__name__, cls.__name__)
|
||||
)
|
||||
return self.fget(cls) # type: ignore
|
||||
return self.fget(cls)
|
||||
elif manager.is_mapped:
|
||||
# the class is mapped, which means we're outside of the declarative
|
||||
# scan setup, just run the function.
|
||||
return self.fget(cls) # type: ignore
|
||||
return self.fget(cls)
|
||||
|
||||
# here, we are inside of the declarative scan. use the registry
|
||||
# that is tracking the values of these attributes.
|
||||
@@ -297,10 +297,10 @@ class _declared_attr_common:
|
||||
reg = declarative_scan.declared_attr_reg
|
||||
|
||||
if self in reg:
|
||||
return reg[self] # type: ignore
|
||||
return reg[self]
|
||||
else:
|
||||
reg[self] = obj = self.fget(cls)
|
||||
return obj # type: ignore
|
||||
return obj
|
||||
|
||||
|
||||
class _declared_directive(_declared_attr_common, Generic[_T]):
|
||||
@@ -558,12 +558,12 @@ def _setup_declarative_base(cls: Type[Any]) -> None:
|
||||
reg = registry(
|
||||
metadata=metadata, type_annotation_map=type_annotation_map
|
||||
)
|
||||
cls.registry = reg # type: ignore
|
||||
cls.registry = reg
|
||||
|
||||
cls._sa_registry = reg # type: ignore
|
||||
cls._sa_registry = reg
|
||||
|
||||
if "metadata" not in cls.__dict__:
|
||||
cls.metadata = cls.registry.metadata # type: ignore
|
||||
cls.metadata = cls.registry.metadata
|
||||
|
||||
if getattr(cls, "__init__", object.__init__) is object.__init__:
|
||||
cls.__init__ = cls.registry.constructor
|
||||
@@ -609,7 +609,7 @@ class MappedAsDataclass(metaclass=DCTransformDeclarative):
|
||||
current_transforms: _DataclassArguments
|
||||
|
||||
if hasattr(cls, "_sa_apply_dc_transforms"):
|
||||
current = cls._sa_apply_dc_transforms # type: ignore[attr-defined]
|
||||
current = cls._sa_apply_dc_transforms
|
||||
|
||||
_ClassScanMapperConfig._assert_dc_arguments(current)
|
||||
|
||||
@@ -1274,7 +1274,7 @@ class registry:
|
||||
sql_type = sqltypes._type_map_get(pt) # type: ignore # noqa: E501
|
||||
|
||||
if sql_type is not None:
|
||||
sql_type_inst = sqltypes.to_instance(sql_type) # type: ignore
|
||||
sql_type_inst = sqltypes.to_instance(sql_type)
|
||||
|
||||
# ... this additional step will reject most
|
||||
# type -> supertype matches, such as if we had
|
||||
@@ -1556,7 +1556,7 @@ class registry:
|
||||
|
||||
if hasattr(cls, "__class_getitem__"):
|
||||
|
||||
def __class_getitem__(cls: Type[_T], key: str) -> Type[_T]:
|
||||
def __class_getitem__(cls: Type[_T], key: Any) -> Type[_T]:
|
||||
# allow generic classes in py3.9+
|
||||
return cls
|
||||
|
||||
|
||||
@@ -1434,9 +1434,9 @@ class _ClassScanMapperConfig(_MapperConfig):
|
||||
cls, "_sa_decl_prepare_nocascade", strict=True
|
||||
)
|
||||
|
||||
allow_unmapped_annotations = self.allow_unmapped_annotations
|
||||
expect_annotations_wo_mapped = (
|
||||
self.allow_unmapped_annotations
|
||||
or self.is_dataclass_prior_to_mapping
|
||||
allow_unmapped_annotations or self.is_dataclass_prior_to_mapping
|
||||
)
|
||||
|
||||
look_for_dataclass_things = bool(self.dataclass_setup_arguments)
|
||||
@@ -1531,7 +1531,15 @@ class _ClassScanMapperConfig(_MapperConfig):
|
||||
# Mapped[] etc. were not used. If annotation is None,
|
||||
# do declarative_scan so that the property can raise
|
||||
# for required
|
||||
if mapped_container is not None or annotation is None:
|
||||
if (
|
||||
mapped_container is not None
|
||||
or annotation is None
|
||||
# issue #10516: need to do declarative_scan even with
|
||||
# a non-Mapped annotation if we are doing
|
||||
# __allow_unmapped__, for things like col.name
|
||||
# assignment
|
||||
or allow_unmapped_annotations
|
||||
):
|
||||
try:
|
||||
value.declarative_scan(
|
||||
self,
|
||||
@@ -1609,7 +1617,7 @@ class _ClassScanMapperConfig(_MapperConfig):
|
||||
setattr(cls, k, value)
|
||||
continue
|
||||
|
||||
our_stuff[k] = value # type: ignore
|
||||
our_stuff[k] = value
|
||||
|
||||
def _extract_declared_columns(self) -> None:
|
||||
our_stuff = self.properties
|
||||
@@ -1979,7 +1987,7 @@ class _DeferredMapperConfig(_ClassScanMapperConfig):
|
||||
|
||||
# mypy disallows plain property override of variable
|
||||
@property # type: ignore
|
||||
def cls(self) -> Type[Any]: # type: ignore
|
||||
def cls(self) -> Type[Any]:
|
||||
return self._cls() # type: ignore
|
||||
|
||||
@cls.setter
|
||||
@@ -1999,7 +2007,7 @@ class _DeferredMapperConfig(_ClassScanMapperConfig):
|
||||
@classmethod
|
||||
def raise_unmapped_for_cls(cls, class_: Type[Any]) -> NoReturn:
|
||||
if hasattr(class_, "_sa_raise_deferred_config"):
|
||||
class_._sa_raise_deferred_config() # type: ignore
|
||||
class_._sa_raise_deferred_config()
|
||||
|
||||
raise orm_exc.UnmappedClassError(
|
||||
class_,
|
||||
|
||||
@@ -425,7 +425,7 @@ class CompositeProperty(
|
||||
elif hasattr(self.composite_class, "__composite_values__"):
|
||||
_composite_getters[
|
||||
self.composite_class
|
||||
] = lambda obj: obj.__composite_values__() # type: ignore
|
||||
] = lambda obj: obj.__composite_values__()
|
||||
|
||||
@util.preload_module("sqlalchemy.orm.properties")
|
||||
@util.preload_module("sqlalchemy.orm.decl_base")
|
||||
@@ -628,7 +628,7 @@ class CompositeProperty(
|
||||
|
||||
proxy_attr = self.parent.class_manager[self.key]
|
||||
proxy_attr.impl.dispatch = proxy_attr.dispatch # type: ignore
|
||||
proxy_attr.impl.dispatch._active_history = self.active_history # type: ignore # noqa: E501
|
||||
proxy_attr.impl.dispatch._active_history = self.active_history
|
||||
|
||||
# TODO: need a deserialize hook here
|
||||
|
||||
@@ -806,16 +806,16 @@ class CompositeProperty(
|
||||
def __ne__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
|
||||
return self._compare(operators.ne, other)
|
||||
|
||||
def __lt__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
|
||||
def __lt__(self, other: Any) -> ColumnElement[bool]:
|
||||
return self._compare(operators.lt, other)
|
||||
|
||||
def __gt__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
|
||||
def __gt__(self, other: Any) -> ColumnElement[bool]:
|
||||
return self._compare(operators.gt, other)
|
||||
|
||||
def __le__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
|
||||
def __le__(self, other: Any) -> ColumnElement[bool]:
|
||||
return self._compare(operators.le, other)
|
||||
|
||||
def __ge__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
|
||||
def __ge__(self, other: Any) -> ColumnElement[bool]:
|
||||
return self._compare(operators.ge, other)
|
||||
|
||||
# what might be interesting would be if we create
|
||||
@@ -839,8 +839,8 @@ class CompositeProperty(
|
||||
]
|
||||
if self._adapt_to_entity:
|
||||
assert self.adapter is not None
|
||||
comparisons = [self.adapter(x) for x in comparisons] # type: ignore # noqa: E501
|
||||
return sql.and_(*comparisons) # type: ignore
|
||||
comparisons = [self.adapter(x) for x in comparisons]
|
||||
return sql.and_(*comparisons)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.parent.class_.__name__) + "." + self.key
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
# mypy: ignore-errors
|
||||
|
||||
|
||||
"""Dynamic collection API.
|
||||
@@ -23,13 +22,19 @@ 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 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
|
||||
@@ -39,15 +44,28 @@ from .writeonly import WriteOnlyLoader
|
||||
from .. import util
|
||||
from ..engine import result
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .session import Session
|
||||
|
||||
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):
|
||||
def __init__(self, attr, state, passive, apply_to=None):
|
||||
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)
|
||||
@@ -63,21 +81,21 @@ class DynamicCollectionHistory(WriteOnlyHistory):
|
||||
|
||||
class DynamicAttributeImpl(WriteOnlyAttributeImpl):
|
||||
_supports_dynamic_iteration = True
|
||||
collection_history_cls = DynamicCollectionHistory
|
||||
collection_history_cls = DynamicCollectionHistory[Any]
|
||||
query_class: Type[AppenderMixin[Any]] # type: ignore[assignment]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
class_,
|
||||
key,
|
||||
typecallable,
|
||||
dispatch,
|
||||
target_mapper,
|
||||
order_by,
|
||||
query_class=None,
|
||||
**kw,
|
||||
):
|
||||
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, typecallable, dispatch, **kw
|
||||
self, class_, key, None, dispatch, **kw
|
||||
)
|
||||
self.target_mapper = target_mapper
|
||||
if order_by:
|
||||
@@ -102,21 +120,23 @@ class AppenderMixin(AbstractCollectionWriter[_T]):
|
||||
|
||||
"""
|
||||
|
||||
query_class = None
|
||||
query_class: Optional[Type[Query[_T]]] = None
|
||||
_order_by_clauses: Tuple[ColumnElement[Any], ...]
|
||||
|
||||
def __init__(self, attr, state):
|
||||
Query.__init__(self, attr.target_mapper, None)
|
||||
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) -> Session:
|
||||
def session(self) -> Optional[Session]:
|
||||
sess = object_session(self.instance)
|
||||
if (
|
||||
sess is not None
|
||||
and self.autoflush
|
||||
and sess.autoflush
|
||||
and self.instance in sess
|
||||
):
|
||||
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
|
||||
@@ -127,7 +147,7 @@ class AppenderMixin(AbstractCollectionWriter[_T]):
|
||||
def session(self, session: Session) -> None:
|
||||
self.sess = session
|
||||
|
||||
def _iter(self):
|
||||
def _iter(self) -> Union[result.ScalarResult[_T], result.Result[_T]]:
|
||||
sess = self.session
|
||||
if sess is None:
|
||||
state = attributes.instance_state(self.instance)
|
||||
@@ -141,9 +161,9 @@ class AppenderMixin(AbstractCollectionWriter[_T]):
|
||||
|
||||
return result.IteratorResult(
|
||||
result.SimpleResultMetaData([self.attr.class_.__name__]),
|
||||
self.attr._get_collection_history(
|
||||
self.attr._get_collection_history( # type: ignore[arg-type]
|
||||
attributes.instance_state(self.instance),
|
||||
attributes.PASSIVE_NO_INITIALIZE,
|
||||
PassiveFlag.PASSIVE_NO_INITIALIZE,
|
||||
).added_items,
|
||||
_source_supports_scalars=True,
|
||||
).scalars()
|
||||
@@ -155,15 +175,15 @@ class AppenderMixin(AbstractCollectionWriter[_T]):
|
||||
def __iter__(self) -> Iterator[_T]:
|
||||
...
|
||||
|
||||
def __getitem__(self, index: Any) -> _T:
|
||||
def __getitem__(self, index: Any) -> Union[_T, List[_T]]:
|
||||
sess = self.session
|
||||
if sess is None:
|
||||
return self.attr._get_collection_history(
|
||||
attributes.instance_state(self.instance),
|
||||
attributes.PASSIVE_NO_INITIALIZE,
|
||||
PassiveFlag.PASSIVE_NO_INITIALIZE,
|
||||
).indexed(index)
|
||||
else:
|
||||
return self._generate(sess).__getitem__(index)
|
||||
return self._generate(sess).__getitem__(index) # type: ignore[no-any-return] # noqa: E501
|
||||
|
||||
def count(self) -> int:
|
||||
sess = self.session
|
||||
@@ -171,13 +191,16 @@ class AppenderMixin(AbstractCollectionWriter[_T]):
|
||||
return len(
|
||||
self.attr._get_collection_history(
|
||||
attributes.instance_state(self.instance),
|
||||
attributes.PASSIVE_NO_INITIALIZE,
|
||||
PassiveFlag.PASSIVE_NO_INITIALIZE,
|
||||
).added_items
|
||||
)
|
||||
else:
|
||||
return self._generate(sess).count()
|
||||
|
||||
def _generate(self, sess=None):
|
||||
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.
|
||||
@@ -259,7 +282,7 @@ class AppenderMixin(AbstractCollectionWriter[_T]):
|
||||
self._remove_impl(item)
|
||||
|
||||
|
||||
class AppenderQuery(AppenderMixin[_T], Query[_T]):
|
||||
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
|
||||
@@ -270,7 +293,7 @@ class AppenderQuery(AppenderMixin[_T], Query[_T]):
|
||||
"""
|
||||
|
||||
|
||||
def mixin_user_query(cls):
|
||||
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})
|
||||
|
||||
@@ -138,7 +138,7 @@ class ClassManager(
|
||||
def deferred_scalar_loader(self):
|
||||
return self.expired_attribute_loader
|
||||
|
||||
@deferred_scalar_loader.setter # type: ignore[no-redef]
|
||||
@deferred_scalar_loader.setter
|
||||
@util.deprecated(
|
||||
"1.4",
|
||||
message="The ClassManager.deferred_scalar_loader attribute is now "
|
||||
@@ -204,7 +204,7 @@ class ClassManager(
|
||||
init_method: Optional[Callable[..., None]] = None,
|
||||
) -> None:
|
||||
if mapper:
|
||||
self.mapper = mapper # type: ignore[assignment]
|
||||
self.mapper = mapper #
|
||||
if registry:
|
||||
registry._add_manager(self)
|
||||
if declarative_scan:
|
||||
@@ -428,7 +428,7 @@ class ClassManager(
|
||||
for key in list(self.originals):
|
||||
self.uninstall_member(key)
|
||||
|
||||
self.mapper = None # type: ignore
|
||||
self.mapper = None
|
||||
self.dispatch = None # type: ignore
|
||||
self.new_init = None
|
||||
self.info.clear()
|
||||
@@ -506,11 +506,11 @@ class ClassManager(
|
||||
# so that mypy sees that __new__ is present. currently
|
||||
# it's bound to Any as there were other problems not having
|
||||
# it that way but these can be revisited
|
||||
instance = self.class_.__new__(self.class_) # type: ignore
|
||||
instance = self.class_.__new__(self.class_)
|
||||
if state is None:
|
||||
state = self._state_constructor(instance, self)
|
||||
self._state_setter(instance, state)
|
||||
return instance # type: ignore[no-any-return]
|
||||
return instance
|
||||
|
||||
def setup_instance(
|
||||
self, instance: _O, state: Optional[InstanceState[_O]] = None
|
||||
|
||||
@@ -107,6 +107,7 @@ if typing.TYPE_CHECKING:
|
||||
_StrategyKey = Tuple[Any, ...]
|
||||
|
||||
_T = TypeVar("_T", bound=Any)
|
||||
_T_co = TypeVar("_T_co", bound=Any, covariant=True)
|
||||
|
||||
_TLS = TypeVar("_TLS", bound="Type[LoaderStrategy]")
|
||||
|
||||
@@ -653,7 +654,7 @@ class MapperProperty(
|
||||
|
||||
|
||||
@inspection._self_inspects
|
||||
class PropComparator(SQLORMOperations[_T], Generic[_T], ColumnOperators):
|
||||
class PropComparator(SQLORMOperations[_T_co], Generic[_T_co], ColumnOperators):
|
||||
r"""Defines SQL operations for ORM mapped attributes.
|
||||
|
||||
SQLAlchemy allows for operators to
|
||||
@@ -740,7 +741,7 @@ class PropComparator(SQLORMOperations[_T], Generic[_T], ColumnOperators):
|
||||
|
||||
_parententity: _InternalEntityType[Any]
|
||||
_adapt_to_entity: Optional[AliasedInsp[Any]]
|
||||
prop: RODescriptorReference[MapperProperty[_T]]
|
||||
prop: RODescriptorReference[MapperProperty[_T_co]]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -922,9 +923,7 @@ class PropComparator(SQLORMOperations[_T], Generic[_T], ColumnOperators):
|
||||
|
||||
"""
|
||||
|
||||
return self.operate( # type: ignore
|
||||
PropComparator.any_op, criterion, **kwargs
|
||||
)
|
||||
return self.operate(PropComparator.any_op, criterion, **kwargs)
|
||||
|
||||
def has(
|
||||
self,
|
||||
@@ -946,9 +945,7 @@ class PropComparator(SQLORMOperations[_T], Generic[_T], ColumnOperators):
|
||||
|
||||
"""
|
||||
|
||||
return self.operate( # type: ignore
|
||||
PropComparator.has_op, criterion, **kwargs
|
||||
)
|
||||
return self.operate(PropComparator.has_op, criterion, **kwargs)
|
||||
|
||||
|
||||
class StrategizedProperty(MapperProperty[_T]):
|
||||
|
||||
@@ -137,40 +137,64 @@ def instances(cursor: CursorResult[Any], context: QueryContext) -> Result[Any]:
|
||||
"Can't use the ORM yield_per feature in conjunction with unique()"
|
||||
)
|
||||
|
||||
def _not_hashable(datatype):
|
||||
def go(obj):
|
||||
raise sa_exc.InvalidRequestError(
|
||||
"Can't apply uniqueness to row tuple containing value of "
|
||||
"type %r; this datatype produces non-hashable values"
|
||||
% datatype
|
||||
)
|
||||
def _not_hashable(datatype, *, legacy=False, uncertain=False):
|
||||
if not legacy:
|
||||
|
||||
return go
|
||||
def go(obj):
|
||||
if uncertain:
|
||||
try:
|
||||
return hash(obj)
|
||||
except:
|
||||
pass
|
||||
|
||||
if context.load_options._legacy_uniquing:
|
||||
unique_filters = [
|
||||
_no_unique
|
||||
if context.yield_per
|
||||
else id
|
||||
if (
|
||||
ent.use_id_for_hash
|
||||
or ent._non_hashable_value
|
||||
or ent._null_column_type
|
||||
)
|
||||
else None
|
||||
for ent in context.compile_state._entities
|
||||
]
|
||||
else:
|
||||
unique_filters = [
|
||||
_no_unique
|
||||
if context.yield_per
|
||||
else _not_hashable(ent.column.type) # type: ignore
|
||||
if (not ent.use_id_for_hash and ent._non_hashable_value)
|
||||
else id
|
||||
if ent.use_id_for_hash
|
||||
else None
|
||||
for ent in context.compile_state._entities
|
||||
]
|
||||
raise sa_exc.InvalidRequestError(
|
||||
"Can't apply uniqueness to row tuple containing value of "
|
||||
f"""type {datatype!r}; {'the values returned appear to be'
|
||||
if uncertain else 'this datatype produces'} """
|
||||
"non-hashable values"
|
||||
)
|
||||
|
||||
return go
|
||||
elif not uncertain:
|
||||
return id
|
||||
else:
|
||||
_use_id = False
|
||||
|
||||
def go(obj):
|
||||
nonlocal _use_id
|
||||
|
||||
if not _use_id:
|
||||
try:
|
||||
return hash(obj)
|
||||
except:
|
||||
pass
|
||||
|
||||
# in #10459, we considered using a warning here, however
|
||||
# as legacy query uses result.unique() in all cases, this
|
||||
# would lead to too many warning cases.
|
||||
_use_id = True
|
||||
|
||||
return id(obj)
|
||||
|
||||
return go
|
||||
|
||||
unique_filters = [
|
||||
_no_unique
|
||||
if context.yield_per
|
||||
else _not_hashable(
|
||||
ent.column.type, # type: ignore
|
||||
legacy=context.load_options._legacy_uniquing,
|
||||
uncertain=ent._null_column_type,
|
||||
)
|
||||
if (
|
||||
not ent.use_id_for_hash
|
||||
and (ent._non_hashable_value or ent._null_column_type)
|
||||
)
|
||||
else id
|
||||
if ent.use_id_for_hash
|
||||
else None
|
||||
for ent in context.compile_state._entities
|
||||
]
|
||||
|
||||
row_metadata = SimpleResultMetaData(
|
||||
labels, extra, _unique_filters=unique_filters
|
||||
@@ -1246,7 +1270,16 @@ def _load_subclass_via_in(
|
||||
|
||||
orig_query = context.query
|
||||
|
||||
options = (enable_opt,) + orig_query._with_options + (disable_opt,)
|
||||
if path.parent:
|
||||
enable_opt_lcl = enable_opt._prepend_path(path)
|
||||
disable_opt_lcl = disable_opt._prepend_path(path)
|
||||
else:
|
||||
enable_opt_lcl = enable_opt
|
||||
disable_opt_lcl = disable_opt
|
||||
options = (
|
||||
(enable_opt_lcl,) + orig_query._with_options + (disable_opt_lcl,)
|
||||
)
|
||||
|
||||
q2 = q.options(*options)
|
||||
|
||||
q2._compile_options = context.compile_state.default_compile_options
|
||||
|
||||
@@ -786,7 +786,7 @@ class Mapper(
|
||||
|
||||
# interim - polymorphic_on is further refined in
|
||||
# _configure_polymorphic_setter
|
||||
self.polymorphic_on = ( # type: ignore
|
||||
self.polymorphic_on = (
|
||||
coercions.expect( # type: ignore
|
||||
roles.ColumnArgumentOrKeyRole,
|
||||
polymorphic_on,
|
||||
@@ -964,8 +964,8 @@ class Mapper(
|
||||
version_id_generator: Optional[Union[Literal[False], Callable[[Any], Any]]]
|
||||
|
||||
local_table: FromClause
|
||||
"""The immediate :class:`_expression.FromClause` which this
|
||||
:class:`_orm.Mapper` refers towards.
|
||||
"""The immediate :class:`_expression.FromClause` to which this
|
||||
:class:`_orm.Mapper` refers.
|
||||
|
||||
Typically is an instance of :class:`_schema.Table`, may be any
|
||||
:class:`.FromClause`.
|
||||
@@ -1400,7 +1400,7 @@ class Mapper(
|
||||
self.with_polymorphic = None
|
||||
|
||||
if self.with_polymorphic and self.with_polymorphic[1] is not None:
|
||||
self.with_polymorphic = ( # type: ignore
|
||||
self.with_polymorphic = (
|
||||
self.with_polymorphic[0],
|
||||
coercions.expect(
|
||||
roles.StrictFromClauseRole,
|
||||
@@ -1504,7 +1504,7 @@ class Mapper(
|
||||
manager = instrumentation.register_class(
|
||||
self.class_,
|
||||
mapper=self,
|
||||
expired_attribute_loader=util.partial( # type: ignore
|
||||
expired_attribute_loader=util.partial(
|
||||
loading.load_scalar_attributes, self
|
||||
),
|
||||
# finalize flag means instrument the __init__ method
|
||||
@@ -1610,7 +1610,7 @@ class Mapper(
|
||||
if isinstance(c, str)
|
||||
else c
|
||||
for c in (
|
||||
coercions.expect( # type: ignore
|
||||
coercions.expect(
|
||||
roles.DDLConstraintColumnRole,
|
||||
coerce_pk,
|
||||
argname="primary_key",
|
||||
@@ -4097,7 +4097,7 @@ class _OptGetColumnsNotAvailable(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def configure_mappers():
|
||||
def configure_mappers() -> None:
|
||||
"""Initialize the inter-mapper relationships of all mappers that
|
||||
have been constructed thus far across all :class:`_orm.registry`
|
||||
collections.
|
||||
|
||||
@@ -57,9 +57,9 @@ else:
|
||||
|
||||
|
||||
_SerializedPath = List[Any]
|
||||
|
||||
_StrPathToken = str
|
||||
_PathElementType = Union[
|
||||
str, "_InternalEntityType[Any]", "MapperProperty[Any]"
|
||||
_StrPathToken, "_InternalEntityType[Any]", "MapperProperty[Any]"
|
||||
]
|
||||
|
||||
# the representation is in fact
|
||||
@@ -70,6 +70,8 @@ _PathElementType = Union[
|
||||
# chopped at odd intervals as well so this is less flexible
|
||||
_PathRepresentation = Tuple[_PathElementType, ...]
|
||||
|
||||
# NOTE: these names are weird since the array is 0-indexed,
|
||||
# the "_Odd" entries are at 0, 2, 4, etc
|
||||
_OddPathRepresentation = Sequence["_InternalEntityType[Any]"]
|
||||
_EvenPathRepresentation = Sequence[Union["MapperProperty[Any]", str]]
|
||||
|
||||
@@ -154,6 +156,9 @@ class PathRegistry(HasCacheKey):
|
||||
def _path_for_compare(self) -> Optional[_PathRepresentation]:
|
||||
return self.path
|
||||
|
||||
def odd_element(self, index: int) -> _InternalEntityType[Any]:
|
||||
return self.path[index] # type: ignore
|
||||
|
||||
def set(self, attributes: Dict[Any, Any], key: Any, value: Any) -> None:
|
||||
log.debug("set '%s' on path '%s' to '%s'", key, self, value)
|
||||
attributes[(key, self.natural_path)] = value
|
||||
@@ -180,7 +185,7 @@ class PathRegistry(HasCacheKey):
|
||||
return id(self)
|
||||
|
||||
@overload
|
||||
def __getitem__(self, entity: str) -> TokenRegistry:
|
||||
def __getitem__(self, entity: _StrPathToken) -> TokenRegistry:
|
||||
...
|
||||
|
||||
@overload
|
||||
@@ -204,7 +209,11 @@ class PathRegistry(HasCacheKey):
|
||||
def __getitem__(
|
||||
self,
|
||||
entity: Union[
|
||||
str, int, slice, _InternalEntityType[Any], MapperProperty[Any]
|
||||
_StrPathToken,
|
||||
int,
|
||||
slice,
|
||||
_InternalEntityType[Any],
|
||||
MapperProperty[Any],
|
||||
],
|
||||
) -> Union[
|
||||
TokenRegistry,
|
||||
@@ -355,7 +364,7 @@ class CreatesToken(PathRegistry):
|
||||
is_aliased_class: bool
|
||||
is_root: bool
|
||||
|
||||
def token(self, token: str) -> TokenRegistry:
|
||||
def token(self, token: _StrPathToken) -> TokenRegistry:
|
||||
if token.endswith(f":{_WILDCARD_TOKEN}"):
|
||||
return TokenRegistry(self, token)
|
||||
elif token.endswith(f":{_DEFAULT_TOKEN}"):
|
||||
@@ -385,7 +394,7 @@ class RootRegistry(CreatesToken):
|
||||
) -> Union[TokenRegistry, AbstractEntityRegistry]:
|
||||
if entity in PathToken._intern:
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(entity, str)
|
||||
assert isinstance(entity, _StrPathToken)
|
||||
return TokenRegistry(self, PathToken._intern[entity])
|
||||
else:
|
||||
try:
|
||||
@@ -433,10 +442,10 @@ class TokenRegistry(PathRegistry):
|
||||
|
||||
inherit_cache = True
|
||||
|
||||
token: str
|
||||
token: _StrPathToken
|
||||
parent: CreatesToken
|
||||
|
||||
def __init__(self, parent: CreatesToken, token: str):
|
||||
def __init__(self, parent: CreatesToken, token: _StrPathToken):
|
||||
token = PathToken.intern(token)
|
||||
|
||||
self.token = token
|
||||
@@ -619,7 +628,7 @@ class PropRegistry(PathRegistry):
|
||||
|
||||
self._wildcard_path_loader_key = (
|
||||
"loader",
|
||||
parent.natural_path + self.prop._wildcard_token, # type: ignore
|
||||
parent.natural_path + self.prop._wildcard_token,
|
||||
)
|
||||
self._default_path_loader_key = self.prop._default_path_loader_key
|
||||
self._loader_key = ("loader", self.natural_path)
|
||||
@@ -721,7 +730,7 @@ class AbstractEntityRegistry(CreatesToken):
|
||||
|
||||
@property
|
||||
def root_entity(self) -> _InternalEntityType[Any]:
|
||||
return cast("_InternalEntityType[Any]", self.path[0])
|
||||
return self.odd_element(0)
|
||||
|
||||
@property
|
||||
def entity_path(self) -> PathRegistry:
|
||||
|
||||
@@ -1082,7 +1082,7 @@ def _emit_insert_statements(
|
||||
records = list(records)
|
||||
|
||||
if returning_is_required_anyway or (
|
||||
not hasvalue and len(records) > 1
|
||||
table.implicit_returning and not hasvalue and len(records) > 1
|
||||
):
|
||||
if (
|
||||
deterministic_results_reqd
|
||||
|
||||
@@ -25,6 +25,7 @@ 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 strategy_options
|
||||
@@ -446,7 +447,7 @@ class ColumnProperty(
|
||||
try:
|
||||
return ce.info # type: ignore
|
||||
except AttributeError:
|
||||
return self.prop.info # type: ignore
|
||||
return self.prop.info
|
||||
|
||||
def _memoized_attr_expressions(self) -> Sequence[NamedColumn[Any]]:
|
||||
"""The full sequence of columns referenced by this
|
||||
@@ -475,13 +476,13 @@ class ColumnProperty(
|
||||
def operate(
|
||||
self, op: OperatorType, *other: Any, **kwargs: Any
|
||||
) -> ColumnElement[Any]:
|
||||
return op(self.__clause_element__(), *other, **kwargs) # type: ignore[return-value,no-any-return] # noqa: E501
|
||||
return op(self.__clause_element__(), *other, **kwargs) # type: ignore[no-any-return] # noqa: E501
|
||||
|
||||
def reverse_operate(
|
||||
self, op: OperatorType, other: Any, **kwargs: Any
|
||||
) -> ColumnElement[Any]:
|
||||
col = self.__clause_element__()
|
||||
return op(col._bind_param(op, other), col, **kwargs) # type: ignore[return-value,no-any-return] # noqa: E501
|
||||
return op(col._bind_param(op, other), col, **kwargs) # type: ignore[no-any-return] # noqa: E501
|
||||
|
||||
def __str__(self) -> str:
|
||||
if not self.parent or not self.key:
|
||||
@@ -542,7 +543,7 @@ class MappedColumn(
|
||||
"_use_existing_column",
|
||||
)
|
||||
|
||||
deferred: bool
|
||||
deferred: Union[_NoArg, bool]
|
||||
deferred_raiseload: bool
|
||||
deferred_group: Optional[str]
|
||||
|
||||
@@ -557,17 +558,15 @@ class MappedColumn(
|
||||
|
||||
self._use_existing_column = kw.pop("use_existing_column", False)
|
||||
|
||||
self._has_dataclass_arguments = False
|
||||
|
||||
if attr_opts is not None and attr_opts != _DEFAULT_ATTRIBUTE_OPTIONS:
|
||||
if attr_opts.dataclasses_default_factory is not _NoArg.NO_ARG:
|
||||
self._has_dataclass_arguments = True
|
||||
|
||||
elif (
|
||||
attr_opts.dataclasses_init is not _NoArg.NO_ARG
|
||||
or attr_opts.dataclasses_repr is not _NoArg.NO_ARG
|
||||
):
|
||||
self._has_dataclass_arguments = True
|
||||
self._has_dataclass_arguments = (
|
||||
attr_opts is not None
|
||||
and attr_opts != _DEFAULT_ATTRIBUTE_OPTIONS
|
||||
and any(
|
||||
attr_opts[i] is not _NoArg.NO_ARG
|
||||
for i, attr in enumerate(attr_opts._fields)
|
||||
if attr != "dataclasses_default"
|
||||
)
|
||||
)
|
||||
|
||||
insert_default = kw.pop("insert_default", _NoArg.NO_ARG)
|
||||
self._has_insert_default = insert_default is not _NoArg.NO_ARG
|
||||
@@ -580,12 +579,9 @@ class MappedColumn(
|
||||
self.deferred_group = kw.pop("deferred_group", None)
|
||||
self.deferred_raiseload = kw.pop("deferred_raiseload", None)
|
||||
self.deferred = kw.pop("deferred", _NoArg.NO_ARG)
|
||||
if self.deferred is _NoArg.NO_ARG:
|
||||
self.deferred = bool(
|
||||
self.deferred_group or self.deferred_raiseload
|
||||
)
|
||||
self.active_history = kw.pop("active_history", False)
|
||||
self._sort_order = kw.pop("sort_order", 0)
|
||||
|
||||
self._sort_order = kw.pop("sort_order", _NoArg.NO_ARG)
|
||||
self.column = cast("Column[_T]", Column(*arg, **kw))
|
||||
self.foreign_keys = self.column.foreign_keys
|
||||
self._has_nullable = "nullable" in kw and kw.get("nullable") not in (
|
||||
@@ -617,10 +613,16 @@ class MappedColumn(
|
||||
|
||||
@property
|
||||
def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]:
|
||||
if self.deferred or self.active_history:
|
||||
effective_deferred = self.deferred
|
||||
if effective_deferred is _NoArg.NO_ARG:
|
||||
effective_deferred = bool(
|
||||
self.deferred_group or self.deferred_raiseload
|
||||
)
|
||||
|
||||
if effective_deferred or self.active_history:
|
||||
return ColumnProperty(
|
||||
self.column,
|
||||
deferred=self.deferred,
|
||||
deferred=effective_deferred,
|
||||
group=self.deferred_group,
|
||||
raiseload=self.deferred_raiseload,
|
||||
attribute_options=self._attribute_options,
|
||||
@@ -631,7 +633,14 @@ class MappedColumn(
|
||||
|
||||
@property
|
||||
def columns_to_assign(self) -> List[Tuple[Column[Any], int]]:
|
||||
return [(self.column, self._sort_order)]
|
||||
return [
|
||||
(
|
||||
self.column,
|
||||
self._sort_order
|
||||
if self._sort_order is not _NoArg.NO_ARG
|
||||
else 0,
|
||||
)
|
||||
]
|
||||
|
||||
def __clause_element__(self) -> Column[_T]:
|
||||
return self.column
|
||||
@@ -639,13 +648,13 @@ class MappedColumn(
|
||||
def operate(
|
||||
self, op: OperatorType, *other: Any, **kwargs: Any
|
||||
) -> ColumnElement[Any]:
|
||||
return op(self.__clause_element__(), *other, **kwargs) # type: ignore[return-value,no-any-return] # noqa: E501
|
||||
return op(self.__clause_element__(), *other, **kwargs) # type: ignore[no-any-return] # noqa: E501
|
||||
|
||||
def reverse_operate(
|
||||
self, op: OperatorType, other: Any, **kwargs: Any
|
||||
) -> ColumnElement[Any]:
|
||||
col = self.__clause_element__()
|
||||
return op(col._bind_param(op, other), col, **kwargs) # type: ignore[return-value,no-any-return] # noqa: E501
|
||||
return op(col._bind_param(op, other), col, **kwargs) # type: ignore[no-any-return] # noqa: E501
|
||||
|
||||
def found_in_pep593_annotated(self) -> Any:
|
||||
# return a blank mapped_column(). This mapped_column()'s
|
||||
@@ -779,6 +788,65 @@ class MappedColumn(
|
||||
use_args_from.column._merge(self.column)
|
||||
sqltype = self.column.type
|
||||
|
||||
if (
|
||||
use_args_from.deferred is not _NoArg.NO_ARG
|
||||
and self.deferred is _NoArg.NO_ARG
|
||||
):
|
||||
self.deferred = use_args_from.deferred
|
||||
|
||||
if (
|
||||
use_args_from.deferred_group is not None
|
||||
and self.deferred_group is None
|
||||
):
|
||||
self.deferred_group = use_args_from.deferred_group
|
||||
|
||||
if (
|
||||
use_args_from.deferred_raiseload is not None
|
||||
and self.deferred_raiseload is None
|
||||
):
|
||||
self.deferred_raiseload = use_args_from.deferred_raiseload
|
||||
|
||||
if (
|
||||
use_args_from._use_existing_column
|
||||
and not self._use_existing_column
|
||||
):
|
||||
self._use_existing_column = True
|
||||
|
||||
if use_args_from.active_history:
|
||||
self.active_history = use_args_from.active_history
|
||||
|
||||
if (
|
||||
use_args_from._sort_order is not None
|
||||
and self._sort_order is _NoArg.NO_ARG
|
||||
):
|
||||
self._sort_order = use_args_from._sort_order
|
||||
|
||||
if (
|
||||
use_args_from.column.key is not None
|
||||
or use_args_from.column.name is not None
|
||||
):
|
||||
util.warn_deprecated(
|
||||
"Can't use the 'key' or 'name' arguments in "
|
||||
"Annotated with mapped_column(); this will be ignored",
|
||||
"2.0.22",
|
||||
)
|
||||
|
||||
if use_args_from._has_dataclass_arguments:
|
||||
for idx, arg in enumerate(
|
||||
use_args_from._attribute_options._fields
|
||||
):
|
||||
if (
|
||||
use_args_from._attribute_options[idx]
|
||||
is not _NoArg.NO_ARG
|
||||
):
|
||||
arg = arg.replace("dataclasses_", "")
|
||||
util.warn_deprecated(
|
||||
f"Argument '{arg}' is a dataclass argument and "
|
||||
"cannot be specified within a mapped_column() "
|
||||
"bundled inside of an Annotated object",
|
||||
"2.0.22",
|
||||
)
|
||||
|
||||
if sqltype._isnull and not self.column.foreign_keys:
|
||||
new_sqltype = None
|
||||
|
||||
|
||||
@@ -235,7 +235,9 @@ class Query(
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
entities: Sequence[_ColumnsClauseArgument[Any]],
|
||||
entities: Union[
|
||||
_ColumnsClauseArgument[Any], Sequence[_ColumnsClauseArgument[Any]]
|
||||
],
|
||||
session: Optional[Session] = None,
|
||||
):
|
||||
"""Construct a :class:`_query.Query` directly.
|
||||
@@ -274,11 +276,14 @@ class Query(
|
||||
self._set_entities(entities)
|
||||
|
||||
def _set_propagate_attrs(self, values: Mapping[str, Any]) -> Self:
|
||||
self._propagate_attrs = util.immutabledict(values) # type: ignore
|
||||
self._propagate_attrs = util.immutabledict(values)
|
||||
return self
|
||||
|
||||
def _set_entities(
|
||||
self, entities: Iterable[_ColumnsClauseArgument[Any]]
|
||||
self,
|
||||
entities: Union[
|
||||
_ColumnsClauseArgument[Any], Iterable[_ColumnsClauseArgument[Any]]
|
||||
],
|
||||
) -> None:
|
||||
self._raw_columns = [
|
||||
coercions.expect(
|
||||
@@ -478,7 +483,7 @@ class Query(
|
||||
return self
|
||||
|
||||
def _clone(self, **kw: Any) -> Self:
|
||||
return self._generate() # type: ignore
|
||||
return self._generate()
|
||||
|
||||
def _get_select_statement_only(self) -> Select[_T]:
|
||||
if self._statement is not None:
|
||||
@@ -1450,7 +1455,7 @@ class Query(
|
||||
q._set_entities(columns)
|
||||
if not q.load_options._yield_per:
|
||||
q.load_options += {"_yield_per": 10}
|
||||
return iter(q) # type: ignore
|
||||
return iter(q)
|
||||
|
||||
@util.deprecated(
|
||||
"1.4",
|
||||
|
||||
@@ -271,6 +271,9 @@ class _RelationshipArg(Generic[_T1, _T2]):
|
||||
self.resolved = attr_value
|
||||
|
||||
|
||||
_RelationshipOrderByArg = Union[Literal[False], Tuple[ColumnElement[Any], ...]]
|
||||
|
||||
|
||||
class _RelationshipArgs(NamedTuple):
|
||||
"""stores user-passed parameters that are resolved at mapper configuration
|
||||
time.
|
||||
@@ -289,10 +292,7 @@ class _RelationshipArgs(NamedTuple):
|
||||
Optional[_RelationshipJoinConditionArgument],
|
||||
Optional[ColumnElement[Any]],
|
||||
]
|
||||
order_by: _RelationshipArg[
|
||||
_ORMOrderByArgument,
|
||||
Union[Literal[None, False], Tuple[ColumnElement[Any], ...]],
|
||||
]
|
||||
order_by: _RelationshipArg[_ORMOrderByArgument, _RelationshipOrderByArg]
|
||||
foreign_keys: _RelationshipArg[
|
||||
Optional[_ORMColCollectionArgument], Set[ColumnElement[Any]]
|
||||
]
|
||||
@@ -341,7 +341,7 @@ class RelationshipProperty(
|
||||
secondaryjoin: Optional[ColumnElement[bool]]
|
||||
secondary: Optional[FromClause]
|
||||
_join_condition: JoinCondition
|
||||
order_by: Union[Literal[False], Tuple[ColumnElement[Any], ...]]
|
||||
order_by: _RelationshipOrderByArg
|
||||
|
||||
_user_defined_foreign_keys: Set[ColumnElement[Any]]
|
||||
_calculated_foreign_keys: Set[ColumnElement[Any]]
|
||||
@@ -1614,7 +1614,8 @@ class RelationshipProperty(
|
||||
@util.memoized_property
|
||||
def entity(self) -> _InternalEntityType[_T]:
|
||||
"""Return the target mapped entity, which is an inspect() of the
|
||||
class or aliased class that is referred towards.
|
||||
class or aliased class that is referenced by this
|
||||
:class:`.RelationshipProperty`.
|
||||
|
||||
"""
|
||||
self.parent._check_configure()
|
||||
@@ -1764,7 +1765,7 @@ class RelationshipProperty(
|
||||
argument = de_optionalize_union_types(argument)
|
||||
|
||||
if hasattr(argument, "__origin__"):
|
||||
arg_origin = argument.__origin__ # type: ignore
|
||||
arg_origin = argument.__origin__
|
||||
if isinstance(arg_origin, type) and issubclass(
|
||||
arg_origin, abc.Collection
|
||||
):
|
||||
@@ -1786,7 +1787,7 @@ class RelationshipProperty(
|
||||
|
||||
if argument.__args__: # type: ignore
|
||||
if isinstance(arg_origin, type) and issubclass(
|
||||
arg_origin, typing.Mapping # type: ignore
|
||||
arg_origin, typing.Mapping
|
||||
):
|
||||
type_arg = argument.__args__[-1] # type: ignore
|
||||
else:
|
||||
@@ -1804,7 +1805,7 @@ class RelationshipProperty(
|
||||
f"Generic alias {argument} requires an argument"
|
||||
)
|
||||
elif hasattr(argument, "__forward_arg__"):
|
||||
argument = argument.__forward_arg__ # type: ignore
|
||||
argument = argument.__forward_arg__
|
||||
|
||||
argument = resolve_name_to_real_class_name(
|
||||
argument, originating_module
|
||||
@@ -1874,7 +1875,7 @@ class RelationshipProperty(
|
||||
% (self.key, type(resolved_argument))
|
||||
)
|
||||
|
||||
self.entity = entity # type: ignore
|
||||
self.entity = entity
|
||||
self.target = self.entity.persist_selectable
|
||||
|
||||
def _setup_join_conditions(self) -> None:
|
||||
@@ -1990,7 +1991,7 @@ class RelationshipProperty(
|
||||
'and not on the "many" side of a many-to-one or many-to-many '
|
||||
"relationship. "
|
||||
"To force this relationship to allow a particular "
|
||||
'"%(relatedcls)s" object to be referred towards by only '
|
||||
'"%(relatedcls)s" object to be referenced by only '
|
||||
'a single "%(clsname)s" object at a time via the '
|
||||
"%(rel)s relationship, which "
|
||||
"would allow "
|
||||
|
||||
@@ -108,6 +108,7 @@ __all__ = ["scoped_session"]
|
||||
"begin",
|
||||
"begin_nested",
|
||||
"close",
|
||||
"reset",
|
||||
"commit",
|
||||
"connection",
|
||||
"delete",
|
||||
@@ -118,6 +119,7 @@ __all__ = ["scoped_session"]
|
||||
"expunge_all",
|
||||
"flush",
|
||||
"get",
|
||||
"get_one",
|
||||
"get_bind",
|
||||
"is_modified",
|
||||
"bulk_save_objects",
|
||||
@@ -466,7 +468,8 @@ class scoped_session(Generic[_S]):
|
||||
|
||||
:ref:`pysqlite_serializable` - special workarounds required
|
||||
with the SQLite driver in order for SAVEPOINT to work
|
||||
correctly.
|
||||
correctly. For asyncio use cases, see the section
|
||||
:ref:`aiosqlite_serializable`.
|
||||
|
||||
|
||||
""" # noqa: E501
|
||||
@@ -491,12 +494,17 @@ class scoped_session(Generic[_S]):
|
||||
|
||||
.. tip::
|
||||
|
||||
The :meth:`_orm.Session.close` method **does not prevent the
|
||||
Session from being used again**. The :class:`_orm.Session` itself
|
||||
does not actually have a distinct "closed" state; it merely means
|
||||
In the default running mode the :meth:`_orm.Session.close`
|
||||
method **does not prevent the Session from being used again**.
|
||||
The :class:`_orm.Session` itself does not actually have a
|
||||
distinct "closed" state; it merely means
|
||||
the :class:`_orm.Session` will release all database connections
|
||||
and ORM objects.
|
||||
|
||||
Setting the parameter :paramref:`_orm.Session.close_resets_only`
|
||||
to ``False`` will instead make the ``close`` final, meaning that
|
||||
any further action on the session will be forbidden.
|
||||
|
||||
.. versionchanged:: 1.4 The :meth:`.Session.close` method does not
|
||||
immediately create a new :class:`.SessionTransaction` object;
|
||||
instead, the new :class:`.SessionTransaction` is created only if
|
||||
@@ -505,13 +513,49 @@ class scoped_session(Generic[_S]):
|
||||
.. seealso::
|
||||
|
||||
:ref:`session_closing` - detail on the semantics of
|
||||
:meth:`_orm.Session.close`
|
||||
:meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
|
||||
|
||||
:meth:`_orm.Session.reset` - a similar method that behaves like
|
||||
``close()`` with the parameter
|
||||
:paramref:`_orm.Session.close_resets_only` set to ``True``.
|
||||
|
||||
|
||||
""" # noqa: E501
|
||||
|
||||
return self._proxied.close()
|
||||
|
||||
def reset(self) -> None:
|
||||
r"""Close out the transactional resources and ORM objects used by this
|
||||
:class:`_orm.Session`, resetting the session to its initial state.
|
||||
|
||||
.. container:: class_bases
|
||||
|
||||
Proxied for the :class:`_orm.Session` class on
|
||||
behalf of the :class:`_orm.scoping.scoped_session` class.
|
||||
|
||||
This method provides for same "reset-only" behavior that the
|
||||
:meth:_orm.Session.close method has provided historically, where the
|
||||
state of the :class:`_orm.Session` is reset as though the object were
|
||||
brand new, and ready to be used again.
|
||||
The method may then be useful for :class:`_orm.Session` objects
|
||||
which set :paramref:`_orm.Session.close_resets_only` to ``False``,
|
||||
so that "reset only" behavior is still available from this method.
|
||||
|
||||
.. versionadded:: 2.0.22
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`session_closing` - detail on the semantics of
|
||||
:meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
|
||||
|
||||
:meth:`_orm.Session.close` - a similar method will additionally
|
||||
prevent re-use of the Session when the parameter
|
||||
:paramref:`_orm.Session.close_resets_only` is set to ``False``.
|
||||
|
||||
""" # noqa: E501
|
||||
|
||||
return self._proxied.reset()
|
||||
|
||||
def commit(self) -> None:
|
||||
r"""Flush pending changes and commit the current transaction.
|
||||
|
||||
@@ -1028,6 +1072,56 @@ class scoped_session(Generic[_S]):
|
||||
bind_arguments=bind_arguments,
|
||||
)
|
||||
|
||||
def get_one(
|
||||
self,
|
||||
entity: _EntityBindKey[_O],
|
||||
ident: _PKIdentityArgument,
|
||||
*,
|
||||
options: Optional[Sequence[ORMOption]] = None,
|
||||
populate_existing: bool = False,
|
||||
with_for_update: ForUpdateParameter = None,
|
||||
identity_token: Optional[Any] = None,
|
||||
execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
|
||||
bind_arguments: Optional[_BindArguments] = None,
|
||||
) -> _O:
|
||||
r"""Return exactly one instance based on the given primary key
|
||||
identifier, or raise an exception if not found.
|
||||
|
||||
.. container:: class_bases
|
||||
|
||||
Proxied for the :class:`_orm.Session` class on
|
||||
behalf of the :class:`_orm.scoping.scoped_session` class.
|
||||
|
||||
Raises ``sqlalchemy.orm.exc.NoResultFound`` if the query
|
||||
selects no rows.
|
||||
|
||||
For a detailed documentation of the arguments see the
|
||||
method :meth:`.Session.get`.
|
||||
|
||||
.. versionadded:: 2.0.22
|
||||
|
||||
:return: The object instance.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.Session.get` - equivalent method that instead
|
||||
returns ``None`` if no row was found with the provided primary
|
||||
key
|
||||
|
||||
|
||||
""" # noqa: E501
|
||||
|
||||
return self._proxied.get_one(
|
||||
entity,
|
||||
ident,
|
||||
options=options,
|
||||
populate_existing=populate_existing,
|
||||
with_for_update=with_for_update,
|
||||
identity_token=identity_token,
|
||||
execution_options=execution_options,
|
||||
bind_arguments=bind_arguments,
|
||||
)
|
||||
|
||||
def get_bind(
|
||||
self,
|
||||
mapper: Optional[_EntityBindKey[_O]] = None,
|
||||
|
||||
@@ -83,6 +83,7 @@ from ..sql import roles
|
||||
from ..sql import Select
|
||||
from ..sql import TableClause
|
||||
from ..sql import visitors
|
||||
from ..sql.base import _NoArg
|
||||
from ..sql.base import CompileState
|
||||
from ..sql.schema import Table
|
||||
from ..sql.selectable import ForUpdateArg
|
||||
@@ -510,7 +511,8 @@ class ORMExecuteState(util.MemoizedSlots):
|
||||
|
||||
|
||||
"""
|
||||
return self.bind_arguments.get("mapper", None)
|
||||
mp: Optional[Mapper[Any]] = self.bind_arguments.get("mapper", None)
|
||||
return mp
|
||||
|
||||
@property
|
||||
def all_mappers(self) -> Sequence[Mapper[Any]]:
|
||||
@@ -718,9 +720,14 @@ class ORMExecuteState(util.MemoizedSlots):
|
||||
"This ORM execution is not against a SELECT statement "
|
||||
"so there are no load options."
|
||||
)
|
||||
return self.execution_options.get(
|
||||
|
||||
lo: Union[
|
||||
context.QueryContext.default_load_options,
|
||||
Type[context.QueryContext.default_load_options],
|
||||
] = self.execution_options.get(
|
||||
"_sa_orm_load_options", context.QueryContext.default_load_options
|
||||
)
|
||||
return lo
|
||||
|
||||
@property
|
||||
def update_delete_options(
|
||||
@@ -737,10 +744,14 @@ class ORMExecuteState(util.MemoizedSlots):
|
||||
"This ORM execution is not against an UPDATE or DELETE "
|
||||
"statement so there are no update options."
|
||||
)
|
||||
return self.execution_options.get(
|
||||
uo: Union[
|
||||
bulk_persistence.BulkUDCompileState.default_update_options,
|
||||
Type[bulk_persistence.BulkUDCompileState.default_update_options],
|
||||
] = self.execution_options.get(
|
||||
"_sa_orm_update_options",
|
||||
bulk_persistence.BulkUDCompileState.default_update_options,
|
||||
)
|
||||
return uo
|
||||
|
||||
@property
|
||||
def _non_compile_orm_options(self) -> Sequence[ORMOption]:
|
||||
@@ -881,6 +892,12 @@ class SessionTransaction(_StateChange, TransactionalContext):
|
||||
self.nested = nested = origin is SessionTransactionOrigin.BEGIN_NESTED
|
||||
self.origin = origin
|
||||
|
||||
if session._close_state is _SessionCloseState.CLOSED:
|
||||
raise sa_exc.InvalidRequestError(
|
||||
"This Session has been permanently closed and is unable "
|
||||
"to handle any more transaction requests."
|
||||
)
|
||||
|
||||
if nested:
|
||||
if not parent:
|
||||
raise sa_exc.InvalidRequestError(
|
||||
@@ -1372,6 +1389,12 @@ class SessionTransaction(_StateChange, TransactionalContext):
|
||||
return self._state not in (COMMITTED, CLOSED)
|
||||
|
||||
|
||||
class _SessionCloseState(Enum):
|
||||
ACTIVE = 1
|
||||
CLOSED = 2
|
||||
CLOSE_IS_RESET = 3
|
||||
|
||||
|
||||
class Session(_SessionClassMethods, EventTarget):
|
||||
"""Manages persistence operations for ORM-mapped objects.
|
||||
|
||||
@@ -1416,6 +1439,7 @@ class Session(_SessionClassMethods, EventTarget):
|
||||
twophase: bool
|
||||
join_transaction_mode: JoinTransactionMode
|
||||
_query_cls: Type[Query[Any]]
|
||||
_close_state: _SessionCloseState
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -1432,6 +1456,7 @@ class Session(_SessionClassMethods, EventTarget):
|
||||
query_cls: Optional[Type[Query[Any]]] = None,
|
||||
autocommit: Literal[False] = False,
|
||||
join_transaction_mode: JoinTransactionMode = "conditional_savepoint",
|
||||
close_resets_only: Union[bool, _NoArg] = _NoArg.NO_ARG,
|
||||
):
|
||||
r"""Construct a new :class:`_orm.Session`.
|
||||
|
||||
@@ -1607,8 +1632,9 @@ class Session(_SessionClassMethods, EventTarget):
|
||||
|
||||
.. tip:: When using SQLite, the SQLite driver included through
|
||||
Python 3.11 does not handle SAVEPOINTs correctly in all cases
|
||||
without workarounds. See the section
|
||||
:ref:`pysqlite_serializable` for details on current workarounds.
|
||||
without workarounds. See the sections
|
||||
:ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable`
|
||||
for details on current workarounds.
|
||||
|
||||
* ``"control_fully"`` - the :class:`_orm.Session` will take
|
||||
control of the given transaction as its own;
|
||||
@@ -1639,6 +1665,18 @@ class Session(_SessionClassMethods, EventTarget):
|
||||
|
||||
.. versionadded:: 2.0.0rc1
|
||||
|
||||
:param close_resets_only: Defaults to ``True``. Determines if
|
||||
the session should reset itself after calling ``.close()``
|
||||
or should pass in a no longer usable state, disabling re-use.
|
||||
|
||||
.. versionadded:: 2.0.22 added flag ``close_resets_only``.
|
||||
A future SQLAlchemy version may change the default value of
|
||||
this flag to ``False``.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`session_closing` - Detail on the semantics of
|
||||
:meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
|
||||
|
||||
""" # noqa
|
||||
|
||||
@@ -1671,6 +1709,13 @@ class Session(_SessionClassMethods, EventTarget):
|
||||
self.autoflush = autoflush
|
||||
self.expire_on_commit = expire_on_commit
|
||||
self.enable_baked_queries = enable_baked_queries
|
||||
|
||||
# the idea is that at some point NO_ARG will warn that in the future
|
||||
# the default will switch to close_resets_only=False.
|
||||
if close_resets_only or close_resets_only is _NoArg.NO_ARG:
|
||||
self._close_state = _SessionCloseState.CLOSE_IS_RESET
|
||||
else:
|
||||
self._close_state = _SessionCloseState.ACTIVE
|
||||
if (
|
||||
join_transaction_mode
|
||||
and join_transaction_mode
|
||||
@@ -1858,7 +1903,8 @@ class Session(_SessionClassMethods, EventTarget):
|
||||
|
||||
:ref:`pysqlite_serializable` - special workarounds required
|
||||
with the SQLite driver in order for SAVEPOINT to work
|
||||
correctly.
|
||||
correctly. For asyncio use cases, see the section
|
||||
:ref:`aiosqlite_serializable`.
|
||||
|
||||
"""
|
||||
return self.begin(nested=True)
|
||||
@@ -2393,12 +2439,17 @@ class Session(_SessionClassMethods, EventTarget):
|
||||
|
||||
.. tip::
|
||||
|
||||
The :meth:`_orm.Session.close` method **does not prevent the
|
||||
Session from being used again**. The :class:`_orm.Session` itself
|
||||
does not actually have a distinct "closed" state; it merely means
|
||||
In the default running mode the :meth:`_orm.Session.close`
|
||||
method **does not prevent the Session from being used again**.
|
||||
The :class:`_orm.Session` itself does not actually have a
|
||||
distinct "closed" state; it merely means
|
||||
the :class:`_orm.Session` will release all database connections
|
||||
and ORM objects.
|
||||
|
||||
Setting the parameter :paramref:`_orm.Session.close_resets_only`
|
||||
to ``False`` will instead make the ``close`` final, meaning that
|
||||
any further action on the session will be forbidden.
|
||||
|
||||
.. versionchanged:: 1.4 The :meth:`.Session.close` method does not
|
||||
immediately create a new :class:`.SessionTransaction` object;
|
||||
instead, the new :class:`.SessionTransaction` is created only if
|
||||
@@ -2407,11 +2458,40 @@ class Session(_SessionClassMethods, EventTarget):
|
||||
.. seealso::
|
||||
|
||||
:ref:`session_closing` - detail on the semantics of
|
||||
:meth:`_orm.Session.close`
|
||||
:meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
|
||||
|
||||
:meth:`_orm.Session.reset` - a similar method that behaves like
|
||||
``close()`` with the parameter
|
||||
:paramref:`_orm.Session.close_resets_only` set to ``True``.
|
||||
|
||||
"""
|
||||
self._close_impl(invalidate=False)
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Close out the transactional resources and ORM objects used by this
|
||||
:class:`_orm.Session`, resetting the session to its initial state.
|
||||
|
||||
This method provides for same "reset-only" behavior that the
|
||||
:meth:_orm.Session.close method has provided historically, where the
|
||||
state of the :class:`_orm.Session` is reset as though the object were
|
||||
brand new, and ready to be used again.
|
||||
The method may then be useful for :class:`_orm.Session` objects
|
||||
which set :paramref:`_orm.Session.close_resets_only` to ``False``,
|
||||
so that "reset only" behavior is still available from this method.
|
||||
|
||||
.. versionadded:: 2.0.22
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`session_closing` - detail on the semantics of
|
||||
:meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
|
||||
|
||||
:meth:`_orm.Session.close` - a similar method will additionally
|
||||
prevent re-use of the Session when the parameter
|
||||
:paramref:`_orm.Session.close_resets_only` is set to ``False``.
|
||||
"""
|
||||
self._close_impl(invalidate=False, is_reset=True)
|
||||
|
||||
def invalidate(self) -> None:
|
||||
"""Close this Session, using connection invalidation.
|
||||
|
||||
@@ -2448,7 +2528,9 @@ class Session(_SessionClassMethods, EventTarget):
|
||||
"""
|
||||
self._close_impl(invalidate=True)
|
||||
|
||||
def _close_impl(self, invalidate: bool) -> None:
|
||||
def _close_impl(self, invalidate: bool, is_reset: bool = False) -> None:
|
||||
if not is_reset and self._close_state is _SessionCloseState.ACTIVE:
|
||||
self._close_state = _SessionCloseState.CLOSED
|
||||
self.expunge_all()
|
||||
if self._transaction is not None:
|
||||
for transaction in self._transaction._iterate_self_and_parents():
|
||||
@@ -3580,6 +3662,57 @@ class Session(_SessionClassMethods, EventTarget):
|
||||
bind_arguments=bind_arguments,
|
||||
)
|
||||
|
||||
def get_one(
|
||||
self,
|
||||
entity: _EntityBindKey[_O],
|
||||
ident: _PKIdentityArgument,
|
||||
*,
|
||||
options: Optional[Sequence[ORMOption]] = None,
|
||||
populate_existing: bool = False,
|
||||
with_for_update: ForUpdateParameter = None,
|
||||
identity_token: Optional[Any] = None,
|
||||
execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
|
||||
bind_arguments: Optional[_BindArguments] = None,
|
||||
) -> _O:
|
||||
"""Return exactly one instance based on the given primary key
|
||||
identifier, or raise an exception if not found.
|
||||
|
||||
Raises ``sqlalchemy.orm.exc.NoResultFound`` if the query
|
||||
selects no rows.
|
||||
|
||||
For a detailed documentation of the arguments see the
|
||||
method :meth:`.Session.get`.
|
||||
|
||||
.. versionadded:: 2.0.22
|
||||
|
||||
:return: The object instance.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.Session.get` - equivalent method that instead
|
||||
returns ``None`` if no row was found with the provided primary
|
||||
key
|
||||
|
||||
"""
|
||||
|
||||
instance = self.get(
|
||||
entity,
|
||||
ident,
|
||||
options=options,
|
||||
populate_existing=populate_existing,
|
||||
with_for_update=with_for_update,
|
||||
identity_token=identity_token,
|
||||
execution_options=execution_options,
|
||||
bind_arguments=bind_arguments,
|
||||
)
|
||||
|
||||
if instance is None:
|
||||
raise sa_exc.NoResultFound(
|
||||
"No row was found when one was required"
|
||||
)
|
||||
|
||||
return instance
|
||||
|
||||
def _get_impl(
|
||||
self,
|
||||
entity: _EntityBindKey[_O],
|
||||
|
||||
@@ -617,8 +617,8 @@ class InstanceState(interfaces.InspectionAttrInfo, Generic[_O]):
|
||||
self.class_ = state_dict["class_"]
|
||||
|
||||
self.committed_state = state_dict.get("committed_state", {})
|
||||
self._pending_mutations = state_dict.get("_pending_mutations", {}) # type: ignore # noqa E501
|
||||
self.parents = state_dict.get("parents", {}) # type: ignore
|
||||
self._pending_mutations = state_dict.get("_pending_mutations", {})
|
||||
self.parents = state_dict.get("parents", {})
|
||||
self.modified = state_dict.get("modified", False)
|
||||
self.expired = state_dict.get("expired", False)
|
||||
if "info" in state_dict:
|
||||
|
||||
@@ -306,8 +306,9 @@ class ExpressionColumnLoader(ColumnLoader):
|
||||
**kwargs,
|
||||
):
|
||||
columns = None
|
||||
if loadopt and "expression" in loadopt.local_opts:
|
||||
columns = [loadopt.local_opts["expression"]]
|
||||
if loadopt and loadopt._extra_criteria:
|
||||
columns = loadopt._extra_criteria
|
||||
|
||||
elif self._have_default_expression:
|
||||
columns = self.parent_property.columns
|
||||
|
||||
@@ -343,8 +344,8 @@ class ExpressionColumnLoader(ColumnLoader):
|
||||
):
|
||||
# look through list of columns represented here
|
||||
# to see which, if any, is present in the row.
|
||||
if loadopt and "expression" in loadopt.local_opts:
|
||||
columns = [loadopt.local_opts["expression"]]
|
||||
if loadopt and loadopt._extra_criteria:
|
||||
columns = loadopt._extra_criteria
|
||||
|
||||
for col in columns:
|
||||
if adapter:
|
||||
|
||||
@@ -34,6 +34,7 @@ from .attributes import QueryableAttribute
|
||||
from .base import InspectionAttr
|
||||
from .interfaces import LoaderOption
|
||||
from .path_registry import _DEFAULT_TOKEN
|
||||
from .path_registry import _StrPathToken
|
||||
from .path_registry import _WILDCARD_TOKEN
|
||||
from .path_registry import AbstractEntityRegistry
|
||||
from .path_registry import path_is_property
|
||||
@@ -77,7 +78,7 @@ if typing.TYPE_CHECKING:
|
||||
from ..sql.cache_key import CacheKey
|
||||
|
||||
|
||||
_AttrType = Union[str, "QueryableAttribute[Any]"]
|
||||
_AttrType = Union[Literal["*"], "QueryableAttribute[Any]"]
|
||||
|
||||
_WildcardKeyType = Literal["relationship", "column"]
|
||||
_StrategySpec = Dict[str, Any]
|
||||
@@ -541,7 +542,12 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
|
||||
)
|
||||
|
||||
def defaultload(self, attr: _AttrType) -> Self:
|
||||
"""Indicate an attribute should load using its default loader style.
|
||||
"""Indicate an attribute should load using its predefined loader style.
|
||||
|
||||
The behavior of this loading option is to not change the current
|
||||
loading style of the attribute, meaning that the previously configured
|
||||
one is used or, if no previous style was selected, the default
|
||||
loading will be used.
|
||||
|
||||
This method is used to link to other loader options further into
|
||||
a chain of attributes without altering the loader style of the links
|
||||
@@ -741,7 +747,7 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
|
||||
)
|
||||
|
||||
return self._set_column_strategy(
|
||||
(key,), {"query_expression": True}, opts={"expression": expression}
|
||||
(key,), {"query_expression": True}, extra_criteria=(expression,)
|
||||
)
|
||||
|
||||
def selectin_polymorphic(self, classes: Iterable[Type[Any]]) -> Self:
|
||||
@@ -813,6 +819,7 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
|
||||
attrs: Tuple[_AttrType, ...],
|
||||
strategy: Optional[_StrategySpec],
|
||||
opts: Optional[_OptsType] = None,
|
||||
extra_criteria: Optional[Tuple[Any, ...]] = None,
|
||||
) -> Self:
|
||||
strategy_key = self._coerce_strat(strategy)
|
||||
|
||||
@@ -822,6 +829,7 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
|
||||
_COLUMN_TOKEN,
|
||||
opts=opts,
|
||||
attr_group=attrs,
|
||||
extra_criteria=extra_criteria,
|
||||
)
|
||||
return self
|
||||
|
||||
@@ -878,6 +886,7 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
|
||||
attr_group: Optional[_AttrGroupType] = None,
|
||||
propagate_to_loaders: bool = True,
|
||||
reconcile_to_other: Optional[bool] = None,
|
||||
extra_criteria: Optional[Tuple[Any, ...]] = None,
|
||||
) -> Self:
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -977,6 +986,7 @@ class Load(_AbstractLoad):
|
||||
__slots__ = (
|
||||
"path",
|
||||
"context",
|
||||
"additional_source_entities",
|
||||
)
|
||||
|
||||
_traverse_internals = [
|
||||
@@ -986,11 +996,16 @@ class Load(_AbstractLoad):
|
||||
visitors.InternalTraversal.dp_has_cache_key_list,
|
||||
),
|
||||
("propagate_to_loaders", visitors.InternalTraversal.dp_boolean),
|
||||
(
|
||||
"additional_source_entities",
|
||||
visitors.InternalTraversal.dp_has_cache_key_list,
|
||||
),
|
||||
]
|
||||
_cache_key_traversal = None
|
||||
|
||||
path: PathRegistry
|
||||
context: Tuple[_LoadElement, ...]
|
||||
additional_source_entities: Tuple[_InternalEntityType[Any], ...]
|
||||
|
||||
def __init__(self, entity: _EntityType[Any]):
|
||||
insp = cast("Union[Mapper[Any], AliasedInsp[Any]]", inspect(entity))
|
||||
@@ -999,16 +1014,20 @@ class Load(_AbstractLoad):
|
||||
self.path = insp._path_registry
|
||||
self.context = ()
|
||||
self.propagate_to_loaders = False
|
||||
self.additional_source_entities = ()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Load({self.path[0]})"
|
||||
|
||||
@classmethod
|
||||
def _construct_for_existing_path(cls, path: PathRegistry) -> Load:
|
||||
def _construct_for_existing_path(
|
||||
cls, path: AbstractEntityRegistry
|
||||
) -> Load:
|
||||
load = cls.__new__(cls)
|
||||
load.path = path
|
||||
load.context = ()
|
||||
load.propagate_to_loaders = False
|
||||
load.additional_source_entities = ()
|
||||
return load
|
||||
|
||||
def _adapt_cached_option_to_uncached_option(
|
||||
@@ -1016,6 +1035,13 @@ class Load(_AbstractLoad):
|
||||
) -> ORMOption:
|
||||
return self._adjust_for_extra_criteria(context)
|
||||
|
||||
def _prepend_path(self, path: PathRegistry) -> Load:
|
||||
cloned = self._clone()
|
||||
cloned.context = tuple(
|
||||
element._prepend_path(path) for element in self.context
|
||||
)
|
||||
return cloned
|
||||
|
||||
def _adjust_for_extra_criteria(self, context: QueryContext) -> Load:
|
||||
"""Apply the current bound parameters in a QueryContext to all
|
||||
occurrences "extra_criteria" stored within this ``Load`` object,
|
||||
@@ -1029,16 +1055,10 @@ class Load(_AbstractLoad):
|
||||
found_crit = False
|
||||
|
||||
def process(opt: _LoadElement) -> _LoadElement:
|
||||
if not opt._extra_criteria:
|
||||
return opt
|
||||
|
||||
nonlocal orig_cache_key, replacement_cache_key, found_crit
|
||||
|
||||
found_crit = True
|
||||
|
||||
# avoid generating cache keys for the queries if we don't
|
||||
# actually have any extra_criteria options, which is the
|
||||
# common case
|
||||
if orig_cache_key is None or replacement_cache_key is None:
|
||||
orig_cache_key = orig_query._generate_cache_key()
|
||||
replacement_cache_key = context.query._generate_cache_key()
|
||||
@@ -1052,8 +1072,12 @@ class Load(_AbstractLoad):
|
||||
)
|
||||
for crit in opt._extra_criteria
|
||||
)
|
||||
|
||||
return opt
|
||||
|
||||
# avoid generating cache keys for the queries if we don't
|
||||
# actually have any extra_criteria options, which is the
|
||||
# common case
|
||||
new_context = tuple(
|
||||
process(value._clone()) if value._extra_criteria else value
|
||||
for value in self.context
|
||||
@@ -1116,9 +1140,12 @@ class Load(_AbstractLoad):
|
||||
|
||||
assert cloned.propagate_to_loaders == self.propagate_to_loaders
|
||||
|
||||
if not orm_util._entity_corresponds_to_use_path_impl(
|
||||
cast("_InternalEntityType[Any]", parent.path[-1]),
|
||||
cast("_InternalEntityType[Any]", cloned.path[0]),
|
||||
if not any(
|
||||
orm_util._entity_corresponds_to_use_path_impl(
|
||||
elem, cloned.path.odd_element(0)
|
||||
)
|
||||
for elem in (parent.path.odd_element(-1),)
|
||||
+ parent.additional_source_entities
|
||||
):
|
||||
if len(cloned.path) > 1:
|
||||
attrname = cloned.path[1]
|
||||
@@ -1137,6 +1164,9 @@ class Load(_AbstractLoad):
|
||||
|
||||
if cloned.context:
|
||||
parent.context += cloned.context
|
||||
parent.additional_source_entities += (
|
||||
cloned.additional_source_entities
|
||||
)
|
||||
|
||||
@_generative
|
||||
def options(self, *opts: _AbstractLoad) -> Self:
|
||||
@@ -1191,6 +1221,7 @@ class Load(_AbstractLoad):
|
||||
attr_group: Optional[_AttrGroupType] = None,
|
||||
propagate_to_loaders: bool = True,
|
||||
reconcile_to_other: Optional[bool] = None,
|
||||
extra_criteria: Optional[Tuple[Any, ...]] = None,
|
||||
) -> Self:
|
||||
# for individual strategy that needs to propagate, set the whole
|
||||
# Load container to also propagate, so that it shows up in
|
||||
@@ -1224,9 +1255,14 @@ class Load(_AbstractLoad):
|
||||
propagate_to_loaders,
|
||||
attr_group=attr_group,
|
||||
reconcile_to_other=reconcile_to_other,
|
||||
extra_criteria=extra_criteria,
|
||||
)
|
||||
if load_element:
|
||||
self.context += (load_element,)
|
||||
assert opts is not None
|
||||
self.additional_source_entities += cast(
|
||||
"Tuple[_InternalEntityType[Any]]", opts["entities"]
|
||||
)
|
||||
|
||||
else:
|
||||
for attr in attrs:
|
||||
@@ -1240,6 +1276,7 @@ class Load(_AbstractLoad):
|
||||
propagate_to_loaders,
|
||||
attr_group=attr_group,
|
||||
reconcile_to_other=reconcile_to_other,
|
||||
extra_criteria=extra_criteria,
|
||||
)
|
||||
else:
|
||||
load_element = _AttributeStrategyLoad.create(
|
||||
@@ -1251,6 +1288,7 @@ class Load(_AbstractLoad):
|
||||
propagate_to_loaders,
|
||||
attr_group=attr_group,
|
||||
reconcile_to_other=reconcile_to_other,
|
||||
extra_criteria=extra_criteria,
|
||||
)
|
||||
|
||||
if load_element:
|
||||
@@ -1314,6 +1352,7 @@ class _WildcardLoad(_AbstractLoad):
|
||||
attr_group=None,
|
||||
propagate_to_loaders=True,
|
||||
reconcile_to_other=None,
|
||||
extra_criteria=None,
|
||||
):
|
||||
assert attrs is not None
|
||||
attr = attrs[0]
|
||||
@@ -1330,6 +1369,8 @@ class _WildcardLoad(_AbstractLoad):
|
||||
if opts:
|
||||
self.local_opts = util.immutabledict(opts)
|
||||
|
||||
assert extra_criteria is None
|
||||
|
||||
def options(self, *opts: _AbstractLoad) -> Self:
|
||||
raise NotImplementedError("Star option does not support sub-options")
|
||||
|
||||
@@ -1616,7 +1657,9 @@ class _LoadElement(
|
||||
|
||||
return effective_path
|
||||
|
||||
def _init_path(self, path, attr, wildcard_key, attr_group, raiseerr):
|
||||
def _init_path(
|
||||
self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
|
||||
):
|
||||
"""Apply ORM attributes and/or wildcard to an existing path, producing
|
||||
a new path.
|
||||
|
||||
@@ -1668,7 +1711,7 @@ class _LoadElement(
|
||||
def create(
|
||||
cls,
|
||||
path: PathRegistry,
|
||||
attr: Optional[_AttrType],
|
||||
attr: Union[_AttrType, _StrPathToken, None],
|
||||
strategy: Optional[_StrategyKey],
|
||||
wildcard_key: Optional[_WildcardKeyType],
|
||||
local_opts: Optional[_OptsType],
|
||||
@@ -1676,6 +1719,7 @@ class _LoadElement(
|
||||
raiseerr: bool = True,
|
||||
attr_group: Optional[_AttrGroupType] = None,
|
||||
reconcile_to_other: Optional[bool] = None,
|
||||
extra_criteria: Optional[Tuple[Any, ...]] = None,
|
||||
) -> _LoadElement:
|
||||
"""Create a new :class:`._LoadElement` object."""
|
||||
|
||||
@@ -1695,7 +1739,9 @@ class _LoadElement(
|
||||
else:
|
||||
opt._reconcile_to_other = None
|
||||
|
||||
path = opt._init_path(path, attr, wildcard_key, attr_group, raiseerr)
|
||||
path = opt._init_path(
|
||||
path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
|
||||
)
|
||||
|
||||
if not path:
|
||||
return None # type: ignore
|
||||
@@ -1714,9 +1760,7 @@ class _LoadElement(
|
||||
|
||||
return cloned
|
||||
|
||||
def _prepend_path_from(
|
||||
self, parent: Union[Load, _LoadElement]
|
||||
) -> _LoadElement:
|
||||
def _prepend_path_from(self, parent: Load) -> _LoadElement:
|
||||
"""adjust the path of this :class:`._LoadElement` to be
|
||||
a subpath of that of the given parent :class:`_orm.Load` object's
|
||||
path.
|
||||
@@ -1725,22 +1769,30 @@ class _LoadElement(
|
||||
which is in turn part of the :meth:`_orm.Load.options` method.
|
||||
|
||||
"""
|
||||
|
||||
if not any(
|
||||
orm_util._entity_corresponds_to_use_path_impl(
|
||||
elem,
|
||||
self.path.odd_element(0),
|
||||
)
|
||||
for elem in (parent.path.odd_element(-1),)
|
||||
+ parent.additional_source_entities
|
||||
):
|
||||
raise sa_exc.ArgumentError(
|
||||
f'Attribute "{self.path[1]}" does not link '
|
||||
f'from element "{parent.path[-1]}".'
|
||||
)
|
||||
|
||||
return self._prepend_path(parent.path)
|
||||
|
||||
def _prepend_path(self, path: PathRegistry) -> _LoadElement:
|
||||
cloned = self._clone()
|
||||
|
||||
assert cloned.strategy == self.strategy
|
||||
assert cloned.local_opts == self.local_opts
|
||||
assert cloned.is_class_strategy == self.is_class_strategy
|
||||
|
||||
if not orm_util._entity_corresponds_to_use_path_impl(
|
||||
cast("_InternalEntityType[Any]", parent.path[-1]),
|
||||
cast("_InternalEntityType[Any]", cloned.path[0]),
|
||||
):
|
||||
raise sa_exc.ArgumentError(
|
||||
f'Attribute "{cloned.path[1]}" does not link '
|
||||
f'from element "{parent.path[-1]}".'
|
||||
)
|
||||
|
||||
cloned.path = PathRegistry.coerce(parent.path[0:-1] + cloned.path[:])
|
||||
cloned.path = PathRegistry.coerce(path[0:-1] + cloned.path[:])
|
||||
|
||||
return cloned
|
||||
|
||||
@@ -1789,7 +1841,7 @@ class _LoadElement(
|
||||
replacement.local_opts = replacement.local_opts.union(
|
||||
existing.local_opts
|
||||
)
|
||||
replacement._extra_criteria += replacement._extra_criteria
|
||||
replacement._extra_criteria += existing._extra_criteria
|
||||
return replacement
|
||||
elif replacement.path.is_token:
|
||||
# use 'last one wins' logic for wildcard options. this is also
|
||||
@@ -1831,7 +1883,9 @@ class _AttributeStrategyLoad(_LoadElement):
|
||||
is_class_strategy = False
|
||||
is_token_strategy = False
|
||||
|
||||
def _init_path(self, path, attr, wildcard_key, attr_group, raiseerr):
|
||||
def _init_path(
|
||||
self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
|
||||
):
|
||||
assert attr is not None
|
||||
self._of_type = None
|
||||
self._path_with_polymorphic_path = None
|
||||
@@ -1877,7 +1931,11 @@ class _AttributeStrategyLoad(_LoadElement):
|
||||
# from an attribute. This appears to have been an artifact of how
|
||||
# _UnboundLoad / Load interacted together, which was opaque and
|
||||
# poorly defined.
|
||||
self._extra_criteria = attr._extra_criteria
|
||||
if extra_criteria:
|
||||
assert not attr._extra_criteria
|
||||
self._extra_criteria = extra_criteria
|
||||
else:
|
||||
self._extra_criteria = attr._extra_criteria
|
||||
|
||||
if getattr(attr, "_of_type", None):
|
||||
ac = attr._of_type
|
||||
@@ -2070,7 +2128,9 @@ class _TokenStrategyLoad(_LoadElement):
|
||||
is_class_strategy = False
|
||||
is_token_strategy = True
|
||||
|
||||
def _init_path(self, path, attr, wildcard_key, attr_group, raiseerr):
|
||||
def _init_path(
|
||||
self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
|
||||
):
|
||||
# assert isinstance(attr, str) or attr is None
|
||||
if attr is not None:
|
||||
default_token = attr.endswith(_DEFAULT_TOKEN)
|
||||
@@ -2156,7 +2216,9 @@ class _ClassStrategyLoad(_LoadElement):
|
||||
|
||||
__visit_name__ = "class_strategy_load_element"
|
||||
|
||||
def _init_path(self, path, attr, wildcard_key, attr_group, raiseerr):
|
||||
def _init_path(
|
||||
self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
|
||||
):
|
||||
return path
|
||||
|
||||
def _prepare_for_compile_state(
|
||||
@@ -2447,7 +2509,9 @@ def _raise_for_does_not_link(path, attrname, parent_entity):
|
||||
(
|
||||
" Did you mean to use "
|
||||
f'"{path[-2]}'
|
||||
f'.of_type({parent_entity_str})"?'
|
||||
f'.of_type({parent_entity_str})" or "loadopt.options('
|
||||
f"selectin_polymorphic({path[-2].mapper.class_.__name__}, "
|
||||
f'[{parent_entity_str}]), ...)" ?'
|
||||
if not path_is_of_type
|
||||
and not path[-1].is_aliased_class
|
||||
and orm_util._entity_corresponds_to(
|
||||
|
||||
@@ -45,6 +45,7 @@ from .base import _never_set as _never_set # noqa: F401
|
||||
from .base import _none_set as _none_set # noqa: F401
|
||||
from .base import attribute_str as attribute_str # noqa: F401
|
||||
from .base import class_mapper as class_mapper
|
||||
from .base import DynamicMapped
|
||||
from .base import InspectionAttr as InspectionAttr
|
||||
from .base import instance_str as instance_str # noqa: F401
|
||||
from .base import Mapped
|
||||
@@ -55,6 +56,7 @@ from .base import ORMDescriptor
|
||||
from .base import state_attribute_str as state_attribute_str # noqa: F401
|
||||
from .base import state_class_str as state_class_str # noqa: F401
|
||||
from .base import state_str as state_str # noqa: F401
|
||||
from .base import WriteOnlyMapped
|
||||
from .interfaces import CriteriaOption
|
||||
from .interfaces import MapperProperty as MapperProperty
|
||||
from .interfaces import ORMColumnsClauseRole
|
||||
@@ -100,6 +102,7 @@ if typing.TYPE_CHECKING:
|
||||
from .context import _MapperEntity
|
||||
from .context import ORMCompileState
|
||||
from .mapper import Mapper
|
||||
from .path_registry import AbstractEntityRegistry
|
||||
from .query import Query
|
||||
from .relationships import RelationshipProperty
|
||||
from ..engine import Row
|
||||
@@ -137,7 +140,14 @@ all_cascades = frozenset(
|
||||
|
||||
|
||||
_de_stringify_partial = functools.partial(
|
||||
functools.partial, locals_=util.immutabledict({"Mapped": Mapped})
|
||||
functools.partial,
|
||||
locals_=util.immutabledict(
|
||||
{
|
||||
"Mapped": Mapped,
|
||||
"WriteOnlyMapped": WriteOnlyMapped,
|
||||
"DynamicMapped": DynamicMapped,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
# partial is practically useless as we have to write out the whole
|
||||
@@ -935,7 +945,7 @@ class AliasedInsp(
|
||||
"""the AliasedClass that refers to this AliasedInsp"""
|
||||
|
||||
_target: Union[Type[_O], AliasedClass[_O]]
|
||||
"""the thing referred towards by the AliasedClass/AliasedInsp.
|
||||
"""the thing referenced by the AliasedClass/AliasedInsp.
|
||||
|
||||
In the vast majority of cases, this is the mapped class. However
|
||||
it may also be another AliasedClass (alias of alias).
|
||||
@@ -1124,7 +1134,7 @@ class AliasedInsp(
|
||||
return self.mapper.class_
|
||||
|
||||
@property
|
||||
def _path_registry(self) -> PathRegistry:
|
||||
def _path_registry(self) -> AbstractEntityRegistry:
|
||||
if self._use_mapper_path:
|
||||
return self.mapper._path_registry
|
||||
else:
|
||||
@@ -1204,7 +1214,7 @@ class AliasedInsp(
|
||||
# IMO mypy should see this one also as returning the same type
|
||||
# we put into it, but it's not
|
||||
return (
|
||||
self._adapter.traverse(expr) # type: ignore
|
||||
self._adapter.traverse(expr)
|
||||
._annotate(d)
|
||||
._set_propagate_attrs(
|
||||
{"compile_state_plugin": "orm", "plugin_subject": self}
|
||||
@@ -1397,7 +1407,7 @@ class LoaderCriteriaOption(CriteriaOption):
|
||||
|
||||
self.deferred_where_criteria = True
|
||||
self.where_criteria = lambdas.DeferredLambdaElement(
|
||||
where_criteria, # type: ignore
|
||||
where_criteria,
|
||||
roles.WhereHavingRole,
|
||||
lambda_args=(_WrapUserEntity(wrap_entity),),
|
||||
opts=lambdas.LambdaOptions(
|
||||
@@ -1620,12 +1630,18 @@ class Bundle(
|
||||
)
|
||||
|
||||
@property
|
||||
def mapper(self) -> Mapper[Any]:
|
||||
return self.exprs[0]._annotations.get("parentmapper", None)
|
||||
def mapper(self) -> Optional[Mapper[Any]]:
|
||||
mp: Optional[Mapper[Any]] = self.exprs[0]._annotations.get(
|
||||
"parentmapper", None
|
||||
)
|
||||
return mp
|
||||
|
||||
@property
|
||||
def entity(self) -> _InternalEntityType[Any]:
|
||||
return self.exprs[0]._annotations.get("parententity", None)
|
||||
def entity(self) -> Optional[_InternalEntityType[Any]]:
|
||||
ie: Optional[_InternalEntityType[Any]] = self.exprs[
|
||||
0
|
||||
]._annotations.get("parententity", None)
|
||||
return ie
|
||||
|
||||
@property
|
||||
def entity_namespace(
|
||||
@@ -1827,8 +1843,8 @@ class _ORMJoin(expression.Join):
|
||||
prop = None
|
||||
on_selectable = None
|
||||
|
||||
left_selectable = left_info.selectable
|
||||
if prop:
|
||||
left_selectable = left_info.selectable
|
||||
adapt_from: Optional[FromClause]
|
||||
if sql_util.clause_is_present(on_selectable, left_selectable):
|
||||
adapt_from = on_selectable
|
||||
@@ -1865,25 +1881,25 @@ class _ORMJoin(expression.Join):
|
||||
|
||||
self._target_adapter = target_adapter
|
||||
|
||||
# we don't use the normal coercions logic for _ORMJoin
|
||||
# (probably should), so do some gymnastics to get the entity.
|
||||
# logic here is for #8721, which was a major bug in 1.4
|
||||
# for almost two years, not reported/fixed until 1.4.43 (!)
|
||||
if is_selectable(left_info):
|
||||
parententity = left_selectable._annotations.get(
|
||||
"parententity", None
|
||||
)
|
||||
elif insp_is_mapper(left_info) or insp_is_aliased_class(left_info):
|
||||
parententity = left_info
|
||||
else:
|
||||
parententity = None
|
||||
# we don't use the normal coercions logic for _ORMJoin
|
||||
# (probably should), so do some gymnastics to get the entity.
|
||||
# logic here is for #8721, which was a major bug in 1.4
|
||||
# for almost two years, not reported/fixed until 1.4.43 (!)
|
||||
if is_selectable(left_info):
|
||||
parententity = left_selectable._annotations.get(
|
||||
"parententity", None
|
||||
)
|
||||
elif insp_is_mapper(left_info) or insp_is_aliased_class(left_info):
|
||||
parententity = left_info
|
||||
else:
|
||||
parententity = None
|
||||
|
||||
if parententity is not None:
|
||||
self._annotations = self._annotations.union(
|
||||
{"parententity": parententity}
|
||||
)
|
||||
if parententity is not None:
|
||||
self._annotations = self._annotations.union(
|
||||
{"parententity": parententity}
|
||||
)
|
||||
|
||||
augment_onclause = onclause is None and _extra_criteria
|
||||
augment_onclause = bool(_extra_criteria) and not prop
|
||||
expression.Join.__init__(self, left, right, onclause, isouter, full)
|
||||
|
||||
assert self.onclause is not None
|
||||
@@ -2169,9 +2185,9 @@ def _getitem(iterable_query: Query[Any], item: Any) -> Any:
|
||||
|
||||
res = iterable_query.slice(start, stop)
|
||||
if step is not None:
|
||||
return list(res)[None : None : item.step] # type: ignore
|
||||
return list(res)[None : None : item.step]
|
||||
else:
|
||||
return list(res) # type: ignore
|
||||
return list(res)
|
||||
else:
|
||||
if item == -1:
|
||||
_no_negative_indexes()
|
||||
@@ -2234,14 +2250,17 @@ def _cleanup_mapped_str_annotation(
|
||||
"outside of TYPE_CHECKING blocks"
|
||||
) from ne
|
||||
|
||||
try:
|
||||
if issubclass(obj, _MappedAnnotationBase):
|
||||
real_symbol = obj.__name__
|
||||
else:
|
||||
if obj is typing.ClassVar:
|
||||
real_symbol = "ClassVar"
|
||||
else:
|
||||
try:
|
||||
if issubclass(obj, _MappedAnnotationBase):
|
||||
real_symbol = obj.__name__
|
||||
else:
|
||||
return annotation
|
||||
except TypeError:
|
||||
# avoid isinstance(obj, type) check, just catch TypeError
|
||||
return annotation
|
||||
except TypeError:
|
||||
# avoid isinstance(obj, type) check, just catch TypeError
|
||||
return annotation
|
||||
|
||||
# note: if one of the codepaths above didn't define real_symbol and
|
||||
# then didn't return, real_symbol raises UnboundLocalError
|
||||
@@ -2380,9 +2399,9 @@ def _extract_mapped_subtype(
|
||||
else:
|
||||
return annotated, None
|
||||
|
||||
if len(annotated.__args__) != 1: # type: ignore
|
||||
if len(annotated.__args__) != 1:
|
||||
raise sa_exc.ArgumentError(
|
||||
"Expected sub-type for Mapped[] annotation"
|
||||
)
|
||||
|
||||
return annotated.__args__[0], annotated.__origin__ # type: ignore
|
||||
return annotated.__args__[0], annotated.__origin__
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
# mypy: ignore-errors
|
||||
|
||||
|
||||
"""Write-only collection API.
|
||||
|
||||
@@ -21,12 +19,17 @@ object must be executed each time.
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Collection
|
||||
from typing import Dict
|
||||
from typing import Generic
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import NoReturn
|
||||
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
|
||||
@@ -36,9 +39,10 @@ from . import attributes
|
||||
from . import interfaces
|
||||
from . import relationships
|
||||
from . import strategies
|
||||
from .base import NEVER_SET
|
||||
from .base import object_mapper
|
||||
from .base import PassiveFlag
|
||||
from .relationships import RelationshipDirection
|
||||
from .base import RelationshipDirection
|
||||
from .. import exc
|
||||
from .. import inspect
|
||||
from .. import log
|
||||
@@ -53,22 +57,38 @@ from ..sql.dml import Update
|
||||
from ..util.typing import Literal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import QueryableAttribute
|
||||
from ._typing import _InstanceDict
|
||||
from .attributes import _AdaptedCollectionProtocol
|
||||
from .attributes import AttributeEventToken
|
||||
from .attributes import CollectionAdapter
|
||||
from .base import LoaderCallableStatus
|
||||
from .collections import _AdaptedCollectionProtocol
|
||||
from .collections import CollectionAdapter
|
||||
from .mapper import Mapper
|
||||
from .relationships import _RelationshipOrderByArg
|
||||
from .state import InstanceState
|
||||
from .util import AliasedClass
|
||||
from ..event import _Dispatch
|
||||
from ..sql.selectable import FromClause
|
||||
from ..sql.selectable import Select
|
||||
|
||||
|
||||
_T = TypeVar("_T", bound=Any)
|
||||
|
||||
|
||||
class WriteOnlyHistory:
|
||||
class WriteOnlyHistory(Generic[_T]):
|
||||
"""Overrides AttributeHistory to receive append/remove events directly."""
|
||||
|
||||
def __init__(self, attr, state, passive, apply_to=None):
|
||||
unchanged_items: util.OrderedIdentitySet
|
||||
added_items: util.OrderedIdentitySet
|
||||
deleted_items: util.OrderedIdentitySet
|
||||
_reconcile_collection: bool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
attr: WriteOnlyAttributeImpl,
|
||||
state: InstanceState[_T],
|
||||
passive: PassiveFlag,
|
||||
apply_to: Optional[WriteOnlyHistory[_T]] = None,
|
||||
) -> None:
|
||||
if apply_to:
|
||||
if passive & PassiveFlag.SQL_OK:
|
||||
raise exc.InvalidRequestError(
|
||||
@@ -90,18 +110,18 @@ class WriteOnlyHistory:
|
||||
self._reconcile_collection = False
|
||||
|
||||
@property
|
||||
def added_plus_unchanged(self):
|
||||
def added_plus_unchanged(self) -> List[_T]:
|
||||
return list(self.added_items.union(self.unchanged_items))
|
||||
|
||||
@property
|
||||
def all_items(self):
|
||||
def all_items(self) -> List[_T]:
|
||||
return list(
|
||||
self.added_items.union(self.unchanged_items).union(
|
||||
self.deleted_items
|
||||
)
|
||||
)
|
||||
|
||||
def as_history(self):
|
||||
def as_history(self) -> attributes.History:
|
||||
if self._reconcile_collection:
|
||||
added = self.added_items.difference(self.unchanged_items)
|
||||
deleted = self.deleted_items.intersection(self.unchanged_items)
|
||||
@@ -114,13 +134,13 @@ class WriteOnlyHistory:
|
||||
)
|
||||
return attributes.History(list(added), list(unchanged), list(deleted))
|
||||
|
||||
def indexed(self, index):
|
||||
def indexed(self, index: Union[int, slice]) -> Union[List[_T], _T]:
|
||||
return list(self.added_items)[index]
|
||||
|
||||
def add_added(self, value):
|
||||
def add_added(self, value: _T) -> None:
|
||||
self.added_items.add(value)
|
||||
|
||||
def add_removed(self, value):
|
||||
def add_removed(self, value: _T) -> None:
|
||||
if value in self.added_items:
|
||||
self.added_items.remove(value)
|
||||
else:
|
||||
@@ -130,35 +150,41 @@ class WriteOnlyHistory:
|
||||
class WriteOnlyAttributeImpl(
|
||||
attributes.HasCollectionAdapter, attributes.AttributeImpl
|
||||
):
|
||||
uses_objects = True
|
||||
default_accepts_scalar_loader = False
|
||||
supports_population = False
|
||||
_supports_dynamic_iteration = False
|
||||
collection = False
|
||||
dynamic = True
|
||||
order_by = ()
|
||||
collection_history_cls = WriteOnlyHistory
|
||||
uses_objects: bool = True
|
||||
default_accepts_scalar_loader: bool = False
|
||||
supports_population: bool = False
|
||||
_supports_dynamic_iteration: bool = False
|
||||
collection: bool = False
|
||||
dynamic: bool = True
|
||||
order_by: _RelationshipOrderByArg = ()
|
||||
collection_history_cls: Type[WriteOnlyHistory[Any]] = WriteOnlyHistory
|
||||
|
||||
query_class: Type[WriteOnlyCollection[Any]]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
class_,
|
||||
key,
|
||||
typecallable,
|
||||
dispatch,
|
||||
target_mapper,
|
||||
order_by,
|
||||
**kw,
|
||||
class_: Union[Type[Any], AliasedClass[Any]],
|
||||
key: str,
|
||||
dispatch: _Dispatch[QueryableAttribute[Any]],
|
||||
target_mapper: Mapper[_T],
|
||||
order_by: _RelationshipOrderByArg,
|
||||
**kw: Any,
|
||||
):
|
||||
super().__init__(class_, key, typecallable, dispatch, **kw)
|
||||
super().__init__(class_, key, None, dispatch, **kw)
|
||||
self.target_mapper = target_mapper
|
||||
self.query_class = WriteOnlyCollection
|
||||
if order_by:
|
||||
self.order_by = tuple(order_by)
|
||||
|
||||
def get(self, state, dict_, passive=attributes.PASSIVE_OFF):
|
||||
if not passive & attributes.SQL_OK:
|
||||
def get(
|
||||
self,
|
||||
state: InstanceState[Any],
|
||||
dict_: _InstanceDict,
|
||||
passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
|
||||
) -> Union[util.OrderedIdentitySet, WriteOnlyCollection[Any]]:
|
||||
if not passive & PassiveFlag.SQL_OK:
|
||||
return self._get_collection_history(
|
||||
state, attributes.PASSIVE_NO_INITIALIZE
|
||||
state, PassiveFlag.PASSIVE_NO_INITIALIZE
|
||||
).added_items
|
||||
else:
|
||||
return self.query_class(self, state)
|
||||
@@ -204,24 +230,34 @@ class WriteOnlyAttributeImpl(
|
||||
) -> Union[
|
||||
Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
|
||||
]:
|
||||
if not passive & attributes.SQL_OK:
|
||||
data: Collection[Any]
|
||||
if not passive & PassiveFlag.SQL_OK:
|
||||
data = self._get_collection_history(state, passive).added_items
|
||||
else:
|
||||
history = self._get_collection_history(state, passive)
|
||||
data = history.added_plus_unchanged
|
||||
return DynamicCollectionAdapter(data) # type: ignore
|
||||
return DynamicCollectionAdapter(data) # type: ignore[return-value]
|
||||
|
||||
@util.memoized_property
|
||||
def _append_token(self):
|
||||
def _append_token( # type:ignore[override]
|
||||
self,
|
||||
) -> attributes.AttributeEventToken:
|
||||
return attributes.AttributeEventToken(self, attributes.OP_APPEND)
|
||||
|
||||
@util.memoized_property
|
||||
def _remove_token(self):
|
||||
def _remove_token( # type:ignore[override]
|
||||
self,
|
||||
) -> attributes.AttributeEventToken:
|
||||
return attributes.AttributeEventToken(self, attributes.OP_REMOVE)
|
||||
|
||||
def fire_append_event(
|
||||
self, state, dict_, value, initiator, collection_history=None
|
||||
):
|
||||
self,
|
||||
state: InstanceState[Any],
|
||||
dict_: _InstanceDict,
|
||||
value: Any,
|
||||
initiator: Optional[AttributeEventToken],
|
||||
collection_history: Optional[WriteOnlyHistory[Any]] = None,
|
||||
) -> None:
|
||||
if collection_history is None:
|
||||
collection_history = self._modified_event(state, dict_)
|
||||
|
||||
@@ -234,8 +270,13 @@ class WriteOnlyAttributeImpl(
|
||||
self.sethasparent(attributes.instance_state(value), state, True)
|
||||
|
||||
def fire_remove_event(
|
||||
self, state, dict_, value, initiator, collection_history=None
|
||||
):
|
||||
self,
|
||||
state: InstanceState[Any],
|
||||
dict_: _InstanceDict,
|
||||
value: Any,
|
||||
initiator: Optional[AttributeEventToken],
|
||||
collection_history: Optional[WriteOnlyHistory[Any]] = None,
|
||||
) -> None:
|
||||
if collection_history is None:
|
||||
collection_history = self._modified_event(state, dict_)
|
||||
|
||||
@@ -247,18 +288,20 @@ class WriteOnlyAttributeImpl(
|
||||
for fn in self.dispatch.remove:
|
||||
fn(state, value, initiator or self._remove_token)
|
||||
|
||||
def _modified_event(self, state, dict_):
|
||||
def _modified_event(
|
||||
self, state: InstanceState[Any], dict_: _InstanceDict
|
||||
) -> WriteOnlyHistory[Any]:
|
||||
if self.key not in state.committed_state:
|
||||
state.committed_state[self.key] = self.collection_history_cls(
|
||||
self, state, PassiveFlag.PASSIVE_NO_FETCH
|
||||
)
|
||||
|
||||
state._modified_event(dict_, self, attributes.NEVER_SET)
|
||||
state._modified_event(dict_, self, NEVER_SET)
|
||||
|
||||
# this is a hack to allow the entities.ComparableEntity fixture
|
||||
# to work
|
||||
dict_[self.key] = True
|
||||
return state.committed_state[self.key]
|
||||
return state.committed_state[self.key] # type: ignore[no-any-return]
|
||||
|
||||
def set(
|
||||
self,
|
||||
@@ -321,25 +364,38 @@ class WriteOnlyAttributeImpl(
|
||||
collection_history=collection_history,
|
||||
)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
def delete(self, *args: Any, **kwargs: Any) -> NoReturn:
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_committed_value(self, state, dict_, value):
|
||||
def set_committed_value(
|
||||
self, state: InstanceState[Any], dict_: _InstanceDict, value: Any
|
||||
) -> NoReturn:
|
||||
raise NotImplementedError(
|
||||
"Dynamic attributes don't support collection population."
|
||||
)
|
||||
|
||||
def get_history(self, state, dict_, passive=attributes.PASSIVE_NO_FETCH):
|
||||
def get_history(
|
||||
self,
|
||||
state: InstanceState[Any],
|
||||
dict_: _InstanceDict,
|
||||
passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
|
||||
) -> attributes.History:
|
||||
c = self._get_collection_history(state, passive)
|
||||
return c.as_history()
|
||||
|
||||
def get_all_pending(
|
||||
self, state, dict_, passive=attributes.PASSIVE_NO_INITIALIZE
|
||||
):
|
||||
self,
|
||||
state: InstanceState[Any],
|
||||
dict_: _InstanceDict,
|
||||
passive: PassiveFlag = PassiveFlag.PASSIVE_NO_INITIALIZE,
|
||||
) -> List[Tuple[InstanceState[Any], Any]]:
|
||||
c = self._get_collection_history(state, passive)
|
||||
return [(attributes.instance_state(x), x) for x in c.all_items]
|
||||
|
||||
def _get_collection_history(self, state, passive):
|
||||
def _get_collection_history(
|
||||
self, state: InstanceState[Any], passive: PassiveFlag
|
||||
) -> WriteOnlyHistory[Any]:
|
||||
c: WriteOnlyHistory[Any]
|
||||
if self.key in state.committed_state:
|
||||
c = state.committed_state[self.key]
|
||||
else:
|
||||
@@ -347,7 +403,7 @@ class WriteOnlyAttributeImpl(
|
||||
self, state, PassiveFlag.PASSIVE_NO_FETCH
|
||||
)
|
||||
|
||||
if state.has_identity and (passive & attributes.INIT_OK):
|
||||
if state.has_identity and (passive & PassiveFlag.INIT_OK):
|
||||
return self.collection_history_cls(
|
||||
self, state, passive, apply_to=c
|
||||
)
|
||||
@@ -356,34 +412,34 @@ class WriteOnlyAttributeImpl(
|
||||
|
||||
def append(
|
||||
self,
|
||||
state,
|
||||
dict_,
|
||||
value,
|
||||
initiator,
|
||||
passive=attributes.PASSIVE_NO_FETCH,
|
||||
):
|
||||
state: InstanceState[Any],
|
||||
dict_: _InstanceDict,
|
||||
value: Any,
|
||||
initiator: Optional[AttributeEventToken],
|
||||
passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
|
||||
) -> None:
|
||||
if initiator is not self:
|
||||
self.fire_append_event(state, dict_, value, initiator)
|
||||
|
||||
def remove(
|
||||
self,
|
||||
state,
|
||||
dict_,
|
||||
value,
|
||||
initiator,
|
||||
passive=attributes.PASSIVE_NO_FETCH,
|
||||
):
|
||||
state: InstanceState[Any],
|
||||
dict_: _InstanceDict,
|
||||
value: Any,
|
||||
initiator: Optional[AttributeEventToken],
|
||||
passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
|
||||
) -> None:
|
||||
if initiator is not self:
|
||||
self.fire_remove_event(state, dict_, value, initiator)
|
||||
|
||||
def pop(
|
||||
self,
|
||||
state,
|
||||
dict_,
|
||||
value,
|
||||
initiator,
|
||||
passive=attributes.PASSIVE_NO_FETCH,
|
||||
):
|
||||
state: InstanceState[Any],
|
||||
dict_: _InstanceDict,
|
||||
value: Any,
|
||||
initiator: Optional[AttributeEventToken],
|
||||
passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
|
||||
) -> None:
|
||||
self.remove(state, dict_, value, initiator, passive=passive)
|
||||
|
||||
|
||||
@@ -392,7 +448,7 @@ class WriteOnlyAttributeImpl(
|
||||
class WriteOnlyLoader(strategies.AbstractRelationshipLoader, log.Identified):
|
||||
impl_class = WriteOnlyAttributeImpl
|
||||
|
||||
def init_class_attribute(self, mapper):
|
||||
def init_class_attribute(self, mapper: Mapper[Any]) -> None:
|
||||
self.is_class_level = True
|
||||
if not self.uselist or self.parent_property.direction not in (
|
||||
interfaces.ONETOMANY,
|
||||
@@ -404,7 +460,7 @@ class WriteOnlyLoader(strategies.AbstractRelationshipLoader, log.Identified):
|
||||
"uselist=False." % self.parent_property
|
||||
)
|
||||
|
||||
strategies._register_attribute(
|
||||
strategies._register_attribute( # type: ignore[no-untyped-call]
|
||||
self.parent_property,
|
||||
mapper,
|
||||
useobject=True,
|
||||
@@ -418,19 +474,21 @@ class WriteOnlyLoader(strategies.AbstractRelationshipLoader, log.Identified):
|
||||
class DynamicCollectionAdapter:
|
||||
"""simplified CollectionAdapter for internal API consistency"""
|
||||
|
||||
def __init__(self, data):
|
||||
data: Collection[Any]
|
||||
|
||||
def __init__(self, data: Collection[Any]):
|
||||
self.data = data
|
||||
|
||||
def __iter__(self):
|
||||
def __iter__(self) -> Iterator[Any]:
|
||||
return iter(self.data)
|
||||
|
||||
def _reset_empty(self):
|
||||
def _reset_empty(self) -> None:
|
||||
pass
|
||||
|
||||
def __len__(self):
|
||||
def __len__(self) -> int:
|
||||
return len(self.data)
|
||||
|
||||
def __bool__(self):
|
||||
def __bool__(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@@ -443,8 +501,14 @@ class AbstractCollectionWriter(Generic[_T]):
|
||||
if not TYPE_CHECKING:
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, attr, state):
|
||||
self.instance = instance = state.obj()
|
||||
instance: _T
|
||||
_from_obj: Tuple[FromClause, ...]
|
||||
|
||||
def __init__(self, attr: WriteOnlyAttributeImpl, state: InstanceState[_T]):
|
||||
instance = state.obj()
|
||||
if TYPE_CHECKING:
|
||||
assert instance
|
||||
self.instance = instance
|
||||
self.attr = attr
|
||||
|
||||
mapper = object_mapper(instance)
|
||||
@@ -535,7 +599,7 @@ class WriteOnlyCollection(AbstractCollectionWriter[_T]):
|
||||
stmt = stmt.order_by(*self._order_by_clauses)
|
||||
return stmt
|
||||
|
||||
def insert(self) -> Insert[_T]:
|
||||
def insert(self) -> Insert:
|
||||
"""For one-to-many collections, produce a :class:`_dml.Insert` which
|
||||
will insert new rows in terms of this this instance-local
|
||||
:class:`_orm.WriteOnlyCollection`.
|
||||
@@ -561,7 +625,7 @@ class WriteOnlyCollection(AbstractCollectionWriter[_T]):
|
||||
"INSERT along with add_all()."
|
||||
)
|
||||
|
||||
dict_ = {}
|
||||
dict_: Dict[str, Any] = {}
|
||||
|
||||
for l, r in prop.synchronize_pairs:
|
||||
fn = prop._get_attr_w_warn_on_none(
|
||||
@@ -575,14 +639,14 @@ class WriteOnlyCollection(AbstractCollectionWriter[_T]):
|
||||
|
||||
return insert(self.attr.target_mapper).values(**dict_)
|
||||
|
||||
def update(self) -> Update[_T]:
|
||||
def update(self) -> Update:
|
||||
"""Produce a :class:`_dml.Update` which will refer to rows in terms
|
||||
of this instance-local :class:`_orm.WriteOnlyCollection`.
|
||||
|
||||
"""
|
||||
return update(self.attr.target_mapper).where(*self._where_criteria)
|
||||
|
||||
def delete(self) -> Delete[_T]:
|
||||
def delete(self) -> Delete:
|
||||
"""Produce a :class:`_dml.Delete` which will refer to rows in terms
|
||||
of this instance-local :class:`_orm.WriteOnlyCollection`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user