sqlalchemy2-stubs icon indicating copy to clipboard operation
sqlalchemy2-stubs copied to clipboard

no implementation for hybrids available

Open zzzeek opened this issue 4 years ago • 2 comments

re: #170

this worked for sqlalchemy-stubs, but does not seem to work here

POC from https://github.com/dropbox/sqlalchemy-stubs/issues/98#issuecomment-781794732


from typing import Any
from typing import Callable
from typing import Generic
from typing import Optional
from typing import overload
from typing import Type
from typing import TypeVar
from typing import Union

from sqlalchemy import column
from sqlalchemy import Integer
from sqlalchemy.sql import ColumnElement

_T = TypeVar("_T")


class hybrid_property(Generic[_T]):
    def __init__(
        self,
        fget: Callable[[Any], _T],
        expr: Callable[[Any], ColumnElement[_T]],
    ):
        self.fget = fget
        self.expr = expr

    @overload
    def __get__(
        self, instance: None, owner: Optional[Type[Any]]
    ) -> "ColumnElement[_T]":
        ...

    @overload
    def __get__(self, instance: object, owner: Optional[Type[Any]]) -> _T:
        ...

    def __get__(
        self, instance: Union[object, None], owner: Optional[Type[Any]] = None
    ) -> Any:
        if instance is None:
            return self.expr(owner)
        else:
            return self.fget(instance)

    def expression(
        self, expr: "Callable[[Any], ColumnElement[_T]]"
    ) -> "hybrid_property[_T]":
        return hybrid_property(self.fget, expr)


class MyClass:
    def my_thing_inst(self) -> int:
        return 5

    def my_thing_expr(cls) -> "ColumnElement[int]":
        return column("five", Integer)

    my_thing = hybrid_property(my_thing_inst, my_thing_expr)


mc = MyClass()

int_value: int = mc.my_thing
expr: ColumnElement[int] = MyClass.my_thing

zzzeek avatar Sep 01 '21 14:09 zzzeek

sure :)

CaselIT avatar Sep 01 '21 16:09 CaselIT

The normal definition does not work with mypy:

import sqlalchemy as sa
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import registry

R: registry = registry()


@R.mapped
class SomeModel:
    __tablename__ = "some_model"
    a = sa.Column(sa.Float, primary_key=True)

    @hybrid_property
    def hp(self) -> float:
        return abs(self.a) / 2 if self.a else 0

    @hp.setter  # type:ignore[no-redef]
    def hp(self, value: float) -> None:
        self.a = value * 2

    @hp.deleter  # type:ignore[no-redef]
    def hp(self):
        self.a = None

    @hp.expression  # type:ignore[no-redef]
    def hp(cls) -> sa.sql.ColumnElement[sa.Float]:
        return sa.func.abs(cls.a) / 2


obj = SomeModel()
obj.hp + 1

this will error with: test\files\hybrid.py:31: error: Unsupported operand types for + ("Callable[..., Any]" and "int") [operator]

This looks like a mypy bug, since removing @hp.setter and the other @hp definition it does better, but it still fails to get the float: test\files\hybrid.py:15: error: Unsupported operand types for / ("object" and "int") [operator]

I've opened a pr, but it does not work. Can you take a look?

CaselIT avatar Sep 01 '21 21:09 CaselIT