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)?