python-zulip-api/tools/run-mypy
Rohitt Vashishtha e49ecfcd39 mypy: Support files without extensions for scripts in /tools.
This assumes that files passed in force_include without any extension are meant
to be run with mypy, and thus assumed to be python scripts.

Also, we ignore the most of the tools dir, except for the tools/deploy script
for which #349 adds type annotations.
2018-03-30 07:54:10 -04:00

179 lines
8.1 KiB
Python
Executable file

#!/usr/bin/env python
from __future__ import absolute_import
from __future__ import print_function
import os
import sys
import argparse
import subprocess
from collections import OrderedDict
from pathlib import PurePath
from server_lib import lister
from typing import cast, Dict, List
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
os.chdir(os.path.dirname(TOOLS_DIR))
sys.path.append(os.path.dirname(TOOLS_DIR))
exclude = [
# Excluded because it's third-party code.
"zulip/integrations/perforce/git_p4.py",
# Excluded because we don't want to require bot authors to
# fully annotate their bots.
"zulip_bots/zulip_bots/bots",
"zulip_bots/zulip_bots/bots_unmaintained",
# Excluded out of laziness:
"zulip_bots/zulip_bots/terminal.py",
"zulip_bots/zulip_bots/simple_lib.py",
"zulip_bots/zulip_bots/lib_tests.py",
"tools",
]
# These files will be included even if excluded by a rule above.
force_include = [
# Include bots that we migrate to mypy.
"zulip_bots/zulip_bots/bots/helloworld/helloworld.py",
"zulip_bots/zulip_bots/bots/helloworld/test_helloworld.py",
"zulip_bots/zulip_bots/bots/followup/followup.py",
"zulip_bots/zulip_bots/bots/followup/test_followup.py",
"zulip_bots/zulip_bots/bots/giphy/giphy.py",
"zulip_bots/zulip_bots/bots/giphy/test_giphy.py",
"zulip_bots/zulip_bots/bots/github_detail/github_detail.py",
"zulip_bots/zulip_bots/bots/github_detail/test_github_detail.py",
"zulip_bots/zulip_bots/bots/google_search/google_search.py",
"zulip_bots/zulip_bots/bots/google_search/test_google_search.py",
"zulip_bots/zulip_bots/bots/help/help.py",
"zulip_bots/zulip_bots/bots/help/test_help.py",
"zulip_bots/zulip_bots/bots/incrementor/incrementor.py",
"zulip_bots/zulip_bots/bots/incrementor/test_incrementor.py",
"zulip_bots/zulip_bots/bots/link_shortener/link_shortener.py",
"zulip_bots/zulip_bots/bots/link_shortener/test_link_shortener.py",
"zulip_bots/zulip_bots/bots/virtual_fs/virtual_fs.py",
"zulip_bots/zulip_bots/bots/virtual_fs/test_virtual_fs.py",
"zulip_bots/zulip_bots/bots/weather/test_weather.py",
"zulip_bots/zulip_bots/bots/weather/weather.py",
"zulip_bots/zulip_bots/bots/youtube/youtube.py",
"zulip_bots/zulip_bots/bots/youtube/test_youtube.py",
"zulip_bots/zulip_bots/bots/converter/converter.py",
"zulip_bots/zulip_bots/bots/converter/test_converter.py",
"zulip_bots/zulip_bots/bots/define/define.py",
"zulip_bots/zulip_bots/bots/define/test_define.py",
"zulip_bots/zulip_bots/bots/encrypt/encrypt.py",
"zulip_bots/zulip_bots/bots/encrypt/test_encrypt.py",
"zulip_bots/zulip_bots/bots/chess/chess.py",
"zulip_bots/zulip_bots/bots/chess/test_chess.py",
"zulip_bots/zulip_bots/bots/xkcd/xkcd.py",
"zulip_bots/zulip_bots/bots/xkcd/test_xkcd.py",
"zulip_bots/zulip_bots/bots/witai/witai.py",
"zulip_bots/zulip_bots/bots/witai/test_witai.py",
"zulip_bots/zulip_bots/bots/wikipedia/wikipedia.py",
"zulip_bots/zulip_bots/bots/wikipedia/test_wikipedia.py",
"zulip_bots/zulip_bots/bots/yoda/yoda.py",
"zulip_bots/zulip_bots/bots/yoda/test_yoda.py",
"zulip_bots/zulip_bots/bots/dialogflow/dialogflow.py",
"zulip_bots/zulip_bots/bots/dialogflow/test_dialogflow.py",
"zulip_bots/zulip_bots/bots/mention/mention.py",
"zulip_bots/zulip_bots/bots/mention/test_mention.py",
"zulip_bots/zulip_bots/bots/baremetrics/baremetrics.py",
"zulip_bots/zulip_bots/bots/baremetrics/test_baremetrics.py",
"zulip_bots/zulip_bots/bots/salesforce/salesforce.py",
"zulip_bots/zulip_bots/bots/salesforce/test_salesforce.py",
"zulip_bots/zulip_bots/bots/idonethis/idonethis.py",
"zulip_bots/zulip_bots/bots/idonethis/test_idonethis.py",
"zulip_bots/zulip_bots/bots/connect_four/connect_four.py",
"zulip_bots/zulip_bots/bots/connect_four/test_connect_four.py",
"zulip_bots/zulip_bots/bots/tictactoe/tictactoe.py",
"zulip_bots/zulip_bots/bots/tictactoe/test_tictactoe.py",
"zulip_bots/zulip_bots/bots/game_handler_bot/game_handler_bot.py",
"zulip_bots/zulip_bots/bots/game_handler_bot/test_game_handler_bot.py",
"zulip_bots/zulip_bots/bots/trello/trello.py",
"zulip_bots/zulip_bots/bots/trello/test_trello.py",
"zulip_bots/zulip_bots/bots/susi/susi.py",
"zulip_bots/zulip_bots/bots/susi/test_susi.py",
"zulip_bots/zulip_bots/bots/front/front.py",
"zulip_bots/zulip_bots/bots/front/test_front.py",
"tools/deploy"
]
parser = argparse.ArgumentParser(description="Run mypy on files tracked by git.")
parser.add_argument('targets', nargs='*', default=[],
help="""files and directories to include in the result.
If this is not specified, the current directory is used""")
parser.add_argument('-m', '--modified', action='store_true', default=False, help='list only modified files')
parser.add_argument('-a', '--all', dest='all', action='store_true', default=False,
help="""run mypy on all python files, ignoring the exclude list.
This is useful if you have to find out which files fail mypy check.""")
parser.add_argument('--no-disallow-untyped-defs', dest='disallow_untyped_defs', action='store_false', default=True,
help="""Don't throw errors when functions are not annotated""")
parser.add_argument('--scripts-only', dest='scripts_only', action='store_true', default=False,
help="""Only type check extensionless python scripts""")
parser.add_argument('--no-strict-optional', dest='strict_optional', action='store_false', default=True,
help="""Don't use the --strict-optional flag with mypy""")
parser.add_argument('--warn-unused-ignores', dest='warn_unused_ignores', action='store_true', default=False,
help="""Use the --warn-unused-ignores flag with mypy""")
parser.add_argument('--no-ignore-missing-imports', dest='ignore_missing_imports', action='store_false', default=True,
help="""Don't use the --ignore-missing-imports flag with mypy""")
parser.add_argument('--quick', action='store_true', default=False,
help="""Use the --quick flag with mypy""")
args = parser.parse_args()
if args.all:
exclude = []
# find all non-excluded files in current directory
files_dict = cast(Dict[str, List[str]],
lister.list_files(targets=args.targets, ftypes=['py', 'pyi'],
use_shebang=True, modified_only=args.modified,
exclude = exclude + ['stubs'], group_by_ftype=True,
extless_only=args.scripts_only))
for inpath in force_include:
try:
ext = os.path.splitext(inpath)[1].split('.')[1]
except IndexError:
ext = 'py' # type: str
files_dict[ext].append(inpath)
pyi_files = set(files_dict['pyi'])
python_files = [fpath for fpath in files_dict['py']
if not fpath.endswith('.py') or fpath + 'i' not in pyi_files]
repo_python_files = OrderedDict([('zulip', []), ('zulip_bots', []), ('zulip_botserver', []), ('tools', [])])
for file_path in python_files:
repo = PurePath(file_path).parts[0]
if repo in repo_python_files:
repo_python_files[repo].append(file_path)
mypy_command = "mypy"
extra_args = ["--check-untyped-defs",
"--follow-imports=silent",
"--scripts-are-modules",
"--disallow-any-generics",
"-i"]
if args.disallow_untyped_defs:
extra_args.append("--disallow-untyped-defs")
if args.warn_unused_ignores:
extra_args.append("--warn-unused-ignores")
if args.strict_optional:
extra_args.append("--strict-optional")
if args.ignore_missing_imports:
extra_args.append("--ignore-missing-imports")
if args.quick:
extra_args.append("--quick")
# run mypy
status = 0
for repo, python_files in repo_python_files.items():
print("Running mypy for `{}`.".format(repo), flush=True)
if python_files:
result = subprocess.call([mypy_command] + extra_args + python_files)
if result != 0:
status = result
else:
print("There are no files to run mypy on.")
sys.exit(status)