Ensure stubs packages are not installed
Verify if the api is typed
Describe the typing issue
In the project I'm working on I have decided we should write some helper functions for hybrid methods/properties to allow us to, wherever possible, write them as single implementations. This is to avoid potential implementation drift between the Python and SQL Expression versions of these implementations.
However, the advertised way in sqlalchemy to implement one hybrid method that covers both cases is kinda hacky. As far as I can tell you are basically writing it as a method, but having the same function also run as a classmethod with the model type passed instead of a model instance when you want to run in Expression mode.
This obviously means that while it might work fine, when implementing a hybrid_method/hybrid_property as a single function, the expression variant remains completely un-type checked.
I was wondering if there is any way of supporting type checking of both variants somehow? Is this even possible? Maybe the answer is explicitly typing the first argument (ie what would normally be self/cls) and having overloads. Any suggestions from those who have done it?
I'm mostly filing this as an issue as I think if it's currently possible, the docs should make clear how to do it; and if it's not currently possible, but it is something that could potentially be implemented, it'd be nice to see it.
To Reproduce
from datetime import datetime
from typing import overload
from sqlalchemy import ColumnElement, SQLColumnExpression
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class SomeModel(DeclarativeBase):
ID: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
archived_at: Mapped[datetime | None] = mapped_column()
@hybrid_property
def archived(self) -> bool:
# This call will ONLY be type-checked in the Python case, not the SQL Expression case!
return is_not_none(self, self.archived_at)
# This overload will appear to be unused even though it's not!
@overload
def is_not_none[
T
](magic: type[DeclarativeBase], field: SQLColumnExpression[T | None]) -> ColumnElement[
bool
]:
...
@overload
def is_not_none[T](magic: DeclarativeBase, field: T | None) -> bool:
...
def is_not_none[
T
](
# We pass this magic value to provide a canonical way of determining if we're being
# called from the instance method (Python) or the class method (SQL).
magic: type[DeclarativeBase] | DeclarativeBase,
# There is a weird mypy bug where if you add `SQLColumnExpression[T | None]` to the
# below it will fail. Since T can be `SQLColumnExpression[U | None]` anyway leaving
# it off is fine.
field: T | None,
) -> (bool | ColumnElement[bool]):
if isinstance(magic, DeclarativeBase):
if isinstance(field, SQLColumnExpression):
raise ValueError("field must not be of type SQLColumnExpression")
return field is not None
else:
if not isinstance(field, SQLColumnExpression):
raise ValueError("field must be of type SQLColumnExpression")
return field.is_not(None)
Error
No error, the issue is just that one substantial code path is not being type checked.
Versions
- OS: Linux
- Python: 3.12.3
- SQLAlchemy: 2.0.44
- Type checker (eg: mypy 0.991, pyright 1.1.290, etc): mypy 1.19.1
Additional context
No response
Ensure stubs packages are not installed
sqlalchemy-stubsandsqlalchemy2-stubsare not compatible with v2)Verify if the api is typed
Describe the typing issue
In the project I'm working on I have decided we should write some helper functions for hybrid methods/properties to allow us to, wherever possible, write them as single implementations. This is to avoid potential implementation drift between the Python and SQL Expression versions of these implementations.
However, the advertised way in sqlalchemy to implement one hybrid method that covers both cases is kinda hacky. As far as I can tell you are basically writing it as a method, but having the same function also run as a classmethod with the model type passed instead of a model instance when you want to run in Expression mode.
This obviously means that while it might work fine, when implementing a hybrid_method/hybrid_property as a single function, the expression variant remains completely un-type checked.
I was wondering if there is any way of supporting type checking of both variants somehow? Is this even possible? Maybe the answer is explicitly typing the first argument (ie what would normally be
self/cls) and having overloads. Any suggestions from those who have done it?I'm mostly filing this as an issue as I think if it's currently possible, the docs should make clear how to do it; and if it's not currently possible, but it is something that could potentially be implemented, it'd be nice to see it.
To Reproduce
Error
No error, the issue is just that one substantial code path is not being type checked.
Versions
Additional context
No response