While iterating on some typing improvements, my colleague @seamuswn pointed out mypy wasn't catching when values with invalid types were set using a `hybrid_property` setter. I believe this is all that's needed to fix the typing.
### Description
Adjust `hybrid_property.__set__` to expect a value of the type that matches the generic's type variable.
### Checklist
<!-- go over following points. check them with an `x` if they do apply, (they turn into clickable checkboxes once the PR is submitted, so no need to do everything at once)
-->
This pull request is:
- [x] A documentation / typographical / small typing error fix
- Good to go, no issue or tests are needed
- [ ] A short code fix
- please include the issue number, and create an issue if none exists, which
must include a complete example of the issue. one line code fixes without an
issue and demonstration will not be accepted.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests. one line code fixes without tests will not be accepted.
- [ ] A new feature implementation
- please include the issue number, and create an issue if none exists, which must
include a complete example of how the feature would look.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests.
**Have a nice day!**
Closes: #12814
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/12814
Pull-request-sha:
8a17b26aea264acf70c52de324b8ccb92b469f2d
Change-Id: Ic99ccc68a32354ef6fe013ec17242058ad2d6d63
(cherry picked from commit
7a68e2aeffd43fc5b78df6182969e031e31043b9)
else:
return self.fget(instance)
- def __set__(self, instance: object, value: Any) -> None:
+ def __set__(
+ self, instance: object, value: Union[SQLCoreOperations[_T], _T]
+ ) -> None:
if self.fset is None:
raise AttributeError("can't set attribute")
- self.fset(instance, value)
+ self.fset(instance, value) # type: ignore[arg-type]
def __delete__(self, instance: object) -> None:
if self.fdel is None:
--- /dev/null
+from sqlalchemy import func
+from sqlalchemy import select
+from sqlalchemy.ext.hybrid import hybrid_property
+from sqlalchemy.orm import DeclarativeBase
+from sqlalchemy.orm import Mapped
+from sqlalchemy.orm import mapped_column
+from sqlalchemy.sql.elements import SQLCoreOperations
+
+
+class Base(DeclarativeBase):
+ pass
+
+
+class MyModel(Base):
+ __tablename__ = "my_model"
+
+ id: Mapped[int] = mapped_column(primary_key=True)
+
+ int_col: Mapped[int | None] = mapped_column()
+
+ @hybrid_property
+ def some_col(self) -> int:
+ return (self.int_col or 0) + 1
+
+ @some_col.inplace.setter
+ def _str_col_setter(self, value: int | SQLCoreOperations[int]) -> None:
+ self.int_col = value - 1
+
+
+m = MyModel(id=42, int_col=1)
+m.some_col = 42
+m.some_col = select(func.max(MyModel.id)).scalar_subquery()
+m.some_col = func.max(MyModel.id)
def needs_update_getter(self) -> bool:
return self.val
- ...
def needs_update_setter(self, value: bool) -> None:
self.val = value