0

I'd like to conditionally use with with an io.TextIOWrapper either directly from open() or from one yielded by another context manager. For example:

import contextlib
import io
import typing


def condition() -> bool:
    return True


@contextlib.contextmanager
def foo() -> typing.Generator[io.TextIOWrapper, None, None]:
    with open("README.md", "rb") as f:
        yield io.TextIOWrapper(f)


with foo() if condition() else open("README.md") as f:
    for line in f:
        print(line, end="")

The above code runs without error, but running mypy on it reports:

foo.py:16: error: "object" has no attribute "__enter__"
foo.py:16: error: "object" has no attribute "__exit__"

mypy is satisfied if I use either subexpression in isolation (i.e., with foo() as f:), but the combination confuses it. I presume that there's some subtle difference in the typehints for foo()'s and open()'s return values that causes their common, inferred supertype to be object. Using typing.cast quells the complaint:

with (typing.cast(io.TextIOWrapper, foo()) if condition() else
      open("README.md")) as f:

Is there something else that I should be doing instead (e.g. by tweaking the typehint for the return value of foo or maybe by implementing foo in some other way)?

jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • [Is this helpful](https://stackoverflow.com/a/49736916/5168011)? – Guy Jul 14 '22 at 10:53
  • @Guy I've read that already, and I don't think it is (or maybe I'm too dense to understand which part would help my case). I'm already using a `Generator` annotation for the return type; I don't see how using `Iterator` would be any better. `typeshed` indicates that [`open` is hinted to return an `io.TextIOWrapper`](https://github.com/python/typeshed/blob/9645dae9250d397a2ae81c7cc7db9b2d0d64180a/stdlib/builtins.pyi#L1413) already (for at least one overload, anyway). – jamesdlin Jul 14 '22 at 11:12
  • [Simple workaround](https://mypy-play.net/?mypy=latest&python=3.10&gist=e2be52139988879b79baecbbf5b6e38b). I suppose the problem is that `io.TextIOWrapper` is a self-returning context manager, so `mypy` gets confused.You probably should report this as `mypy` issue. – STerliakov Jul 18 '22 at 14:28
  • I should have checked the mypy issue tracker first. This looks like https://github.com/python/mypy/issues/5512. – jamesdlin Jul 18 '22 at 19:07

0 Answers0