From ecfb3218f166e4ee445c416fb629dc62be6639d0 Mon Sep 17 00:00:00 2001 From: Peter Landry Date: Wed, 26 Jul 2023 13:37:18 +0200 Subject: [PATCH 01/11] `Result` expects a type `Tuple[_T]` --- sqlmodel/engine/result.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlmodel/engine/result.py b/sqlmodel/engine/result.py index 17020d9995..a0ddf283b9 100644 --- a/sqlmodel/engine/result.py +++ b/sqlmodel/engine/result.py @@ -1,4 +1,4 @@ -from typing import Generic, Iterator, List, Optional, Sequence, TypeVar +from typing import Generic, Iterator, List, Optional, Sequence, Tuple, TypeVar from sqlalchemy.engine.result import Result as _Result from sqlalchemy.engine.result import ScalarResult as _ScalarResult @@ -35,7 +35,7 @@ def one(self) -> _T: return super().one() -class Result(_Result[_T], Generic[_T]): +class Result(_Result[Tuple[_T]], Generic[_T]): def scalars(self, index: int = 0) -> ScalarResult[_T]: return super().scalars(index) # type: ignore From 3738a7f6f968e8ab09c3a4b6fcb95c6bb0415cf8 Mon Sep 17 00:00:00 2001 From: Peter Landry Date: Wed, 26 Jul 2023 13:37:39 +0200 Subject: [PATCH 02/11] Remove unused type ignore --- sqlmodel/engine/result.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlmodel/engine/result.py b/sqlmodel/engine/result.py index a0ddf283b9..ecdb6cd547 100644 --- a/sqlmodel/engine/result.py +++ b/sqlmodel/engine/result.py @@ -67,7 +67,7 @@ def one_or_none(self) -> Optional[_T]: # type: ignore return super().one_or_none() # type: ignore def scalar_one(self) -> _T: - return super().scalar_one() # type: ignore + return super().scalar_one() def scalar_one_or_none(self) -> Optional[_T]: return super().scalar_one_or_none() @@ -76,4 +76,4 @@ def one(self) -> _T: # type: ignore return super().one() # type: ignore def scalar(self) -> Optional[_T]: - return super().scalar() # type: ignore + return super().scalar() From 814988b907ac7fc06d52376c4b35e8ab686c4d55 Mon Sep 17 00:00:00 2001 From: Peter Landry Date: Wed, 26 Jul 2023 14:05:06 +0200 Subject: [PATCH 03/11] Result seems well enough typed in SqlAlchemy now we can simply shim over --- sqlmodel/engine/result.py | 44 ++------------------------------------- 1 file changed, 2 insertions(+), 42 deletions(-) diff --git a/sqlmodel/engine/result.py b/sqlmodel/engine/result.py index ecdb6cd547..650dd92b27 100644 --- a/sqlmodel/engine/result.py +++ b/sqlmodel/engine/result.py @@ -1,4 +1,4 @@ -from typing import Generic, Iterator, List, Optional, Sequence, Tuple, TypeVar +from typing import Generic, Iterator, Optional, Sequence, Tuple, TypeVar from sqlalchemy.engine.result import Result as _Result from sqlalchemy.engine.result import ScalarResult as _ScalarResult @@ -36,44 +36,4 @@ def one(self) -> _T: class Result(_Result[Tuple[_T]], Generic[_T]): - def scalars(self, index: int = 0) -> ScalarResult[_T]: - return super().scalars(index) # type: ignore - - def __iter__(self) -> Iterator[_T]: # type: ignore - return super().__iter__() # type: ignore - - def __next__(self) -> _T: # type: ignore - return super().__next__() # type: ignore - - def partitions(self, size: Optional[int] = None) -> Iterator[List[_T]]: # type: ignore - return super().partitions(size) # type: ignore - - def fetchall(self) -> List[_T]: # type: ignore - return super().fetchall() # type: ignore - - def fetchone(self) -> Optional[_T]: # type: ignore - return super().fetchone() # type: ignore - - def fetchmany(self, size: Optional[int] = None) -> List[_T]: # type: ignore - return super().fetchmany() # type: ignore - - def all(self) -> List[_T]: # type: ignore - return super().all() # type: ignore - - def first(self) -> Optional[_T]: # type: ignore - return super().first() # type: ignore - - def one_or_none(self) -> Optional[_T]: # type: ignore - return super().one_or_none() # type: ignore - - def scalar_one(self) -> _T: - return super().scalar_one() - - def scalar_one_or_none(self) -> Optional[_T]: - return super().scalar_one_or_none() - - def one(self) -> _T: # type: ignore - return super().one() # type: ignore - - def scalar(self) -> Optional[_T]: - return super().scalar() + ... From 118ca33d365a7ece781a0eddf17d89e465a80f16 Mon Sep 17 00:00:00 2001 From: Peter Landry Date: Wed, 26 Jul 2023 14:07:52 +0200 Subject: [PATCH 04/11] Implicit export of ForwardRef was remove in pydantic --- sqlmodel/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 658e5384d8..d05fdcc8b7 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -11,6 +11,7 @@ Callable, ClassVar, Dict, + ForwardRef, List, Mapping, Optional, @@ -29,7 +30,7 @@ from pydantic.fields import FieldInfo as PydanticFieldInfo from pydantic.fields import ModelField, Undefined, UndefinedType from pydantic.main import ModelMetaclass, validate_model -from pydantic.typing import ForwardRef, NoArgAnyCallable, resolve_annotations +from pydantic.typing import NoArgAnyCallable, resolve_annotations from pydantic.utils import ROOT_KEY, Representation from sqlalchemy import Boolean, Column, Date, DateTime from sqlalchemy import Enum as sa_Enum From 91707292c1cabcde3ebc0d1e07be6e430b49e214 Mon Sep 17 00:00:00 2001 From: Peter Landry Date: Wed, 26 Jul 2023 14:11:13 +0200 Subject: [PATCH 05/11] _Select expects a `Tuple[Any, ...]` --- sqlmodel/sql/expression.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlmodel/sql/expression.py b/sqlmodel/sql/expression.py index faa2762e3e..a0ac1bd9d9 100644 --- a/sqlmodel/sql/expression.py +++ b/sqlmodel/sql/expression.py @@ -23,7 +23,7 @@ _TSelect = TypeVar("_TSelect") -class Select(_Select[_TSelect], Generic[_TSelect]): +class Select(_Select[Tuple[_TSelect]], Generic[_TSelect]): inherit_cache = True @@ -31,7 +31,7 @@ class Select(_Select[_TSelect], Generic[_TSelect]): # purpose. This is the same as a normal SQLAlchemy Select class where there's only one # entity, so the result will be converted to a scalar by default. This way writing # for loops on the results will feel natural. -class SelectOfScalar(_Select[_TSelect], Generic[_TSelect]): +class SelectOfScalar(_Select[Tuple[_TSelect]], Generic[_TSelect]): inherit_cache = True From 7f5ba1c118cda7eb603e9b84695b88ff480ebba1 Mon Sep 17 00:00:00 2001 From: Peter Landry Date: Wed, 26 Jul 2023 14:21:03 +0200 Subject: [PATCH 06/11] Use Dict type instead of Mapping for SqlAlchemy compat --- sqlmodel/orm/session.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/sqlmodel/orm/session.py b/sqlmodel/orm/session.py index 64f6ad7967..3b8cec5eed 100644 --- a/sqlmodel/orm/session.py +++ b/sqlmodel/orm/session.py @@ -1,4 +1,14 @@ -from typing import Any, Mapping, Optional, Sequence, Type, TypeVar, Union, overload +from typing import ( + Any, + Dict, + Mapping, + Optional, + Sequence, + Type, + TypeVar, + Union, + overload, +) from sqlalchemy import util from sqlalchemy.orm import Query as _Query @@ -21,7 +31,7 @@ def exec( *, params: Optional[Union[Mapping[str, Any], Sequence[Mapping[str, Any]]]] = None, execution_options: Mapping[str, Any] = util.EMPTY_DICT, - bind_arguments: Optional[Mapping[str, Any]] = None, + bind_arguments: Optional[Dict[str, Any]] = None, _parent_execute_state: Optional[Any] = None, _add_event: Optional[Any] = None, **kw: Any, @@ -35,7 +45,7 @@ def exec( *, params: Optional[Union[Mapping[str, Any], Sequence[Mapping[str, Any]]]] = None, execution_options: Mapping[str, Any] = util.EMPTY_DICT, - bind_arguments: Optional[Mapping[str, Any]] = None, + bind_arguments: Optional[Dict[str, Any]] = None, _parent_execute_state: Optional[Any] = None, _add_event: Optional[Any] = None, **kw: Any, @@ -52,7 +62,7 @@ def exec( *, params: Optional[Union[Mapping[str, Any], Sequence[Mapping[str, Any]]]] = None, execution_options: Mapping[str, Any] = util.EMPTY_DICT, - bind_arguments: Optional[Mapping[str, Any]] = None, + bind_arguments: Optional[Dict[str, Any]] = None, _parent_execute_state: Optional[Any] = None, _add_event: Optional[Any] = None, **kw: Any, @@ -75,7 +85,7 @@ def execute( statement: _Executable, params: Optional[Union[Mapping[str, Any], Sequence[Mapping[str, Any]]]] = None, execution_options: Optional[Mapping[str, Any]] = util.EMPTY_DICT, - bind_arguments: Optional[Mapping[str, Any]] = None, + bind_arguments: Optional[Dict[str, Any]] = None, _parent_execute_state: Optional[Any] = None, _add_event: Optional[Any] = None, **kw: Any, From e942e5e22c38e3d210ab16da0e778b713a3b871a Mon Sep 17 00:00:00 2001 From: Peter Landry Date: Wed, 26 Jul 2023 14:24:01 +0200 Subject: [PATCH 07/11] Execution options are not Optional in SA --- sqlmodel/orm/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlmodel/orm/session.py b/sqlmodel/orm/session.py index 3b8cec5eed..0305dfe463 100644 --- a/sqlmodel/orm/session.py +++ b/sqlmodel/orm/session.py @@ -84,7 +84,7 @@ def execute( self, statement: _Executable, params: Optional[Union[Mapping[str, Any], Sequence[Mapping[str, Any]]]] = None, - execution_options: Optional[Mapping[str, Any]] = util.EMPTY_DICT, + execution_options: Mapping[str, Any] = util.EMPTY_DICT, bind_arguments: Optional[Dict[str, Any]] = None, _parent_execute_state: Optional[Any] = None, _add_event: Optional[Any] = None, From ef9f00a6e11945fd014c9b1c13872755fe09a791 Mon Sep 17 00:00:00 2001 From: Peter Landry Date: Wed, 26 Jul 2023 14:26:41 +0200 Subject: [PATCH 08/11] Another instance of non-optional execution_options --- sqlmodel/orm/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlmodel/orm/session.py b/sqlmodel/orm/session.py index 0305dfe463..33f57abcf8 100644 --- a/sqlmodel/orm/session.py +++ b/sqlmodel/orm/session.py @@ -138,7 +138,7 @@ def get( populate_existing: bool = False, with_for_update: Optional[Union[Literal[True], Mapping[str, Any]]] = None, identity_token: Optional[Any] = None, - execution_options: Optional[Mapping[Any, Any]] = util.EMPTY_DICT, + execution_options: Mapping[Any, Any] = util.EMPTY_DICT, ) -> Optional[_TSelectParam]: return super().get( entity, From 643cea59c56ca79ad3e9d42ef5c0f6be7081f0ec Mon Sep 17 00:00:00 2001 From: Peter Landry Date: Wed, 26 Jul 2023 14:49:28 +0200 Subject: [PATCH 09/11] Fix Tuple in jinja template as well --- sqlmodel/sql/expression.py.jinja2 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlmodel/sql/expression.py.jinja2 b/sqlmodel/sql/expression.py.jinja2 index 49b7678fb0..4284543fe2 100644 --- a/sqlmodel/sql/expression.py.jinja2 +++ b/sqlmodel/sql/expression.py.jinja2 @@ -20,14 +20,14 @@ from sqlalchemy.sql.expression import Select as _Select _TSelect = TypeVar("_TSelect") -class Select(_Select[_TSelect], Generic[_TSelect]): +class Select(_Select[Tuple[_TSelect]], Generic[_TSelect]): inherit_cache = True # This is not comparable to sqlalchemy.sql.selectable.ScalarSelect, that has a different # purpose. This is the same as a normal SQLAlchemy Select class where there's only one # entity, so the result will be converted to a scalar by default. This way writing # for loops on the results will feel natural. -class SelectOfScalar(_Select[_TSelect], Generic[_TSelect]): +class SelectOfScalar(_Select[Tuple[_TSelect]], Generic[_TSelect]): inherit_cache = True From b89adbbab39fbe06bd9be9e82fcd39c96527334c Mon Sep 17 00:00:00 2001 From: Peter Landry Date: Wed, 26 Jul 2023 17:39:35 +0200 Subject: [PATCH 10/11] Use ForUpdateArg from sqlalchemy --- sqlmodel/orm/session.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sqlmodel/orm/session.py b/sqlmodel/orm/session.py index 33f57abcf8..11fdcc4be1 100644 --- a/sqlmodel/orm/session.py +++ b/sqlmodel/orm/session.py @@ -14,6 +14,7 @@ from sqlalchemy.orm import Query as _Query from sqlalchemy.orm import Session as _Session from sqlalchemy.sql.base import Executable as _Executable +from sqlalchemy.sql.selectable import ForUpdateArg as _ForUpdateArg from sqlmodel.sql.expression import Select, SelectOfScalar from typing_extensions import Literal @@ -136,7 +137,7 @@ def get( ident: Any, options: Optional[Sequence[Any]] = None, populate_existing: bool = False, - with_for_update: Optional[Union[Literal[True], Mapping[str, Any]]] = None, + with_for_update: Optional[_ForUpdateArg] = None, identity_token: Optional[Any] = None, execution_options: Mapping[Any, Any] = util.EMPTY_DICT, ) -> Optional[_TSelectParam]: From eff0803f0d6ac6e7456b0d3db0388774b7a79cee Mon Sep 17 00:00:00 2001 From: Peter Landry Date: Wed, 26 Jul 2023 18:05:49 +0200 Subject: [PATCH 11/11] Fix signature for `Session.get` --- sqlmodel/orm/session.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sqlmodel/orm/session.py b/sqlmodel/orm/session.py index 11fdcc4be1..65214a6146 100644 --- a/sqlmodel/orm/session.py +++ b/sqlmodel/orm/session.py @@ -11,6 +11,7 @@ ) from sqlalchemy import util +from sqlalchemy.orm import Mapper as _Mapper from sqlalchemy.orm import Query as _Query from sqlalchemy.orm import Session as _Session from sqlalchemy.sql.base import Executable as _Executable @@ -133,13 +134,14 @@ def query(self, *entities: Any, **kwargs: Any) -> "_Query[Any]": def get( self, - entity: Type[_TSelectParam], + entity: Union[Type[_TSelectParam], "_Mapper[_TSelectParam]"], ident: Any, options: Optional[Sequence[Any]] = None, populate_existing: bool = False, with_for_update: Optional[_ForUpdateArg] = None, identity_token: Optional[Any] = None, execution_options: Mapping[Any, Any] = util.EMPTY_DICT, + bind_arguments: Optional[Dict[str, Any]] = None, ) -> Optional[_TSelectParam]: return super().get( entity, @@ -149,4 +151,5 @@ def get( with_for_update=with_for_update, identity_token=identity_token, execution_options=execution_options, + bind_arguments=bind_arguments )