diff --git a/sqlmodel/ext/asyncio/session.py b/sqlmodel/ext/asyncio/session.py index ff99dff899..598cb0d05b 100644 --- a/sqlmodel/ext/asyncio/session.py +++ b/sqlmodel/ext/asyncio/session.py @@ -11,7 +11,7 @@ overload, ) -from sqlalchemy import util +from sqlalchemy import TextClause, util from sqlalchemy.engine.cursor import CursorResult from sqlalchemy.engine.interfaces import _CoreAnyExecuteParams from sqlalchemy.engine.result import Result, ScalarResult, TupleResult @@ -71,6 +71,18 @@ async def exec( _add_event: Optional[Any] = None, ) -> CursorResult[Any]: ... + @overload + async def exec( + self, + statement: TextClause, + *, + params: Optional[Union[Mapping[str, Any], Sequence[Mapping[str, Any]]]] = None, + 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, + ) -> CursorResult[Any]: ... + async def exec( self, statement: Union[ @@ -78,6 +90,7 @@ async def exec( SelectOfScalar[_TSelectParam], Executable[_TSelectParam], UpdateBase, + TextClause, ], *, params: Optional[Union[Mapping[str, Any], Sequence[Mapping[str, Any]]]] = None, diff --git a/sqlmodel/orm/session.py b/sqlmodel/orm/session.py index 9e82d48a73..caa788b398 100644 --- a/sqlmodel/orm/session.py +++ b/sqlmodel/orm/session.py @@ -9,7 +9,7 @@ overload, ) -from sqlalchemy import util +from sqlalchemy import TextClause, util from sqlalchemy.engine.cursor import CursorResult from sqlalchemy.engine.interfaces import _CoreAnyExecuteParams from sqlalchemy.engine.result import Result, ScalarResult, TupleResult @@ -63,6 +63,18 @@ def exec( _add_event: Optional[Any] = None, ) -> CursorResult[Any]: ... + @overload + def exec( + self, + statement: TextClause, + *, + params: Optional[Union[Mapping[str, Any], Sequence[Mapping[str, Any]]]] = None, + 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, + ) -> CursorResult[Any]: ... + def exec( self, statement: Union[ @@ -70,6 +82,7 @@ def exec( SelectOfScalar[_TSelectParam], Executable[_TSelectParam], UpdateBase, + TextClause, ], *, params: Optional[Union[Mapping[str, Any], Sequence[Mapping[str, Any]]]] = None, diff --git a/tests/test_exec_text.py b/tests/test_exec_text.py new file mode 100644 index 0000000000..2b7eb16f7a --- /dev/null +++ b/tests/test_exec_text.py @@ -0,0 +1,49 @@ +from typing import Optional + +from sqlmodel import Field, Session, SQLModel, create_engine, text + + +def test_select_using_text_statement(clear_sqlmodel): + class Hero(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + name: str + secret_name: str + age: Optional[int] = None + + hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson") + + engine = create_engine("sqlite://") + + SQLModel.metadata.create_all(engine) + + with Session(engine) as session: + session.add(hero_1) + session.commit() + session.refresh(hero_1) + + with Session(engine) as session: + res = session.exec(text("SELECT * FROM hero")).all() + assert len(res) == 1 + assert res[0] == (1, "Deadpond", "Dive Wilson", None) + + +def test_insert_using_text_statement(clear_sqlmodel): + class Hero(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + name: str + secret_name: str + age: Optional[int] = None + + engine = create_engine("sqlite://") + + SQLModel.metadata.create_all(engine) + + with Session(engine) as session: + res = session.exec( + text( + "INSERT INTO hero (name, secret_name) VALUES ('Deadpond', 'Dive Wilson')" + ) + ) + session.commit() + + assert res.rowcount == 1