Skip to content
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

Error with Protocols and AsyncContextManager #8276

Open
junqed opened this issue Jan 13, 2020 · 10 comments
Open

Error with Protocols and AsyncContextManager #8276

junqed opened this issue Jan 13, 2020 · 10 comments

Comments

@junqed
Copy link

junqed commented Jan 13, 2020

  • Are you reporting a bug, or opening a feature request?
    A bug

  • Please insert below the code you are checking with mypy,

from __future__ import annotations

import asyncio
import typing as t
from contextlib import asynccontextmanager

import typing_extensions as te


class LockProto(te.Protocol):
    async def with_lock(self) -> t.AsyncContextManager[str]: ...


class Consumer:
    def __init__(self, locker: LockProto) -> None:
        self.locker = locker

    def run(self) -> None:
        async with self.locker.with_lock() as name:
            print(name)


class SockLock:
    @asynccontextmanager
    async def with_lock(self) -> t.AsyncGenerator[str, None]:
        yield 'some-string'


async def main() -> None:
    cons = Consumer(SockLock())
    await cons.run()


if __name__ == '__main__':
    asyncio.run(main())
  • What is the actual behavior/output?
bug.py:18: error: "Coroutine[Any, Any, AsyncContextManager[str]]" has no attribute "__enter__"
bug.py:18: error: "Coroutine[Any, Any, AsyncContextManager[str]]" has no attribute "__exit__"
bug.py:29: error: Argument 1 to "Consumer" has incompatible type "SockLock"; expected "LockProto"
bug.py:29: note: Following member(s) of "SockLock" have conflicts:
bug.py:29: note:     Expected:
bug.py:29: note:         def with_lock(self) -> Coroutine[Any, Any, AsyncContextManager[str]]
bug.py:29: note:     Got:
bug.py:29: note:         def with_lock(*Any, **Any) -> AsyncContextManager[Any]
  • What is the behavior/output you expect?
    No errors

  • What are the versions of mypy and Python you are using?
    0.761

  • What are the mypy flags you are using? (For example --strict-optional)
    No flags, just mypy bug.py

@Michael0x2a
Copy link
Collaborator

Not sure about the error on line 29, but the error from line 18 at least seems to be a legitimate one -- you need to use async with (and consequently make your run function an async def). Otherwise you get the following runtime error:

Traceback (most recent call last):
  File "test.py", line 34, in <module>
    main()
  File "test.py", line 30, in main
    cons.run()
  File "test.py", line 18, in run
    with self.locker.with_lock() as name:
AttributeError: __enter__

@junqed
Copy link
Author

junqed commented Jan 13, 2020

Not sure about the error on line 29, but the error from line 18 at least seems to be a legitimate one -- you need to use async with (and consequently make your run function an async def). Otherwise you get the following runtime error:

Sorry, I forgot about it, fixed in my example

@JukkaL
Copy link
Collaborator

JukkaL commented Jan 14, 2020

It looks like the type annotations were incorrect and the errors were legitimate. This version passes without errors (I'm not 100% sure if the annotations are correct here, though):

import asyncio
from contextlib import asynccontextmanager

from typing import AsyncGenerator, AsyncContextManager
from typing_extensions import Protocol


class LockProto(Protocol):
    def with_lock(self) -> AsyncContextManager[str]: ...


class Consumer:
    def __init__(self, locker: LockProto) -> None:
        self.locker = locker

    async def run(self) -> None:
        async with self.locker.with_lock() as name:
            print(name)


class SockLock:
    @asynccontextmanager
    async def with_lock(self) -> AsyncGenerator[str, None]:
        yield 'some-string'


async def main() -> None:
    cons = Consumer(SockLock())
    await cons.run()


if __name__ == '__main__':
    asyncio.run(main())

Changes I made:

  • Made with_lock a normal def
  • Made run an async def

I wonder if some error messages could be improved here?

@junqed
Copy link
Author

junqed commented Jan 14, 2020

Thanks, moving away async from the protocol fixed the error. But why? Do I understand correctly that the decorator asynccontextmanager changes the function signature implicitly and mypy understands it? Is it a good idea to add such cases to the docs?

@JukkaL
Copy link
Collaborator

JukkaL commented Jan 14, 2020

Yeah, asynccontextmanager changes the signature and mypy understands (to a certain extent, at least). Mentioning this in the documentation may be a good idea, if we find a good place to put it. contextmanager is another typical example and much more common, so it should be documented as well (or at first).

@junqed
Copy link
Author

junqed commented Jan 14, 2020

https://mypy.readthedocs.io/en/stable/kinds_of_types.html is it a good place for such kind of examples? Or at least just to put them in the examples https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html#miscellaneous

@JukkaL
Copy link
Collaborator

JukkaL commented Jan 15, 2020

I'd say that appending to https://mypy.readthedocs.io/en/stable/kinds_of_types.html is a good place for contextmanager. asynccontextmanager could be documented in https://mypy.readthedocs.io/en/stable/more_types.html#typing-async-await. These don't feel important enough to be included in the cheat sheet.

@joybh98
Copy link
Contributor

joybh98 commented Mar 12, 2020

I'm taking this issue

@joybh98
Copy link
Contributor

joybh98 commented Mar 14, 2020

@JukkaL where should I add contextmanager in https://mypy.readthedocs.io/en/stable/kinds_of_types.html ?

@ethanhs
Copy link
Collaborator

ethanhs commented Mar 14, 2020

@joybhallaa I would create a new section called "Context Managers".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants