asyncstdlib icon indicating copy to clipboard operation
asyncstdlib copied to clipboard

pyright: Failed to call method "__get__" for descriptor class "LRUAsyncCallable..."

Open Luffbee opened this issue 1 year ago • 2 comments

The following code can run, but pyright will report errors. These errors are annoying, it would be great if they could be eliminated.

Thanks for this great library!

import asyncio

import asyncstdlib as astd


class Test:
    @astd.cache
    async def foo(self, n: int) -> int:
        return n

    async def bar(self) -> int:
        return await self.foo(1)

if __name__ == "__main__":
    a = Test()
    with asyncio.Runner() as r:
        print(r.run(a.bar()))
        print(r.run(a.foo(2)))
$ python aaa.py 
1
2

$ pyright aaa.py 
/liuyifan/aaa.py
  /liuyifan/aaa.py:11:27 - error: Cannot access member "foo" for type "Test*"
    Failed to call method "__get__" for descriptor class "LRUAsyncCallable[(self: Self@Test, n: int) -> Coroutine[Any, Any, int]]" (reportAttributeAccessIssue)
  /liuyifan/aaa.py:17:23 - error: Cannot access member "foo" for type "Test"
    Failed to call method "__get__" for descriptor class "LRUAsyncCallable[(self: Test, n: int) -> Coroutine[Any, Any, int]]" (reportAttributeAccessIssue)
2 errors, 0 warnings, 0 information 

$ python --version
Python 3.12.1

$ pyright --version
pyright 1.1.348

Luffbee avatar Jan 24 '24 07:01 Luffbee

Thanks for the report! I can confirm that I can reproduce this issue, but I still have to figure out what exactly PyRight is complaining about here.

maxfischer2781 avatar Jan 25 '24 12:01 maxfischer2781

This is going to take a bit longer. I've made some progress on understand __get__ to the point that MyPy now has full parameter support for methods (#129) - but PyRight still chokes. :/

I might have to build an MRE and ask the PyRight maintainers just what the thing complains about. If anyone has capacities to handle this earlier than me, feel free to move ahead.

maxfischer2781 avatar Mar 03 '24 10:03 maxfischer2781

I seem to have tracked this down to an issue with capturing the -> Awaitable[R] part of the wrapped callable; a synchronous setup using -> R seems to work.

My current MRE for the descriptor:

AC = TypeVar("AC", bound=Callable[..., Awaitable[Any]])
R = TypeVar("R")
P = ParamSpec("P")
S = TypeVar("S")


class ADescr(Generic[AC]):
    __call__: AC

    def __init__(self, callable: AC): ...

    @overload
    def __get__(
        self: ADescr[AC], instance: None, owner: type | None = ...
    ) -> ADescr[AC]: ...
    @overload
    def __get__(
        self: ADescr[Callable[Concatenate[S, P], Awaitable[R]]],
        instance: S,
        owner: type | None = ...,
    ) -> Callable[P, Awaitable[R]]: ...

    def __get__(self, instance: object | None, owner: type | None = None) -> Any: ...

Which fails for a simple testcase:

class Test:
    @ADescr
    async def foo(self, a: int) -> int:
        return a

    async def access(self):
        return await self.foo(12)
        # Cannot access member "foo" for type "Test*"
        # Failed to call method "__get__" for descriptor class "ADescr[(self: Self@Test, a: int) -> Coroutine[Any, Any, int]]" Pylance[reportAttributeAccessIssue]

Notably, MyPy accepts this and infers everything correctly.

maxfischer2781 avatar Mar 18 '24 06:03 maxfischer2781

I've just reported this as https://github.com/microsoft/pyright/issues/7533.

maxfischer2781 avatar Mar 21 '24 06:03 maxfischer2781

The solution suggested in the PyRight issue doesn't work for the practical case (AC must be invariant since it is also used in __call__). So the workaround is going to stay.

maxfischer2781 avatar Mar 23 '24 09:03 maxfischer2781