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