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][1] 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:
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
][2] from PyPI doesn't solve that. (And there are users that run the package on that version of Python).
This make my package dependent on [typing
][2] for 2.7/3.3/3.4 installs, requiring extra downloads and installs.
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]: https://github.com/python/typeshed [2]: https://pypi.python.org/pypi/typing
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.