diff --git a/tools/provision b/tools/provision index 6f1d7546f..6ec9015b3 100755 --- a/tools/provision +++ b/tools/provision @@ -16,7 +16,7 @@ end_format = "\033[0m" bold = "\033[1m" -def main(): +def main() -> None: usage = """./tools/provision Creates a Python virtualenv. Its Python version is equal to @@ -94,7 +94,7 @@ the Python version this command is executed with.""" # In order to install all required packages for the venv, `pip` needs to be executed by # the venv's Python interpreter. `--prefix venv_dir` ensures that all modules are installed # in the right place. - def install_dependencies(requirements_filename): + def install_dependencies(requirements_filename: str) -> None: pip_path = os.path.join(venv_dir, venv_exec_dir, "pip") # We first install a modern version of pip that supports --prefix subprocess.call([pip_path, "install", "pip>=10"]) diff --git a/tools/run-mypy b/tools/run-mypy index a2733a197..d197b5737 100755 --- a/tools/run-mypy +++ b/tools/run-mypy @@ -4,9 +4,8 @@ import argparse import os import subprocess import sys -from collections import OrderedDict from pathlib import PurePath -from typing import Dict, List, cast +from typing import List, OrderedDict from zulint import lister @@ -23,13 +22,7 @@ exclude = [ "zulip_bots/zulip_bots/bots", "zulip_bots/zulip_bots/bots_unmaintained", # Excluded out of laziness: - "zulip_bots/zulip_bots/simple_lib.py", "zulip_bots/zulip_bots/tests/test_lib.py", - # Excluded because this is a self-contained script - # we ask our users to download and run directly and - # py2 and py3 compatibility is required. - "zulip/integrations/trello/zulip_trello.py", - "tools", ] # These files will be included even if excluded by a rule above. @@ -97,8 +90,6 @@ force_include = [ "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/custom_check.py", - "tools/deploy", ] parser = argparse.ArgumentParser(description="Run mypy on files tracked by git.") @@ -158,17 +149,14 @@ 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, - ), +files_dict = 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: @@ -183,7 +171,7 @@ 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( +repo_python_files = OrderedDict[str, List[str]]( [("zulip", []), ("zulip_bots", []), ("zulip_botserver", []), ("tools", [])] ) for file_path in python_files: diff --git a/zulip/integrations/google/get-google-credentials b/zulip/integrations/google/get-google-credentials index e598f0ccb..5bd45a3a5 100644 --- a/zulip/integrations/google/get-google-credentials +++ b/zulip/integrations/google/get-google-credentials @@ -1,18 +1,11 @@ #!/usr/bin/env python3 +import argparse import os -from typing import Optional from oauth2client import client, tools from oauth2client.file import Storage -try: - import argparse - - flags: Optional[argparse.Namespace] = argparse.ArgumentParser( - parents=[tools.argparser] - ).parse_args() -except ImportError: - flags = None +flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() # If modifying these scopes, delete your previously saved credentials # at zulip/bots/gcal/ @@ -43,13 +36,10 @@ def get_credentials() -> client.Credentials: if not credentials or credentials.invalid: flow = client.flow_from_clientsecrets(os.path.join(HOME_DIR, CLIENT_SECRET_FILE), SCOPES) flow.user_agent = APPLICATION_NAME - if flags: - # This attempts to open an authorization page in the default web browser, and asks the user - # to grant the bot access to their data. If the user grants permission, the run_flow() - # function returns new credentials. - credentials = tools.run_flow(flow, store, flags) - else: # Needed only for compatibility with Python 2.6 - credentials = tools.run(flow, store) + # This attempts to open an authorization page in the default web browser, and asks the user + # to grant the bot access to their data. If the user grants permission, the run_flow() + # function returns new credentials. + credentials = tools.run_flow(flow, store, flags) print("Storing credentials to " + credential_path) diff --git a/zulip/integrations/trello/zulip_trello.py b/zulip/integrations/trello/zulip_trello.py index eb1e7ddee..e7a58de0c 100755 --- a/zulip/integrations/trello/zulip_trello.py +++ b/zulip/integrations/trello/zulip_trello.py @@ -14,7 +14,7 @@ sys.exit(1) -def get_model_id(options): +def get_model_id(options: argparse.Namespace) -> str: """get_model_id Get Model Id from Trello API @@ -43,7 +43,7 @@ def get_model_id(options): return board_info_json["id"] -def get_webhook_id(options, id_model): +def get_webhook_id(options: argparse.Namespace, id_model: str) -> str: """get_webhook_id Get webhook id from Trello API @@ -78,7 +78,7 @@ def get_webhook_id(options, id_model): return webhook_info_json["id"] -def create_webhook(options): +def create_webhook(options: argparse.Namespace) -> None: """create_webhook Create Trello webhook @@ -107,7 +107,7 @@ def create_webhook(options): ) -def main(): +def main() -> None: description = """ zulip_trello.py is a handy little script that allows Zulip users to quickly set up a Trello webhook. diff --git a/zulip_bots/zulip_bots/simple_lib.py b/zulip_bots/zulip_bots/simple_lib.py index 24c2c7606..ed82f2b83 100644 --- a/zulip_bots/zulip_bots/simple_lib.py +++ b/zulip_bots/zulip_bots/simple_lib.py @@ -1,61 +1,62 @@ import configparser import sys +from typing import IO, Any, Dict, Optional from uuid import uuid4 from zulip_bots.lib import BotIdentity class SimpleStorage: - def __init__(self): - self.data = dict() + def __init__(self) -> None: + self.data: Dict[str, Any] = dict() - def contains(self, key): + def contains(self, key: str) -> bool: return key in self.data - def put(self, key, value): + def put(self, key: str, value: Any) -> None: self.data[key] = value - def get(self, key): + def get(self, key: str) -> Any: return self.data[key] class MockMessageServer: # This class is needed for the incrementor bot, which # actually updates messages! - def __init__(self): + def __init__(self) -> None: self.message_id = 0 - self.messages = dict() + self.messages: Dict[int, Dict[str, Any]] = dict() - def send(self, message): + def send(self, message: Dict[str, Any]) -> Dict[str, Any]: self.message_id += 1 message["id"] = self.message_id self.messages[self.message_id] = message return message - def add_reaction(self, reaction_data): + def add_reaction(self, reaction_data: object) -> Dict[str, Any]: return dict(result="success", msg="", uri=f"https://server/messages/{uuid4()}/reactions") - def update(self, message): + def update(self, message: Dict[str, Any]) -> None: self.messages[message["message_id"]] = message - def upload_file(self, file): + def upload_file(self, file: IO[Any]) -> Dict[str, Any]: return dict(result="success", msg="", uri=f"https://server/user_uploads/{uuid4()}") class TerminalBotHandler: - def __init__(self, bot_config_file, message_server): + def __init__(self, bot_config_file: Optional[str], message_server: MockMessageServer) -> None: self.bot_config_file = bot_config_file self._storage = SimpleStorage() self.message_server = message_server @property - def storage(self): + def storage(self) -> SimpleStorage: return self._storage - def identity(self): + def identity(self) -> BotIdentity: return BotIdentity("bot name", "bot-email@domain") - def react(self, message, emoji_name): + def react(self, message: Dict[str, Any], emoji_name: str) -> Dict[str, Any]: """ Mock adding an emoji reaction and print it in the terminal. """ @@ -64,7 +65,7 @@ def react(self, message, emoji_name): dict(message_id=message["id"], emoji_name=emoji_name, reaction_type="unicode_emoji") ) - def send_message(self, message): + def send_message(self, message: Dict[str, Any]) -> Dict[str, Any]: """ Print the message sent in the terminal and store it in a mock message server. """ @@ -90,7 +91,7 @@ def send_message(self, message): # id to the message instead of actually displaying it. return self.message_server.send(message) - def send_reply(self, message, response): + def send_reply(self, message: Dict[str, Any], response: str) -> Dict[str, Any]: """ Print the reply message in the terminal and store it in a mock message server. """ @@ -102,7 +103,7 @@ def send_reply(self, message, response): response_message = dict(content=response) return self.message_server.send(response_message) - def update_message(self, message): + def update_message(self, message: Dict[str, Any]) -> None: """ Update a previously sent message and print the result in the terminal. Throw an IndexError if the message id is invalid. @@ -117,14 +118,14 @@ def update_message(self, message): ) ) - def upload_file_from_path(self, file_path): + def upload_file_from_path(self, file_path: str) -> Dict[str, Any]: with open(file_path) as file: return self.upload_file(file) - def upload_file(self, file): + def upload_file(self, file: IO[Any]) -> Dict[str, Any]: return self.message_server.upload_file(file) - def get_config_info(self, bot_name, optional=False): + def get_config_info(self, bot_name: str, optional: bool = False) -> Dict[str, Any]: if self.bot_config_file is None: if optional: return dict()