Search code examples
pythonpython-typingmypy

Passing StringIO object to TextIOWrapper object is giving mypy error


I wasn't getting any Mypy error with Mypy 0.812. But after upgrading Mypy to latest (1.14.1) I am getting below error. error: Argument 1 to "_redirect_stdout" has incompatible type "StringIO"; expected "TextIOWrapper[_WrappedBuffer]" [arg-type]

Please help me to understand

  • the error
  • whats changed in mypy that is raising the error and
  • the possible ways to resolve it. Thanks!
@contextlib.contextmanager
def _redirect_stdout(
    new_target: io.TextIOWrapper
) -> Generator[io.TextIOWrapper, None, None]:
    old_target = sys.stdout
    sys.stdout = new_target

    try:
        yield new_target
    finally:
        sys.stdout = old_target


target_stdout = io.StringIO()

with _redirect_stdout(target_stdout):
    print(['These', 'are', 'sample', 'strings.'])

Solution

  • Mypy 0.812 is ancient: It was released in February 2021. There have been many, many changes since then. This answer will thus not cover the "exact" change, if there is even such a thing.

    The type StringIO is not assignable to TextIOWrapper, as they are defined as different classes, and with different bases, to boot:

    # https://github.com/python/typeshed/blob/101287091c/stdlib/_io.pyi#L157
    class TextIOWrapper(TextIOBase, _TextIOBase, TextIO, Generic[_BufferT_co]):
        ...
    
    # https://github.com/python/typeshed/blob/101287091c/stdlib/_io.pyi#L191
    class StringIO(TextIOBase, _TextIOBase, TextIO):
        ...
    

    They do, however, share a few base classes. _TextIOBase is private, as signalled by its name, so it's out of the question. That leaves TextIOBase and TextIO:

    (playgrounds: Mypy, Pyright)

    from io import StringIO, TextIOBase
    from typing import TextIO
    
    def a(v: TextIOBase) -> None: ...
    def b(v: TextIO) -> None: ...
    
    a(StringIO())  # fine
    b(StringIO())  # fine
    

    sys.stdout has the type TextIO, so the best choice overall is TextIO:

    # https://github.com/python/typeshed/blob/101287091c/stdlib/sys/__init__.pyi#L67
    stdout: TextIO | MaybeNone