--- /dev/null
+.. change::
+ :tags: bug, typing
+ :tickets: 10353
+
+ Repaired the core "SQL element" class ``SQLCoreOperations`` to support the
+ ``__hash__()`` method from a typing perspective, as objects like
+ :class:`.Column` and ORM :class:`.InstrumentedAttribute` are hashable and
+ are used as dictionary keys in the public API for the :class:`_dml.Update`
+ and :class:`_dml.Insert` constructs. Previously, type checkers were not
+ aware the root SQL element was hashable.
def __le__(self, other: Any) -> ColumnElement[bool]:
...
+ # declare also that this class has an hash method otherwise
+ # it may be assumed to be None by type checkers since the
+ # object defines __eq__ and python sets it to None in that case:
+ # https://docs.python.org/3/reference/datamodel.html#object.__hash__
+ def __hash__(self) -> int:
+ ...
+
def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
...
"""
return self.operate(le, other)
- # TODO: not sure why we have this
- __hash__ = Operators.__hash__
+ # ColumnOperators defines an __eq__ so it must explicitly declare also
+ # an hash or it's set to None by python:
+ # https://docs.python.org/3/reference/datamodel.html#object.__hash__
+ if TYPE_CHECKING:
+
+ def __hash__(self) -> int:
+ ...
+
+ else:
+ __hash__ = Operators.__hash__
def __eq__(self, other: Any) -> ColumnOperators: # type: ignore[override]
"""Implement the ``==`` operator.
reveal_type(r1.rowcount)
+def t_dml_update_with_values() -> None:
+ s1 = update(User).values({User.id: 123, User.data: "value"})
+ r1 = session.execute(s1)
+ # EXPECTED_TYPE: CursorResult[Any]
+ reveal_type(r1)
+ # EXPECTED_TYPE: int
+ reveal_type(r1.rowcount)
+
+
def t_dml_bare_delete() -> None:
s1 = delete(User)
r1 = session.execute(s1)
from sqlalchemy import asc
from sqlalchemy import Column
+from sqlalchemy import column
from sqlalchemy import desc
from sqlalchemy import Integer
from sqlalchemy import literal
reveal_type(literal("123", Integer))
# EXPECTED_TYPE: BindParameter[int]
reveal_type(literal("123", Integer))
+
+
+# hashable (issue #10353):
+
+mydict = {
+ Column("q"): "q",
+ Column("q").desc(): "q",
+ User.id: "q",
+ literal("5"): "q",
+ column("q"): "q",
+}
from sqlalchemy import Column
from sqlalchemy import insert
+from sqlalchemy import Integer
+from sqlalchemy import MetaData
from sqlalchemy import select
+from sqlalchemy import String
+from sqlalchemy import Table
+from sqlalchemy import update
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
pass
+user_table = Table(
+ "user",
+ MetaData(),
+ Column("id", Integer, primary_key=True),
+ Column("data", String),
+)
+
+
class User(Base):
__tablename__ = "user"
[User.id, "name", User.__table__.c.data],
select(User.id, User.name, User.data),
)
+
+
+# test #10353
+stmt5 = update(User).values({User.id: 123, User.data: "value"})
+
+stmt6 = user_table.update().values(
+ {user_table.c.d: 123, user_table.c.data: "value"}
+)