Home > Net >  How does one comply with the mypy type 'SupportsWrite[str]'?
How does one comply with the mypy type 'SupportsWrite[str]'?

Time:09-24

I have a 'smart' open function that opens a variety of files and returns an IO-ish object type:

def sopen(anything_at_all: str, mode: str) -> FileIO:
   ...

And I use it in a print statement like:

with sopen('footxt.gz', mode = 'w ') as fout:
    print("hello, world!", file=fout)

Then, when analyzing this code with mypy 0.812, I get the following mystery error:

Argument "fout" to "print" has incompatible type "FileIO"; expected "Optional[SupportsWrite[str]]"

Ok great: SupportsWrite is definitely better than FileIO, only one problem: when I adapt my code to support write using _typeshed.SupportsWrite, nothing gets better...

def sopen(anything_at_all: str, mode: str) \
  -> Union[SupportsWrite[str],SupportsRead[str]]:
  ...

mypy wants exactly Optional[SupportsWrite]:

 Argument "fout" to "print" has incompatible type "Union[SupportsWrite[str], SupportsRead[str]]"; expected "Optional[SupportsWrite[str]]"

Next I try casting, and creating some sort of type-casting enforcement, but in the middle of trying out my caster in the interpreter to see what errors fall out when:

>>> from _typeshed import SupportsRead, SupportsWrite
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named '_typeshed'

And now the fundamental problem is: how does one comply, in this situation, with mypy's wishes?

CodePudding user response:

TL;DR Use typing.IO instead of FileIO. typing.IO supports all the return types that the built-in open might return.


print itself annotates its file argument as Optional[SupportsWrite[str]], so mypy is correct.

To fix the missing _typeshed module error (also correct, it is only available when type checking, not when the interpreter is executing code) you can use the if TYPE_CHECKING1 trick and then use string annotations.

The below almost satisfies mypy:

from typing import Optional, TYPE_CHECKING

if TYPE_CHECKING:
    from _typeshed import SupportsWrite

def sopen(anything_at_all: str, mode: str) -> 'Optional[SupportsWrite[str]]':
   ...

with sopen('footxt.gz', mode = 'w ') as fout:
    print("hello, world!", file=fout)

Feeding this to mypy results with

test.py:9: error: Item "SupportsWrite[str]" of "Optional[SupportsWrite[str]]" has no attribute "__enter__"
test.py:9: error: Item "None" of "Optional[SupportsWrite[str]]" has no attribute "__enter__"
test.py:9: error: Item "SupportsWrite[str]" of "Optional[SupportsWrite[str]]" has no attribute "__exit__"
test.py:9: error: Item "None" of "Optional[SupportsWrite[str]]" has no attribute "__exit__"

Enters typing.IO.

Instead of messing with SupportsWrite directly, you can simply use typing.IO (which also happens to match open's return types). The following fully satisfies mypy:

from typing import IO

def sopen(anything_at_all: str, mode: str) -> IO:
   ...

with sopen('footxt.gz', mode = 'w ') as fout:
    print("hello, world!", file=fout)

1 TYPE_CHECKING is a constant which is False by default, and is only being set to True by mypy and/or other type analyzing tools.

  • Related