-
Notifications
You must be signed in to change notification settings - Fork 100
hybrid_property support #98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
If anyone's reading this and is curious if there's a quick way to get an easy (but janky) fix here, you can do something like this to basically sub in mypy's type checking for
This also allows mypy to type expression functions, which is nice. |
I tried keeping the name and mypy doesn't recognise it as an alias for property: if TYPE_CHECKING:
hybrid_property = property # type: ignore[misc,assignment] Maybe it's because of the errors raised:
|
@jace Make sure that you're not re-assigning The following should work:
The following won't:
|
Thank you. This does indeed work. The |
im playing with descriptors today and shouldn't this be the general approach for a descriptor? from typing import Any, Union, overload
class Descriptor:
def some_method(self) -> Any:
pass
@overload
def __get__(self, instance: None, other: Any) -> "Descriptor": ...
@overload
def __get__(self, instance: object, other: Any) -> "int": ...
def __get__(self, instance: object, other: Any) -> "Any":
if instance is None:
return self
else:
return 5
class Foo:
value = Descriptor()
# class level access, you get "Descriptor"
# (for hybrid this would be ColumnElement)
Foo.value.some_method()
f1 = Foo()
# instance level, you get a value type
val : int = f1.value
that is, the hybrid_property separation of "expression" and "instance" is made apparent by the type of "instance" passed to the descriptor protocol. can someone comment on this approach? considering this is what I would try to adapt to hybrid properties, which are just descriptors with pluggable class/instance level functions. |
Here's POC 1 for this approach with hybrids: 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 |
Here we go, this is just about the whole thing, how about this 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], Union[_T, ColumnElement[_T]]],
expr: Optional[Callable[[Any], ColumnElement[_T]]] = None,
):
self.fget = fget
if expr is None:
self.expr = fget
else:
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:
# seems like "use the name twice" pattern isn't accepted by
# mypy, so use two separate names?
@hybrid_property
def _my_thing_inst(self) -> int:
return 5
@_my_thing_inst.expression
def my_thing(cls) -> "ColumnElement[int]":
return column("five", Integer)
mc = MyClass()
int_value: int = mc.my_thing
expr: ColumnElement[int] = MyClass.my_thing |
Hi, I saw the thread a this topic here, but can't haven't seen any TODOs in this repo for it, so I'll go ahead and make one.
We need support to infer types for
hybrid_property
similar toproperty
, returning different types based on whether it's being accessed on a class or an instance, and support for the@<property>.expression
decorator.The text was updated successfully, but these errors were encountered: