adding type information without dependency on typing module

1.2k views Asked by At

I have been adding type information to my package's .py files in order to support running mypy against the package. Among other things allows to generate typeshed information for this, third party, package.

Since my package has to be Python 2.7 compatible, I use comments for the type information:

def __init__(self, s):
    # type: (Text) -> None

but in order to run mypy this requires me to import typing:

from typing import Text, IO, BinaryIO, Union

this causes two problems:

  1. This will not work on Python 3.5.0 and 3.5.1 as it has a module typing but that doesn't include Text. Installing typing from PyPI doesn't solve that. (And there are users that run the package on that version of Python).

  2. This make my package dependent on typing for 2.7/3.3/3.4 installs, requiring extra downloads and installs.

  3. I have my own Union types defined:

    StreamType = Union[BinaryIO, IO[str], StringIO]
    StreamTextType = Union[Text, StreamType]
    

    the code for this would have to be conditionally executed depending on typing being available or not.

For the first problem, since I do not run mypy under Python 3.5.0/1, I can do something like:

import sys
if sys.version_info < (3, 5, 0) and sys.version_info >= (3, 5, 2):
    from typing import Text, IO, BinaryIO, Union

but that doesn't solve the second issue.

Commenting out the import, like the type information that is in comments,

# from typing import Text, IO, BinaryIO, Union

will cause mypy to throw an error Name 'Text' is not defined.

The third issue can be solve by using a try-except (ugly, and maybe also inefficient) or e.g. by testing against an environment variable (which could also be used to solve the first problem).

Is there an environment variable set when running mypy, that I can test against, so that the import statement is only executed when running mypy? Testing against an environment variable would also allow me to put the definition of my own types within that "guarded".

Or some other solution?

1

There are 1 answers

0
Anthon On BEST ANSWER

The only environment variable associated with mypy is MYPYPATH and that is read by the package's code, and not set by it. Although MYPYPATH might be set (especially when generating typeshed information, to provide the "other" type info), there is no guarantee that it is.

You cannot comment out the import statement, but you can put it in a block that never executes:

if False:  # MYPY
    from typing import Text, IO, BinaryIO, Union

this has the advantage that you don't have to import os to get at the environment variable (and/or sys to get at the version_info) if you have no other need for that in your specific Python file.

Your type definitions should be specified like that as well, and can occur anywhere after all of the used types have been imported or defined:

# import or define StringIO

if False:  # MYPY
    StreamType = Union[BinaryIO, IO[str], StringIO]
    StreamTextType = Union[Text, StreamType]

If the above is in mytypes.py, any other source file in your package, using StreamTypeText in any of the type definitions, should do:

if False:  # MYPY
    from typing import Text, IO, BinaryIO, Union
    from .mytypes StreamType

The above will satisfy mypy, so it will not throw an error on Text not being defined. It will also work on 3.5.0/1 and obliviates the need for making your package dependent on typing

You would probably still have to install typing if you were to run mypy in a Python 2.7 environment, but your normal package's users are not affected by that.

Note that I added a comment # MYPY after the if of each block. Searching files for from typing is easy, but the block with StreamType would otherwise not so easily be found, in case mypy changes its behaviour and your code needs adapting.