summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/engine/row.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/engine/row.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/engine/row.py')
-rw-r--r--lib/sqlalchemy/engine/row.py51
1 files changed, 44 insertions, 7 deletions
diff --git a/lib/sqlalchemy/engine/row.py b/lib/sqlalchemy/engine/row.py
index 4ba39b55d..7c9eacb78 100644
--- a/lib/sqlalchemy/engine/row.py
+++ b/lib/sqlalchemy/engine/row.py
@@ -16,6 +16,7 @@ import typing
from typing import Any
from typing import Callable
from typing import Dict
+from typing import Generic
from typing import Iterator
from typing import List
from typing import Mapping
@@ -24,12 +25,14 @@ from typing import Optional
from typing import overload
from typing import Sequence
from typing import Tuple
+from typing import TYPE_CHECKING
+from typing import TypeVar
from typing import Union
from ..sql import util as sql_util
from ..util._has_cy import HAS_CYEXTENSION
-if typing.TYPE_CHECKING or not HAS_CYEXTENSION:
+if TYPE_CHECKING or not HAS_CYEXTENSION:
from ._py_row import BaseRow as BaseRow
from ._py_row import KEY_INTEGER_ONLY
from ._py_row import KEY_OBJECTS_ONLY
@@ -38,13 +41,16 @@ else:
from sqlalchemy.cyextension.resultproxy import KEY_INTEGER_ONLY
from sqlalchemy.cyextension.resultproxy import KEY_OBJECTS_ONLY
-if typing.TYPE_CHECKING:
+if TYPE_CHECKING:
from .result import _KeyType
from .result import RMKeyView
from ..sql.type_api import _ResultProcessorType
+_T = TypeVar("_T", bound=Any)
+_TP = TypeVar("_TP", bound=Tuple[Any, ...])
-class Row(BaseRow, typing.Sequence[Any]):
+
+class Row(BaseRow, Sequence[Any], Generic[_TP]):
"""Represent a single result row.
The :class:`.Row` object represents a row of a database result. It is
@@ -82,6 +88,37 @@ class Row(BaseRow, typing.Sequence[Any]):
def __delattr__(self, name: str) -> NoReturn:
raise AttributeError("can't delete attribute")
+ def tuple(self) -> _TP:
+ """Return a 'tuple' form of this :class:`.Row`.
+
+ At runtime, this method returns "self"; the :class:`.Row` object is
+ already a named tuple. However, at the typing level, if this
+ :class:`.Row` is typed, the "tuple" return type will be a :pep:`484`
+ ``Tuple`` datatype that contains typing information about individual
+ elements, supporting typed unpacking and attribute access.
+
+ .. versionadded:: 2.0
+
+ .. seealso::
+
+ :meth:`.Result.tuples`
+
+ """
+ return self # type: ignore
+
+ @property
+ def t(self) -> _TP:
+ """a synonym for :attr:`.Row.tuple`
+
+ .. versionadded:: 2.0
+
+ .. seealso::
+
+ :meth:`.Result.t`
+
+ """
+ return self # type: ignore
+
@property
def _mapping(self) -> RowMapping:
"""Return a :class:`.RowMapping` for this :class:`.Row`.
@@ -107,7 +144,7 @@ class Row(BaseRow, typing.Sequence[Any]):
def _filter_on_values(
self, filters: Optional[Sequence[Optional[_ResultProcessorType[Any]]]]
- ) -> Row:
+ ) -> Row[Any]:
return Row(
self._parent,
filters,
@@ -116,7 +153,7 @@ class Row(BaseRow, typing.Sequence[Any]):
self._data,
)
- if not typing.TYPE_CHECKING:
+ if not TYPE_CHECKING:
def _special_name_accessor(name: str) -> Any:
"""Handle ambiguous names such as "count" and "index" """
@@ -151,7 +188,7 @@ class Row(BaseRow, typing.Sequence[Any]):
__hash__ = BaseRow.__hash__
- if typing.TYPE_CHECKING:
+ if TYPE_CHECKING:
@overload
def __getitem__(self, index: int) -> Any:
@@ -299,7 +336,7 @@ class RowMapping(BaseRow, typing.Mapping[str, Any]):
_default_key_style = KEY_OBJECTS_ONLY
- if typing.TYPE_CHECKING:
+ if TYPE_CHECKING:
def __getitem__(self, key: _KeyType) -> Any:
...