summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/util.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-04-19 21:06:41 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2022-04-27 14:46:36 -0400
commitad11c482e2233f44e8747d4d5a2b17a995fff1fa (patch)
tree57f8ddd30928951519fd6ac0f418e9cbf8e65610 /lib/sqlalchemy/orm/util.py
parent033d1a16e7a220555d7611a5b8cacb1bd83822ae (diff)
downloadsqlalchemy-ad11c482e2233f44e8747d4d5a2b17a995fff1fa.tar.gz
pep484 ORM / SQL result support
after some experimentation it seems mypy is more amenable to the generic types being fully integrated rather than having separate spin-off types. so key structures like Result, Row, Select become generic. For DML Insert, Update, Delete, these are spun into type-specific subclasses ReturningInsert, ReturningUpdate, ReturningDelete, which is fine since the "row-ness" of these constructs doesn't happen until returning() is called in any case. a Tuple based model is then integrated so that these objects can carry along information about their return types. Overloads at the .execute() level carry through the Tuple from the invoked object to the result. To suit the issue of AliasedClass generating attributes that are dynamic, experimented with a custom subclass AsAliased, but then just settled on having aliased() lie to the type checker and return `Type[_O]`, essentially. will need some type-related accessors for with_polymorphic() also. Additionally, identified an issue in Update when used "mysql style" against a join(), it basically doesn't work if asked to UPDATE two tables on the same column name. added an error message to the specific condition where it happens with a very non-specific error message that we hit a thing we can't do right now, suggest multi-table update as a possible cause. Change-Id: I5eff7eefe1d6166ee74160b2785c5e6a81fa8b95
Diffstat (limited to 'lib/sqlalchemy/orm/util.py')
-rw-r--r--lib/sqlalchemy/orm/util.py53
1 files changed, 39 insertions, 14 deletions
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index 3934de535..8148793b1 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -28,6 +28,7 @@ from typing import Union
import weakref
from . import attributes # noqa
+from . import exc
from ._typing import _O
from ._typing import insp_is_aliased_class
from ._typing import insp_is_mapper
@@ -41,6 +42,7 @@ from .base import InspectionAttr as InspectionAttr
from .base import instance_str as instance_str
from .base import object_mapper as object_mapper
from .base import object_state as object_state
+from .base import opt_manager_of_class
from .base import state_attribute_str as state_attribute_str
from .base import state_class_str as state_class_str
from .base import state_str as state_str
@@ -68,6 +70,7 @@ from ..sql.base import ColumnCollection
from ..sql.cache_key import HasCacheKey
from ..sql.cache_key import MemoizedHasCacheKey
from ..sql.elements import ColumnElement
+from ..sql.elements import KeyedColumnElement
from ..sql.selectable import FromClause
from ..util.langhelpers import MemoizedSlots
from ..util.typing import de_stringify_annotation
@@ -95,9 +98,7 @@ if typing.TYPE_CHECKING:
from ..sql.selectable import _ColumnsClauseElement
from ..sql.selectable import Alias
from ..sql.selectable import Subquery
- from ..sql.visitors import _ET
from ..sql.visitors import anon_map
- from ..sql.visitors import ExternallyTraversible
_T = TypeVar("_T", bound=Any)
@@ -341,7 +342,7 @@ def identity_key(
ident: Union[Any, Tuple[Any, ...]] = None,
*,
instance: Optional[_T] = None,
- row: Optional[Union[Row, RowMapping]] = None,
+ row: Optional[Union[Row[Any], RowMapping]] = None,
identity_token: Optional[Any] = None,
) -> _IdentityKeyType[_T]:
r"""Generate "identity key" tuples, as are used as keys in the
@@ -468,7 +469,9 @@ class ORMAdapter(sql_util.ColumnAdapter):
return not entity or entity.isa(self.mapper)
-class AliasedClass(inspection.Inspectable["AliasedInsp[_O]"], Generic[_O]):
+class AliasedClass(
+ inspection.Inspectable["AliasedInsp[_O]"], ORMColumnsClauseRole[_O]
+):
r"""Represents an "aliased" form of a mapped class for usage with Query.
The ORM equivalent of a :func:`~sqlalchemy.sql.expression.alias`
@@ -663,7 +666,7 @@ class AliasedClass(inspection.Inspectable["AliasedInsp[_O]"], Generic[_O]):
@inspection._self_inspects
class AliasedInsp(
- ORMEntityColumnsClauseRole,
+ ORMEntityColumnsClauseRole[_O],
ORMFromClauseRole,
HasCacheKey,
InspectionAttr,
@@ -1276,12 +1279,29 @@ class LoaderCriteriaOption(CriteriaOption):
inspection._inspects(AliasedClass)(lambda target: target._aliased_insp)
+@inspection._inspects(type)
+def _inspect_mc(
+ class_: Type[_O],
+) -> Optional[Mapper[_O]]:
+
+ try:
+ class_manager = opt_manager_of_class(class_)
+ if class_manager is None or not class_manager.is_mapped:
+ return None
+ mapper = class_manager.mapper
+ except exc.NO_STATE:
+
+ return None
+ else:
+ return mapper
+
+
@inspection._self_inspects
class Bundle(
- ORMColumnsClauseRole,
+ ORMColumnsClauseRole[_T],
SupportsCloneAnnotations,
MemoizedHasCacheKey,
- inspection.Inspectable["Bundle"],
+ inspection.Inspectable["Bundle[_T]"],
InspectionAttr,
):
"""A grouping of SQL expressions that are returned by a :class:`.Query`
@@ -1373,10 +1393,10 @@ class Bundle(
@property
def entity_namespace(
self,
- ) -> ReadOnlyColumnCollection[str, ColumnElement[Any]]:
+ ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
return self.c
- columns: ReadOnlyColumnCollection[str, ColumnElement[Any]]
+ columns: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]
"""A namespace of SQL expressions referred to by this :class:`.Bundle`.
@@ -1402,7 +1422,7 @@ class Bundle(
"""
- c: ReadOnlyColumnCollection[str, ColumnElement[Any]]
+ c: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]
"""An alias for :attr:`.Bundle.columns`."""
def _clone(self):
@@ -1908,9 +1928,10 @@ def _extract_mapped_subtype(
raw_annotation: Union[type, str],
cls: type,
key: str,
- attr_cls: type,
+ attr_cls: Type[Any],
required: bool,
is_dataclass_field: bool,
+ superclasses: Optional[Tuple[Type[Any], ...]] = None,
) -> Optional[Union[type, str]]:
if raw_annotation is None:
@@ -1930,9 +1951,13 @@ def _extract_mapped_subtype(
if is_dataclass_field:
return annotated
else:
- if (
- not hasattr(annotated, "__origin__")
- or not issubclass(annotated.__origin__, attr_cls) # type: ignore
+ # TODO: there don't seem to be tests for the failure
+ # conditions here
+ if not hasattr(annotated, "__origin__") or (
+ not issubclass(
+ annotated.__origin__, # type: ignore
+ superclasses if superclasses else attr_cls,
+ )
and not issubclass(attr_cls, annotated.__origin__) # type: ignore
):
our_annotated_str = (