typing: Convert function type annotations to Python 3 style.

Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and two fixes for use-before-define
issues:

-    def set_zulip_client(self, zulipToJabberClient: ZulipToJabberBot) -> None:
+    def set_zulip_client(self, zulipToJabberClient: 'ZulipToJabberBot') -> None:

-def init_from_options(options: Any, client: Optional[str] = None) -> Client:
+def init_from_options(options: Any, client: Optional[str] = None) -> 'Client':

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
This commit is contained in:
Anders Kaseorg 2020-04-18 15:59:12 -07:00 committed by Tim Abbott
parent 7c5f73dce9
commit 5428c5f296
42 changed files with 311 additions and 577 deletions

View file

@ -12,8 +12,7 @@ EXCLUDED_FILES = [
'zulip/integrations/perforce/git_p4.py', 'zulip/integrations/perforce/git_p4.py',
] ]
def run(): def run() -> None:
# type: () -> None
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
add_default_linter_arguments(parser) add_default_linter_arguments(parser)
args = parser.parse_args() args = parser.parse_args()
@ -27,15 +26,13 @@ def run():
description="Static type checker for Python (config: mypy.ini)") description="Static type checker for Python (config: mypy.ini)")
@linter_config.lint @linter_config.lint
def custom_py(): def custom_py() -> int:
# type: () -> int
"""Runs custom checks for python files (config: tools/linter_lib/custom_check.py)""" """Runs custom checks for python files (config: tools/linter_lib/custom_check.py)"""
failed = python_rules.check(by_lang, verbose=args.verbose) failed = python_rules.check(by_lang, verbose=args.verbose)
return 1 if failed else 0 return 1 if failed else 0
@linter_config.lint @linter_config.lint
def custom_nonpy(): def custom_nonpy() -> int:
# type: () -> int
"""Runs custom checks for non-python files (config: tools/linter_lib/custom_check.py)""" """Runs custom checks for non-python files (config: tools/linter_lib/custom_check.py)"""
failed = False failed = False
for rule in non_py_rules: for rule in non_py_rules:
@ -43,8 +40,7 @@ def run():
return 1 if failed else 0 return 1 if failed else 0
@linter_config.lint @linter_config.lint
def pep8(): def pep8() -> int:
# type: () -> int
"""Standard Python style linter on 50% of files (config: tools/linter_lib/pep8.py)""" """Standard Python style linter on 50% of files (config: tools/linter_lib/pep8.py)"""
failed = check_pep8(by_lang['py']) failed = check_pep8(by_lang['py'])
return 1 if failed else 0 return 1 if failed else 0

View file

@ -8,11 +8,9 @@ from zulint.printer import print_err, colors
from typing import List from typing import List
def check_pep8(files): def check_pep8(files: List[str]) -> bool:
# type: (List[str]) -> bool
def run_pycodestyle(files, ignored_rules): def run_pycodestyle(files: List[str], ignored_rules: List[str]) -> bool:
# type: (List[str], List[str]) -> bool
failed = False failed = False
color = next(colors) color = next(colors)
pep8 = subprocess.Popen( pep8 = subprocess.Popen(

View file

@ -3,36 +3,30 @@
import subprocess import subprocess
import sys import sys
def exit(message): def exit(message: str) -> None:
# type: (str) -> None
print('PROBLEM!') print('PROBLEM!')
print(message) print(message)
sys.exit(1) sys.exit(1)
def run(command): def run(command: str) -> None:
# type: (str) -> None
print('\n>>> ' + command) print('\n>>> ' + command)
subprocess.check_call(command.split()) subprocess.check_call(command.split())
def check_output(command): def check_output(command: str) -> str:
# type: (str) -> str
return subprocess.check_output(command.split()).decode('ascii') return subprocess.check_output(command.split()).decode('ascii')
def get_git_branch(): def get_git_branch() -> str:
# type: () -> str
command = 'git rev-parse --abbrev-ref HEAD' command = 'git rev-parse --abbrev-ref HEAD'
output = check_output(command) output = check_output(command)
return output.strip() return output.strip()
def check_git_pristine(): def check_git_pristine() -> None:
# type: () -> None
command = 'git status --porcelain' command = 'git status --porcelain'
output = check_output(command) output = check_output(command)
if output.strip(): if output.strip():
exit('Git is not pristine:\n' + output) exit('Git is not pristine:\n' + output)
def ensure_on_clean_master(): def ensure_on_clean_master() -> None:
# type: () -> None
branch = get_git_branch() branch = get_git_branch()
if branch != 'master': if branch != 'master':
exit('You are still on a feature branch: %s' % (branch,)) exit('You are still on a feature branch: %s' % (branch,))
@ -40,8 +34,7 @@ def ensure_on_clean_master():
run('git fetch upstream master') run('git fetch upstream master')
run('git rebase upstream/master') run('git rebase upstream/master')
def create_pull_branch(pull_id): def create_pull_branch(pull_id: int) -> None:
# type: (int) -> None
run('git fetch upstream pull/%d/head' % (pull_id,)) run('git fetch upstream pull/%d/head' % (pull_id,))
run('git checkout -B review-%s FETCH_HEAD' % (pull_id,)) run('git checkout -B review-%s FETCH_HEAD' % (pull_id,))
run('git rebase upstream/master') run('git rebase upstream/master')
@ -53,8 +46,7 @@ def create_pull_branch(pull_id):
print(subprocess.check_output(['git', 'log', 'HEAD~..', print(subprocess.check_output(['git', 'log', 'HEAD~..',
'--pretty=format:Author: %an'])) '--pretty=format:Author: %an']))
def review_pr(): def review_pr() -> None:
# type: () -> None
try: try:
pull_id = int(sys.argv[1]) pull_id = int(sys.argv[1])
except Exception: except Exception:

View file

@ -9,9 +9,8 @@ from typing import Any, Dict
class IRCBot(irc.bot.SingleServerIRCBot): class IRCBot(irc.bot.SingleServerIRCBot):
reactor_class = AioReactor reactor_class = AioReactor
def __init__(self, zulip_client, stream, topic, channel, def __init__(self, zulip_client: Any, stream: str, topic: str, channel: irc.bot.Channel,
nickname, server, nickserv_password='', port=6667): nickname: str, server: str, nickserv_password: str = '', port: int = 6667) -> None:
# type: (Any, str, str, irc.bot.Channel, str, str, str, int) -> None
self.channel = channel # type: irc.bot.Channel self.channel = channel # type: irc.bot.Channel
self.zulip_client = zulip_client self.zulip_client = zulip_client
self.stream = stream self.stream = stream
@ -23,13 +22,11 @@ class IRCBot(irc.bot.SingleServerIRCBot):
# Initialize IRC bot after proper connection to Zulip server has been confirmed. # Initialize IRC bot after proper connection to Zulip server has been confirmed.
irc.bot.SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname) irc.bot.SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname)
def zulip_sender(self, sender_string): def zulip_sender(self, sender_string: str) -> str:
# type: (str) -> str
nick = sender_string.split("!")[0] nick = sender_string.split("!")[0]
return nick + "@" + self.IRC_DOMAIN return nick + "@" + self.IRC_DOMAIN
def connect(self, *args, **kwargs): def connect(self, *args: Any, **kwargs: Any) -> None:
# type: (*Any, **Any) -> None
# Taken from # Taken from
# https://github.com/jaraco/irc/blob/master/irc/client_aio.py, # https://github.com/jaraco/irc/blob/master/irc/client_aio.py,
# in particular the method of AioSimpleIRCClient # in particular the method of AioSimpleIRCClient
@ -38,8 +35,7 @@ class IRCBot(irc.bot.SingleServerIRCBot):
) )
print("Listening now. Please send an IRC message to verify operation") print("Listening now. Please send an IRC message to verify operation")
def check_subscription_or_die(self): def check_subscription_or_die(self) -> None:
# type: () -> None
resp = self.zulip_client.list_subscriptions() resp = self.zulip_client.list_subscriptions()
if resp["result"] != "success": if resp["result"] != "success":
print("ERROR: %s" % (resp["msg"],)) print("ERROR: %s" % (resp["msg"],))
@ -49,19 +45,16 @@ class IRCBot(irc.bot.SingleServerIRCBot):
print("The bot is not yet subscribed to stream '%s'. Please subscribe the bot to the stream first." % (self.stream,)) print("The bot is not yet subscribed to stream '%s'. Please subscribe the bot to the stream first." % (self.stream,))
exit(1) exit(1)
def on_nicknameinuse(self, c, e): def on_nicknameinuse(self, c: ServerConnection, e: Event) -> None:
# type: (ServerConnection, Event) -> None
c.nick(c.get_nickname().replace("_zulip", "__zulip")) c.nick(c.get_nickname().replace("_zulip", "__zulip"))
def on_welcome(self, c, e): def on_welcome(self, c: ServerConnection, e: Event) -> None:
# type: (ServerConnection, Event) -> None
if len(self.nickserv_password) > 0: if len(self.nickserv_password) > 0:
msg = 'identify %s' % (self.nickserv_password,) msg = 'identify %s' % (self.nickserv_password,)
c.privmsg('NickServ', msg) c.privmsg('NickServ', msg)
c.join(self.channel) c.join(self.channel)
def forward_to_irc(msg): def forward_to_irc(msg: Dict[str, Any]) -> None:
# type: (Dict[str, Any]) -> None
not_from_zulip_bot = msg["sender_email"] != self.zulip_client.email not_from_zulip_bot = msg["sender_email"] != self.zulip_client.email
if not not_from_zulip_bot: if not not_from_zulip_bot:
# Do not forward echo # Do not forward echo
@ -88,8 +81,7 @@ class IRCBot(irc.bot.SingleServerIRCBot):
z2i = mp.Process(target=self.zulip_client.call_on_each_message, args=(forward_to_irc,)) z2i = mp.Process(target=self.zulip_client.call_on_each_message, args=(forward_to_irc,))
z2i.start() z2i.start()
def on_privmsg(self, c, e): def on_privmsg(self, c: ServerConnection, e: Event) -> None:
# type: (ServerConnection, Event) -> None
content = e.arguments[0] content = e.arguments[0]
sender = self.zulip_sender(e.source) sender = self.zulip_sender(e.source)
if sender.endswith("_zulip@" + self.IRC_DOMAIN): if sender.endswith("_zulip@" + self.IRC_DOMAIN):
@ -103,8 +95,7 @@ class IRCBot(irc.bot.SingleServerIRCBot):
"content": content, "content": content,
})) }))
def on_pubmsg(self, c, e): def on_pubmsg(self, c: ServerConnection, e: Event) -> None:
# type: (ServerConnection, Event) -> None
content = e.arguments[0] content = e.arguments[0]
sender = self.zulip_sender(e.source) sender = self.zulip_sender(e.source)
if sender.endswith("_zulip@" + self.IRC_DOMAIN): if sender.endswith("_zulip@" + self.IRC_DOMAIN):
@ -119,12 +110,10 @@ class IRCBot(irc.bot.SingleServerIRCBot):
"content": "**{}**: {}".format(sender, content), "content": "**{}**: {}".format(sender, content),
})) }))
def on_dccmsg(self, c, e): def on_dccmsg(self, c: ServerConnection, e: Event) -> None:
# type: (ServerConnection, Event) -> None
c.privmsg("You said: " + e.arguments[0]) c.privmsg("You said: " + e.arguments[0])
def on_dccchat(self, c, e): def on_dccchat(self, c: ServerConnection, e: Event) -> None:
# type: (ServerConnection, Event) -> None
if len(e.arguments) != 2: if len(e.arguments) != 2:
return return
args = e.arguments[1].split() args = e.arguments[1].split()

View file

@ -34,8 +34,7 @@ class Bridge_FatalMatrixException(Exception):
class Bridge_ZulipFatalException(Exception): class Bridge_ZulipFatalException(Exception):
pass pass
def matrix_login(matrix_client, matrix_config): def matrix_login(matrix_client: Any, matrix_config: Dict[str, Any]) -> None:
# type: (Any, Dict[str, Any]) -> None
try: try:
matrix_client.login_with_password(matrix_config["username"], matrix_client.login_with_password(matrix_config["username"],
matrix_config["password"]) matrix_config["password"])
@ -47,8 +46,7 @@ def matrix_login(matrix_client, matrix_config):
except MissingSchema as exception: except MissingSchema as exception:
raise Bridge_FatalMatrixException("Bad URL format.") raise Bridge_FatalMatrixException("Bad URL format.")
def matrix_join_room(matrix_client, matrix_config): def matrix_join_room(matrix_client: Any, matrix_config: Dict[str, Any]) -> Any:
# type: (Any, Dict[str, Any]) -> Any
try: try:
room = matrix_client.join_room(matrix_config["room_id"]) room = matrix_client.join_room(matrix_config["room_id"])
return room return room
@ -58,15 +56,17 @@ def matrix_join_room(matrix_client, matrix_config):
else: else:
raise Bridge_FatalMatrixException("Couldn't find room.") raise Bridge_FatalMatrixException("Couldn't find room.")
def die(signal, frame): def die(signal: int, frame: FrameType) -> None:
# type: (int, FrameType) -> None
# We actually want to exit, so run os._exit (so as not to be caught and restarted) # We actually want to exit, so run os._exit (so as not to be caught and restarted)
os._exit(1) os._exit(1)
def matrix_to_zulip(zulip_client, zulip_config, matrix_config, no_noise): def matrix_to_zulip(
# type: (zulip.Client, Dict[str, Any], Dict[str, Any], bool) -> Callable[[Any, Dict[str, Any]], None] zulip_client: zulip.Client,
def _matrix_to_zulip(room, event): zulip_config: Dict[str, Any],
# type: (Any, Dict[str, Any]) -> None matrix_config: Dict[str, Any],
no_noise: bool,
) -> Callable[[Any, Dict[str, Any]], None]:
def _matrix_to_zulip(room: Any, event: Dict[str, Any]) -> None:
""" """
Matrix -> Zulip Matrix -> Zulip
""" """
@ -95,8 +95,7 @@ def matrix_to_zulip(zulip_client, zulip_config, matrix_config, no_noise):
return _matrix_to_zulip return _matrix_to_zulip
def get_message_content_from_event(event, no_noise): def get_message_content_from_event(event: Dict[str, Any], no_noise: bool) -> Optional[str]:
# type: (Dict[str, Any], bool) -> Optional[str]
irc_nick = shorten_irc_nick(event['sender']) irc_nick = shorten_irc_nick(event['sender'])
if event['type'] == "m.room.member": if event['type'] == "m.room.member":
if no_noise: if no_noise:
@ -117,8 +116,7 @@ def get_message_content_from_event(event, no_noise):
content = event['type'] content = event['type']
return content return content
def shorten_irc_nick(nick): def shorten_irc_nick(nick: str) -> str:
# type: (str) -> str
""" """
Add nick shortner functions for specific IRC networks Add nick shortner functions for specific IRC networks
Eg: For freenode change '@freenode_user:matrix.org' to 'user' Eg: For freenode change '@freenode_user:matrix.org' to 'user'
@ -134,11 +132,9 @@ def shorten_irc_nick(nick):
return match.group(1) return match.group(1)
return nick return nick
def zulip_to_matrix(config, room): def zulip_to_matrix(config: Dict[str, Any], room: Any) -> Callable[[Dict[str, Any]], None]:
# type: (Dict[str, Any], Any) -> Callable[[Dict[str, Any]], None]
def _zulip_to_matrix(msg): def _zulip_to_matrix(msg: Dict[str, Any]) -> None:
# type: (Dict[str, Any]) -> None
""" """
Zulip -> Matrix Zulip -> Matrix
""" """
@ -151,8 +147,7 @@ def zulip_to_matrix(config, room):
room.send_text(matrix_text) room.send_text(matrix_text)
return _zulip_to_matrix return _zulip_to_matrix
def check_zulip_message_validity(msg, config): def check_zulip_message_validity(msg: Dict[str, Any], config: Dict[str, Any]) -> bool:
# type: (Dict[str, Any], Dict[str, Any]) -> bool
is_a_stream = msg["type"] == "stream" is_a_stream = msg["type"] == "stream"
in_the_specified_stream = msg["display_recipient"] == config["stream"] in_the_specified_stream = msg["display_recipient"] == config["stream"]
at_the_specified_subject = msg["subject"] == config["topic"] at_the_specified_subject = msg["subject"] == config["topic"]
@ -164,8 +159,7 @@ def check_zulip_message_validity(msg, config):
return True return True
return False return False
def generate_parser(): def generate_parser() -> argparse.ArgumentParser:
# type: () -> argparse.ArgumentParser
description = """ description = """
Script to bridge between a topic in a Zulip stream, and a Matrix channel. Script to bridge between a topic in a Zulip stream, and a Matrix channel.
@ -190,8 +184,7 @@ def generate_parser():
help="Enable IRC join/leave events.") help="Enable IRC join/leave events.")
return parser return parser
def read_configuration(config_file): def read_configuration(config_file: str) -> Dict[str, Dict[str, str]]:
# type: (str) -> Dict[str, Dict[str, str]]
config = configparser.ConfigParser() config = configparser.ConfigParser()
try: try:
@ -206,8 +199,7 @@ def read_configuration(config_file):
return {section: dict(config[section]) for section in config.sections()} return {section: dict(config[section]) for section in config.sections()}
def write_sample_config(target_path, zuliprc): def write_sample_config(target_path: str, zuliprc: Optional[str]) -> None:
# type: (str, Optional[str]) -> None
if os.path.exists(target_path): if os.path.exists(target_path):
raise Bridge_ConfigException("Path '{}' exists; not overwriting existing file.".format(target_path)) raise Bridge_ConfigException("Path '{}' exists; not overwriting existing file.".format(target_path))
@ -248,8 +240,7 @@ def write_sample_config(target_path, zuliprc):
with open(target_path, 'w') as target: with open(target_path, 'w') as target:
sample.write(target) sample.write(target)
def main(): def main() -> None:
# type: () -> None
signal.signal(signal.SIGINT, die) signal.signal(signal.SIGINT, die)
logging.basicConfig(level=logging.WARNING) logging.basicConfig(level=logging.WARNING)

View file

@ -36,20 +36,17 @@ topic = matrix
""" """
@contextmanager @contextmanager
def new_temp_dir(): def new_temp_dir() -> Iterator[str]:
# type: () -> Iterator[str]
path = mkdtemp() path = mkdtemp()
yield path yield path
shutil.rmtree(path) shutil.rmtree(path)
class MatrixBridgeScriptTests(TestCase): class MatrixBridgeScriptTests(TestCase):
def output_from_script(self, options): def output_from_script(self, options: List[str]) -> List[str]:
# type: (List[str]) -> List[str]
popen = Popen(["python", script] + options, stdin=PIPE, stdout=PIPE, universal_newlines=True) popen = Popen(["python", script] + options, stdin=PIPE, stdout=PIPE, universal_newlines=True)
return popen.communicate()[0].strip().split("\n") return popen.communicate()[0].strip().split("\n")
def test_no_args(self): def test_no_args(self) -> None:
# type: () -> None
output_lines = self.output_from_script([]) output_lines = self.output_from_script([])
expected_lines = [ expected_lines = [
"Options required: -c or --config to run, OR --write-sample-config.", "Options required: -c or --config to run, OR --write-sample-config.",
@ -58,8 +55,7 @@ class MatrixBridgeScriptTests(TestCase):
for expected, output in zip(expected_lines, output_lines): for expected, output in zip(expected_lines, output_lines):
self.assertIn(expected, output) self.assertIn(expected, output)
def test_help_usage_and_description(self): def test_help_usage_and_description(self) -> None:
# type: () -> None
output_lines = self.output_from_script(["-h"]) output_lines = self.output_from_script(["-h"])
usage = "usage: {} [-h]".format(script_file) usage = "usage: {} [-h]".format(script_file)
description = "Script to bridge" description = "Script to bridge"
@ -72,8 +68,7 @@ class MatrixBridgeScriptTests(TestCase):
# Minimal description should be in the first line of the 2nd "paragraph" # Minimal description should be in the first line of the 2nd "paragraph"
self.assertIn(description, output_lines[blank_lines[0] + 1]) self.assertIn(description, output_lines[blank_lines[0] + 1])
def test_write_sample_config(self): def test_write_sample_config(self) -> None:
# type: () -> None
with new_temp_dir() as tempdir: with new_temp_dir() as tempdir:
path = os.path.join(tempdir, sample_config_path) path = os.path.join(tempdir, sample_config_path)
output_lines = self.output_from_script(["--write-sample-config", path]) output_lines = self.output_from_script(["--write-sample-config", path])
@ -82,8 +77,7 @@ class MatrixBridgeScriptTests(TestCase):
with open(path) as sample_file: with open(path) as sample_file:
self.assertEqual(sample_file.read(), sample_config_text) self.assertEqual(sample_file.read(), sample_config_text)
def test_write_sample_config_from_zuliprc(self): def test_write_sample_config_from_zuliprc(self) -> None:
# type: () -> None
zuliprc_template = ["[api]", "email={email}", "key={key}", "site={site}"] zuliprc_template = ["[api]", "email={email}", "key={key}", "site={site}"]
zulip_params = {'email': 'foo@bar', zulip_params = {'email': 'foo@bar',
'key': 'some_api_key', 'key': 'some_api_key',
@ -107,8 +101,7 @@ class MatrixBridgeScriptTests(TestCase):
expected_lines[9] = 'site = {}'.format(zulip_params['site']) expected_lines[9] = 'site = {}'.format(zulip_params['site'])
self.assertEqual(sample_lines, expected_lines[:-1]) self.assertEqual(sample_lines, expected_lines[:-1])
def test_detect_zuliprc_does_not_exist(self): def test_detect_zuliprc_does_not_exist(self) -> None:
# type: () -> None
with new_temp_dir() as tempdir: with new_temp_dir() as tempdir:
path = os.path.join(tempdir, sample_config_path) path = os.path.join(tempdir, sample_config_path)
zuliprc_path = os.path.join(tempdir, "zuliprc") zuliprc_path = os.path.join(tempdir, "zuliprc")
@ -132,8 +125,7 @@ class MatrixBridgeZulipToMatrixTests(TestCase):
subject=valid_zulip_config['topic'] subject=valid_zulip_config['topic']
) )
def test_zulip_message_validity_success(self): def test_zulip_message_validity_success(self) -> None:
# type: () -> None
zulip_config = self.valid_zulip_config zulip_config = self.valid_zulip_config
msg = self.valid_msg msg = self.valid_msg
# Ensure the test inputs are valid for success # Ensure the test inputs are valid for success
@ -141,8 +133,7 @@ class MatrixBridgeZulipToMatrixTests(TestCase):
self.assertTrue(check_zulip_message_validity(msg, zulip_config)) self.assertTrue(check_zulip_message_validity(msg, zulip_config))
def test_zulip_message_validity_failure(self): def test_zulip_message_validity_failure(self) -> None:
# type: () -> None
zulip_config = self.valid_zulip_config zulip_config = self.valid_zulip_config
msg_wrong_stream = dict(self.valid_msg, display_recipient='foo') msg_wrong_stream = dict(self.valid_msg, display_recipient='foo')
@ -157,8 +148,7 @@ class MatrixBridgeZulipToMatrixTests(TestCase):
msg_from_bot = dict(self.valid_msg, sender_email=zulip_config['email']) msg_from_bot = dict(self.valid_msg, sender_email=zulip_config['email'])
self.assertFalse(check_zulip_message_validity(msg_from_bot, zulip_config)) self.assertFalse(check_zulip_message_validity(msg_from_bot, zulip_config))
def test_zulip_to_matrix(self): def test_zulip_to_matrix(self) -> None:
# type: () -> None
room = mock.MagicMock() room = mock.MagicMock()
zulip_config = self.valid_zulip_config zulip_config = self.valid_zulip_config
send_msg = zulip_to_matrix(zulip_config, room) send_msg = zulip_to_matrix(zulip_config, room)

View file

@ -50,8 +50,7 @@ while len(json_implementations):
except ImportError: except ImportError:
continue continue
def make_api_call(path): def make_api_call(path: str) -> Optional[List[Dict[str, Any]]]:
# type: (str) -> Optional[List[Dict[str, Any]]]
response = requests.get("https://api3.codebasehq.com/%s" % (path,), response = requests.get("https://api3.codebasehq.com/%s" % (path,),
auth=(config.CODEBASE_API_USERNAME, config.CODEBASE_API_KEY), auth=(config.CODEBASE_API_USERNAME, config.CODEBASE_API_KEY),
params={'raw': 'True'}, params={'raw': 'True'},
@ -71,12 +70,10 @@ def make_api_call(path):
logging.warn("Found non-success response status code: %s %s" % (response.status_code, response.text)) logging.warn("Found non-success response status code: %s %s" % (response.status_code, response.text))
return None return None
def make_url(path): def make_url(path: str) -> str:
# type: (str) -> str
return "%s/%s" % (config.CODEBASE_ROOT_URL, path) return "%s/%s" % (config.CODEBASE_ROOT_URL, path)
def handle_event(event): def handle_event(event: Dict[str, Any]) -> None:
# type: (Dict[str, Any]) -> None
event = event['event'] event = event['event']
event_type = event['type'] event_type = event['type']
actor_name = event['actor_name'] actor_name = event['actor_name']
@ -240,12 +237,10 @@ def handle_event(event):
# the main run loop for this mirror script # the main run loop for this mirror script
def run_mirror(): def run_mirror() -> None:
# type: () -> None
# we should have the right (write) permissions on the resume file, as seen # we should have the right (write) permissions on the resume file, as seen
# in check_permissions, but it may still be empty or corrupted # in check_permissions, but it may still be empty or corrupted
def default_since(): def default_since() -> datetime:
# type: () -> datetime
return datetime.now(tz=pytz.utc) - timedelta(hours=config.CODEBASE_INITIAL_HISTORY_HOURS) return datetime.now(tz=pytz.utc) - timedelta(hours=config.CODEBASE_INITIAL_HISTORY_HOURS)
try: try:
@ -282,8 +277,7 @@ def run_mirror():
logging.info("Shutting down Codebase mirror") logging.info("Shutting down Codebase mirror")
# void function that checks the permissions of the files this script needs. # void function that checks the permissions of the files this script needs.
def check_permissions(): def check_permissions() -> None:
# type: () -> None
# check that the log file can be written # check that the log file can be written
if config.LOG_FILE: if config.LOG_FILE:
try: try:

View file

@ -29,16 +29,14 @@ client = zulip.Client(
api_key=config.ZULIP_API_KEY, api_key=config.ZULIP_API_KEY,
client="ZulipGit/" + VERSION) client="ZulipGit/" + VERSION)
def git_repository_name(): def git_repository_name() -> Text:
# type: () -> Text
output = subprocess.check_output(["git", "rev-parse", "--is-bare-repository"]) output = subprocess.check_output(["git", "rev-parse", "--is-bare-repository"])
if output.strip() == "true": if output.strip() == "true":
return os.path.basename(os.getcwd())[:-len(".git")] return os.path.basename(os.getcwd())[:-len(".git")]
else: else:
return os.path.basename(os.path.dirname(os.getcwd())) return os.path.basename(os.path.dirname(os.getcwd()))
def git_commit_range(oldrev, newrev): def git_commit_range(oldrev: str, newrev: str) -> str:
# type: (str, str) -> str
log_cmd = ["git", "log", "--reverse", log_cmd = ["git", "log", "--reverse",
"--pretty=%aE %H %s", "%s..%s" % (oldrev, newrev)] "--pretty=%aE %H %s", "%s..%s" % (oldrev, newrev)]
commits = '' commits = ''
@ -50,8 +48,7 @@ def git_commit_range(oldrev, newrev):
commits += '!avatar(%s) %s\n' % (author_email, subject) commits += '!avatar(%s) %s\n' % (author_email, subject)
return commits return commits
def send_bot_message(oldrev, newrev, refname): def send_bot_message(oldrev: str, newrev: str, refname: str) -> None:
# type: (str, str, str) -> None
repo_name = git_repository_name() repo_name = git_repository_name()
branch = refname.replace('refs/heads/', '') branch = refname.replace('refs/heads/', '')
destination = config.commit_notice_destination(repo_name, branch, newrev) destination = config.commit_notice_destination(repo_name, branch, newrev)

View file

@ -23,8 +23,7 @@ ZULIP_API_KEY = "0123456789abcdef0123456789abcdef"
# * stream "commits" # * stream "commits"
# * topic "master" # * topic "master"
# And similarly for branch "test-post-receive" (for use when testing). # And similarly for branch "test-post-receive" (for use when testing).
def commit_notice_destination(repo, branch, commit): def commit_notice_destination(repo: Text, branch: Text, commit: Text) -> Optional[Dict[Text, Text]]:
# type: (Text, Text, Text) -> Optional[Dict[Text, Text]]
if branch in ["master", "test-post-receive"]: if branch in ["master", "test-post-receive"]:
return dict(stream = STREAM_NAME, return dict(stream = STREAM_NAME,
subject = "%s" % (branch,)) subject = "%s" % (branch,))
@ -37,8 +36,7 @@ def commit_notice_destination(repo, branch, commit):
# graphical repository viewer, e.g. # graphical repository viewer, e.g.
# #
# return '!avatar(%s) [%s](https://example.com/commits/%s)\n' % (author, subject, commit_id) # return '!avatar(%s) [%s](https://example.com/commits/%s)\n' % (author, subject, commit_id)
def format_commit_message(author, subject, commit_id): def format_commit_message(author: Text, subject: Text, commit_id: Text) -> Text:
# type: (Text, Text, Text) -> Text
return '!avatar(%s) %s\n' % (author, subject) return '!avatar(%s) %s\n' % (author, subject)
## If properly installed, the Zulip API should be in your import ## If properly installed, the Zulip API should be in your import

View file

@ -26,8 +26,7 @@ CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Zulip Calendar Bot' APPLICATION_NAME = 'Zulip Calendar Bot'
HOME_DIR = os.path.expanduser('~') HOME_DIR = os.path.expanduser('~')
def get_credentials(): def get_credentials() -> client.Credentials:
# type: () -> client.Credentials
"""Gets valid user credentials from storage. """Gets valid user credentials from storage.
If nothing has been stored, or if the stored credentials are invalid, If nothing has been stored, or if the stored credentials are invalid,

View file

@ -79,8 +79,7 @@ if not (options.zulip_email):
zulip_client = zulip.init_from_options(options) zulip_client = zulip.init_from_options(options)
def get_credentials(): def get_credentials() -> client.Credentials:
# type: () -> client.Credentials
"""Gets valid user credentials from storage. """Gets valid user credentials from storage.
If nothing has been stored, or if the stored credentials are invalid, If nothing has been stored, or if the stored credentials are invalid,
@ -104,8 +103,7 @@ def get_credentials():
logging.error("Run the get-google-credentials script from this directory first.") logging.error("Run the get-google-credentials script from this directory first.")
def populate_events(): def populate_events() -> Optional[None]:
# type: () -> Optional[None]
global events global events
credentials = get_credentials() credentials = get_credentials()
@ -145,8 +143,7 @@ def populate_events():
events.append((event["id"], start, "(No Title)")) events.append((event["id"], start, "(No Title)"))
def send_reminders(): def send_reminders() -> Optional[None]:
# type: () -> Optional[None]
global sent global sent
messages = [] messages = []

View file

@ -12,8 +12,7 @@ from mercurial import ui, repository as repo
VERSION = "0.9" VERSION = "0.9"
def format_summary_line(web_url, user, base, tip, branch, node): def format_summary_line(web_url: str, user: str, base: int, tip: int, branch: str, node: Text) -> Text:
# type: (str, str, int, int, str, Text) -> Text
""" """
Format the first line of the message, which contains summary Format the first line of the message, which contains summary
information about the changeset and links to the changelog if a information about the changeset and links to the changelog if a
@ -38,8 +37,7 @@ def format_summary_line(web_url, user, base, tip, branch, node):
user=user, commits=formatted_commit_count, branch=branch, tip=tip, user=user, commits=formatted_commit_count, branch=branch, tip=tip,
node=node[:12]) node=node[:12])
def format_commit_lines(web_url, repo, base, tip): def format_commit_lines(web_url: str, repo: repo, base: int, tip: int) -> str:
# type: (str, repo, int, int) -> str
""" """
Format the per-commit information for the message, including the one-line Format the per-commit information for the message, including the one-line
commit summary and a link to the diff if a web URL has been configured: commit summary and a link to the diff if a web URL has been configured:
@ -64,8 +62,7 @@ def format_commit_lines(web_url, repo, base, tip):
return "\n".join(summary for summary in commit_summaries) return "\n".join(summary for summary in commit_summaries)
def send_zulip(email, api_key, site, stream, subject, content): def send_zulip(email: str, api_key: str, site: str, stream: str, subject: str, content: Text) -> None:
# type: (str, str, str, str, str, Text) -> None
""" """
Send a message to Zulip using the provided credentials, which should be for Send a message to Zulip using the provided credentials, which should be for
a bot in most cases. a bot in most cases.
@ -83,8 +80,7 @@ def send_zulip(email, api_key, site, stream, subject, content):
client.send_message(message_data) client.send_message(message_data)
def get_config(ui, item): def get_config(ui: ui, item: str) -> str:
# type: (ui, str) -> str
try: try:
# config returns configuration value. # config returns configuration value.
return ui.config('zulip', item) return ui.config('zulip', item)
@ -92,8 +88,7 @@ def get_config(ui, item):
ui.warn("Zulip: Could not find required item {} in hg config.".format(item)) ui.warn("Zulip: Could not find required item {} in hg config.".format(item))
sys.exit(1) sys.exit(1)
def hook(ui, repo, **kwargs): def hook(ui: ui, repo: repo, **kwargs: Text) -> None:
# type: (ui, repo, **Text) -> None
""" """
Invoked by configuring a [hook] entry in .hg/hgrc. Invoked by configuring a [hook] entry in .hg/hgrc.
""" """

View file

@ -9,8 +9,7 @@ from types import FrameType
from typing import Any from typing import Any
from zulip import RandomExponentialBackoff from zulip import RandomExponentialBackoff
def die(signal, frame): def die(signal: int, frame: FrameType) -> None:
# type: (int, FrameType) -> None
"""We actually want to exit, so run os._exit (so as not to be caught and restarted)""" """We actually want to exit, so run os._exit (so as not to be caught and restarted)"""
os._exit(1) os._exit(1)

View file

@ -57,23 +57,19 @@ from typing import Any, Callable
__version__ = "1.1" __version__ = "1.1"
def room_to_stream(room): def room_to_stream(room: str) -> str:
# type: (str) -> str
return room + "/xmpp" return room + "/xmpp"
def stream_to_room(stream): def stream_to_room(stream: str) -> str:
# type: (str) -> str
return stream.lower().rpartition("/xmpp")[0] return stream.lower().rpartition("/xmpp")[0]
def jid_to_zulip(jid): def jid_to_zulip(jid: JID) -> str:
# type: (JID) -> str
suffix = '' suffix = ''
if not jid.username.endswith("-bot"): if not jid.username.endswith("-bot"):
suffix = options.zulip_email_suffix suffix = options.zulip_email_suffix
return "%s%s@%s" % (jid.username, suffix, options.zulip_domain) return "%s%s@%s" % (jid.username, suffix, options.zulip_domain)
def zulip_to_jid(email, jabber_domain): def zulip_to_jid(email: str, jabber_domain: str) -> JID:
# type: (str, str) -> JID
jid = JID(email, domain=jabber_domain) jid = JID(email, domain=jabber_domain)
if (options.zulip_email_suffix and if (options.zulip_email_suffix and
options.zulip_email_suffix in jid.username and options.zulip_email_suffix in jid.username and
@ -82,8 +78,7 @@ def zulip_to_jid(email, jabber_domain):
return jid return jid
class JabberToZulipBot(ClientXMPP): class JabberToZulipBot(ClientXMPP):
def __init__(self, jid, password, rooms): def __init__(self, jid: JID, password: str, rooms: List[str]) -> None:
# type: (JID, str, List[str]) -> None
if jid.resource: if jid.resource:
self.nick = jid.resource self.nick = jid.resource
else: else:
@ -100,19 +95,16 @@ class JabberToZulipBot(ClientXMPP):
self.register_plugin('xep_0045') # Jabber chatrooms self.register_plugin('xep_0045') # Jabber chatrooms
self.register_plugin('xep_0199') # XMPP Ping self.register_plugin('xep_0199') # XMPP Ping
def set_zulip_client(self, zulipToJabberClient): def set_zulip_client(self, zulipToJabberClient: 'ZulipToJabberBot') -> None:
# type: (ZulipToJabberBot) -> None
self.zulipToJabber = zulipToJabberClient self.zulipToJabber = zulipToJabberClient
def session_start(self, event): def session_start(self, event: Dict[str, Any]) -> None:
# type: (Dict[str, Any]) -> None
self.get_roster() self.get_roster()
self.send_presence() self.send_presence()
for room in self.rooms_to_join: for room in self.rooms_to_join:
self.join_muc(room) self.join_muc(room)
def join_muc(self, room): def join_muc(self, room: str) -> None:
# type: (str) -> None
if room in self.rooms: if room in self.rooms:
return return
logging.debug("Joining " + room) logging.debug("Joining " + room)
@ -137,8 +129,7 @@ class JabberToZulipBot(ClientXMPP):
else: else:
logging.error("Could not configure room: " + str(muc_jid)) logging.error("Could not configure room: " + str(muc_jid))
def leave_muc(self, room): def leave_muc(self, room: str) -> None:
# type: (str) -> None
if room not in self.rooms: if room not in self.rooms:
return return
logging.debug("Leaving " + room) logging.debug("Leaving " + room)
@ -146,8 +137,7 @@ class JabberToZulipBot(ClientXMPP):
muc_jid = JID(local=room, domain=options.conference_domain) muc_jid = JID(local=room, domain=options.conference_domain)
self.plugin['xep_0045'].leaveMUC(muc_jid, self.nick) self.plugin['xep_0045'].leaveMUC(muc_jid, self.nick)
def message(self, msg): def message(self, msg: JabberMessage) -> Any:
# type: (JabberMessage) -> Any
try: try:
if msg["type"] == "groupchat": if msg["type"] == "groupchat":
return self.group(msg) return self.group(msg)
@ -159,8 +149,7 @@ class JabberToZulipBot(ClientXMPP):
except Exception: except Exception:
logging.exception("Error forwarding Jabber => Zulip") logging.exception("Error forwarding Jabber => Zulip")
def private(self, msg): def private(self, msg: JabberMessage) -> None:
# type: (JabberMessage) -> None
if options.mode == 'public' or msg['thread'] == '\u1FFFE': if options.mode == 'public' or msg['thread'] == '\u1FFFE':
return return
sender = jid_to_zulip(msg["from"]) sender = jid_to_zulip(msg["from"])
@ -176,8 +165,7 @@ class JabberToZulipBot(ClientXMPP):
if ret.get("result") != "success": if ret.get("result") != "success":
logging.error(str(ret)) logging.error(str(ret))
def group(self, msg): def group(self, msg: JabberMessage) -> None:
# type: (JabberMessage) -> None
if options.mode == 'personal' or msg["thread"] == '\u1FFFE': if options.mode == 'personal' or msg["thread"] == '\u1FFFE':
return return
@ -204,8 +192,7 @@ class JabberToZulipBot(ClientXMPP):
if ret.get("result") != "success": if ret.get("result") != "success":
logging.error(str(ret)) logging.error(str(ret))
def nickname_to_jid(self, room, nick): def nickname_to_jid(self, room: str, nick: str) -> JID:
# type: (str, str) -> JID
jid = self.plugin['xep_0045'].getJidProperty(room, nick, "jid") jid = self.plugin['xep_0045'].getJidProperty(room, nick, "jid")
if (jid is None or jid == ''): if (jid is None or jid == ''):
return JID(local=nick.replace(' ', ''), domain=self.boundjid.domain) return JID(local=nick.replace(' ', ''), domain=self.boundjid.domain)
@ -213,17 +200,14 @@ class JabberToZulipBot(ClientXMPP):
return jid return jid
class ZulipToJabberBot: class ZulipToJabberBot:
def __init__(self, zulip_client): def __init__(self, zulip_client: Client) -> None:
# type: (Client) -> None
self.client = zulip_client self.client = zulip_client
self.jabber = None # type: Optional[JabberToZulipBot] self.jabber = None # type: Optional[JabberToZulipBot]
def set_jabber_client(self, client): def set_jabber_client(self, client: JabberToZulipBot) -> None:
# type: (JabberToZulipBot) -> None
self.jabber = client self.jabber = client
def process_event(self, event): def process_event(self, event: Dict[str, Any]) -> None:
# type: (Dict[str, Any]) -> None
if event['type'] == 'message': if event['type'] == 'message':
message = event["message"] message = event["message"]
if message['sender_email'] != self.client.email: if message['sender_email'] != self.client.email:
@ -241,8 +225,7 @@ class ZulipToJabberBot:
elif event['type'] == 'stream': elif event['type'] == 'stream':
self.process_stream(event) self.process_stream(event)
def stream_message(self, msg): def stream_message(self, msg: Dict[str, str]) -> None:
# type: (Dict[str, str]) -> None
assert(self.jabber is not None) assert(self.jabber is not None)
stream = msg['display_recipient'] stream = msg['display_recipient']
if not stream.endswith("/xmpp"): if not stream.endswith("/xmpp"):
@ -257,8 +240,7 @@ class ZulipToJabberBot:
outgoing['thread'] = '\u1FFFE' outgoing['thread'] = '\u1FFFE'
outgoing.send() outgoing.send()
def private_message(self, msg): def private_message(self, msg: Dict[str, Any]) -> None:
# type: (Dict[str, Any]) -> None
assert(self.jabber is not None) assert(self.jabber is not None)
for recipient in msg['display_recipient']: for recipient in msg['display_recipient']:
if recipient["email"] == self.client.email: if recipient["email"] == self.client.email:
@ -274,8 +256,7 @@ class ZulipToJabberBot:
outgoing['thread'] = '\u1FFFE' outgoing['thread'] = '\u1FFFE'
outgoing.send() outgoing.send()
def process_subscription(self, event): def process_subscription(self, event: Dict[str, Any]) -> None:
# type: (Dict[str, Any]) -> None
assert(self.jabber is not None) assert(self.jabber is not None)
if event['op'] == 'add': if event['op'] == 'add':
streams = [s['name'].lower() for s in event['subscriptions']] streams = [s['name'].lower() for s in event['subscriptions']]
@ -288,8 +269,7 @@ class ZulipToJabberBot:
for stream in streams: for stream in streams:
self.jabber.leave_muc(stream_to_room(stream)) self.jabber.leave_muc(stream_to_room(stream))
def process_stream(self, event): def process_stream(self, event: Dict[str, Any]) -> None:
# type: (Dict[str, Any]) -> None
assert(self.jabber is not None) assert(self.jabber is not None)
if event['op'] == 'occupy': if event['op'] == 'occupy':
streams = [s['name'].lower() for s in event['streams']] streams = [s['name'].lower() for s in event['streams']]
@ -302,10 +282,8 @@ class ZulipToJabberBot:
for stream in streams: for stream in streams:
self.jabber.leave_muc(stream_to_room(stream)) self.jabber.leave_muc(stream_to_room(stream))
def get_rooms(zulipToJabber): def get_rooms(zulipToJabber: ZulipToJabberBot) -> List[str]:
# type: (ZulipToJabberBot) -> List[str] def get_stream_infos(key: str, method: Callable[[], Dict[str, Any]]) -> Any:
def get_stream_infos(key, method):
# type: (str, Callable[[], Dict[str, Any]]) -> Any
ret = method() ret = method()
if ret.get("result") != "success": if ret.get("result") != "success":
logging.error(str(ret)) logging.error(str(ret))
@ -324,8 +302,7 @@ def get_rooms(zulipToJabber):
rooms.append(stream_to_room(stream)) rooms.append(stream_to_room(stream))
return rooms return rooms
def config_error(msg): def config_error(msg: str) -> None:
# type: (str) -> None
sys.stderr.write("%s\n" % (msg,)) sys.stderr.write("%s\n" % (msg,))
sys.exit(2) sys.exit(2)

View file

@ -31,8 +31,7 @@ from typing import List
temp_dir = "/var/tmp/" if os.name == "posix" else tempfile.gettempdir() temp_dir = "/var/tmp/" if os.name == "posix" else tempfile.gettempdir()
def mkdir_p(path): def mkdir_p(path: str) -> None:
# type: (str) -> None
# Python doesn't have an analog to `mkdir -p` < Python 3.2. # Python doesn't have an analog to `mkdir -p` < Python 3.2.
try: try:
os.makedirs(path) os.makedirs(path)
@ -42,8 +41,7 @@ def mkdir_p(path):
else: else:
raise raise
def send_log_zulip(file_name, count, lines, extra=""): def send_log_zulip(file_name: str, count: int, lines: List[str], extra: str = "") -> None:
# type: (str, int, List[str], str) -> None
content = "%s new errors%s:\n```\n%s\n```" % (count, extra, "\n".join(lines)) content = "%s new errors%s:\n```\n%s\n```" % (count, extra, "\n".join(lines))
zulip_client.send_message({ zulip_client.send_message({
"type": "stream", "type": "stream",
@ -52,8 +50,7 @@ def send_log_zulip(file_name, count, lines, extra=""):
"content": content, "content": content,
}) })
def process_lines(raw_lines, file_name): def process_lines(raw_lines: List[str], file_name: str) -> None:
# type: (List[str], str) -> None
lines = [] lines = []
for line in raw_lines: for line in raw_lines:
# Add any filtering or modification code here # Add any filtering or modification code here
@ -68,8 +65,7 @@ def process_lines(raw_lines, file_name):
else: else:
send_log_zulip(file_name, len(lines), lines) send_log_zulip(file_name, len(lines), lines)
def process_logs(): def process_logs() -> None:
# type: () -> None
data_file_path = os.path.join(temp_dir, "log2zulip.state") data_file_path = os.path.join(temp_dir, "log2zulip.state")
mkdir_p(os.path.dirname(data_file_path)) mkdir_p(os.path.dirname(data_file_path))
if not os.path.exists(data_file_path): if not os.path.exists(data_file_path):

View file

@ -21,8 +21,7 @@ client = zulip.Client(
api_key=config.ZULIP_API_KEY, api_key=config.ZULIP_API_KEY,
client='ZulipOpenShift/' + VERSION) client='ZulipOpenShift/' + VERSION)
def get_deployment_details(): def get_deployment_details() -> Dict[str, str]:
# type: () -> Dict[str, str]
# "gear deployments" output example: # "gear deployments" output example:
# Activation time - Deployment ID - Git Ref - Git SHA1 # Activation time - Deployment ID - Git Ref - Git SHA1
# 2017-01-07 15:40:30 -0500 - 9e2b7143 - master - b9ce57c - ACTIVE # 2017-01-07 15:40:30 -0500 - 9e2b7143 - master - b9ce57c - ACTIVE
@ -34,8 +33,7 @@ def get_deployment_details():
branch=splits[2], branch=splits[2],
commit_id=splits[3]) commit_id=splits[3])
def send_bot_message(deployment): def send_bot_message(deployment: Dict[str, str]) -> None:
# type: (Dict[str, str]) -> None
destination = config.deployment_notice_destination(deployment['branch']) destination = config.deployment_notice_destination(deployment['branch'])
if destination is None: if destination is None:
# No message should be sent # No message should be sent

View file

@ -19,8 +19,7 @@ ZULIP_API_KEY = '0123456789abcdef0123456789abcdef'
# * stream "deployments" # * stream "deployments"
# * topic "master" # * topic "master"
# And similarly for branch "test-post-receive" (for use when testing). # And similarly for branch "test-post-receive" (for use when testing).
def deployment_notice_destination(branch): def deployment_notice_destination(branch: str) -> Optional[Dict[str, Text]]:
# type: (str) -> Optional[Dict[str, Text]]
if branch in ['master', 'test-post-receive']: if branch in ['master', 'test-post-receive']:
return dict(stream = 'deployments', return dict(stream = 'deployments',
subject = '%s' % (branch,)) subject = '%s' % (branch,))
@ -40,8 +39,7 @@ def deployment_notice_destination(branch):
# * dep_id = deployment id # * dep_id = deployment id
# * dep_time = deployment timestamp # * dep_time = deployment timestamp
def format_deployment_message( def format_deployment_message(
app_name='', url='', branch='', commit_id='', dep_id='', dep_time=''): app_name: str = '', url: str = '', branch: str = '', commit_id: str = '', dep_id: str = '', dep_time: str = '') -> str:
# type: (str, str, str, str, str, str) -> str
return 'Deployed commit `%s` (%s) in [%s](%s)' % ( return 'Deployed commit `%s` (%s) in [%s](%s)' % (
commit_id, branch, app_name, url) commit_id, branch, app_name, url)

View file

@ -28,8 +28,7 @@ P4_WEB = None
# "master-plan" and "secret" subdirectories of //depot/ to: # "master-plan" and "secret" subdirectories of //depot/ to:
# * stream "depot_subdirectory-commits" # * stream "depot_subdirectory-commits"
# * subject "change_root" # * subject "change_root"
def commit_notice_destination(path, changelist): def commit_notice_destination(path: Text, changelist: int) -> Optional[Dict[Text, Text]]:
# type: (Text, int) -> Optional[Dict[Text, Text]]
dirs = path.split('/') dirs = path.split('/')
if len(dirs) >= 4 and dirs[3] not in ("*", "..."): if len(dirs) >= 4 and dirs[3] not in ("*", "..."):
directory = dirs[3] directory = dirs[3]

View file

@ -75,8 +75,7 @@ parser.add_argument('--math',
opts = parser.parse_args() # type: Any opts = parser.parse_args() # type: Any
def mkdir_p(path): def mkdir_p(path: str) -> None:
# type: (str) -> None
# Python doesn't have an analog to `mkdir -p` < Python 3.2. # Python doesn't have an analog to `mkdir -p` < Python 3.2.
try: try:
os.makedirs(path) os.makedirs(path)
@ -105,53 +104,44 @@ logger = logging.getLogger(__name__) # type: logging.Logger
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
logger.addHandler(file_handler) logger.addHandler(file_handler)
def log_error_and_exit(error): def log_error_and_exit(error: str) -> None:
# type: (str) -> None
logger.error(error) logger.error(error)
logger.error(usage) logger.error(usage)
exit(1) exit(1)
class MLStripper(HTMLParser): class MLStripper(HTMLParser):
def __init__(self): def __init__(self) -> None:
# type: () -> None
self.reset() self.reset()
self.fed = [] # type: List[str] self.fed = [] # type: List[str]
def handle_data(self, data): def handle_data(self, data: str) -> None:
# type: (str) -> None
self.fed.append(data) self.fed.append(data)
def get_data(self): def get_data(self) -> str:
# type: () -> str
return ''.join(self.fed) return ''.join(self.fed)
def strip_tags(html): def strip_tags(html: str) -> str:
# type: (str) -> str
stripper = MLStripper() stripper = MLStripper()
stripper.feed(html) stripper.feed(html)
return stripper.get_data() return stripper.get_data()
def compute_entry_hash(entry): def compute_entry_hash(entry: Dict[str, Any]) -> str:
# type: (Dict[str, Any]) -> str
entry_time = entry.get("published", entry.get("updated")) entry_time = entry.get("published", entry.get("updated"))
entry_id = entry.get("id", entry.get("link")) entry_id = entry.get("id", entry.get("link"))
return hashlib.md5(entry_id + str(entry_time)).hexdigest() return hashlib.md5(entry_id + str(entry_time)).hexdigest()
def unwrap_text(body): def unwrap_text(body: str) -> str:
# type: (str) -> str
# Replace \n by space if it is preceded and followed by a non-\n. # Replace \n by space if it is preceded and followed by a non-\n.
# Example: '\na\nb\nc\n\nd\n' -> '\na b c\n\nd\n' # Example: '\na\nb\nc\n\nd\n' -> '\na b c\n\nd\n'
return re.sub('(?<=[^\n])\n(?=[^\n])', ' ', body) return re.sub('(?<=[^\n])\n(?=[^\n])', ' ', body)
def elide_subject(subject): def elide_subject(subject: str) -> str:
# type: (str) -> str
MAX_TOPIC_LENGTH = 60 MAX_TOPIC_LENGTH = 60
if len(subject) > MAX_TOPIC_LENGTH: if len(subject) > MAX_TOPIC_LENGTH:
subject = subject[:MAX_TOPIC_LENGTH - 3].rstrip() + '...' subject = subject[:MAX_TOPIC_LENGTH - 3].rstrip() + '...'
return subject return subject
def send_zulip(entry, feed_name): def send_zulip(entry: Any, feed_name: str) -> Dict[str, Any]:
# type: (Any, str) -> Dict[str, Any]
body = entry.summary # type: str body = entry.summary # type: str
if opts.unwrap: if opts.unwrap:
body = unwrap_text(body) body = unwrap_text(body)

View file

@ -18,8 +18,7 @@ ZULIP_API_KEY = "0123456789abcdef0123456789abcdef"
# and "my-super-secret-repository" repos to # and "my-super-secret-repository" repos to
# * stream "commits" # * stream "commits"
# * topic "branch_name" # * topic "branch_name"
def commit_notice_destination(path, commit): def commit_notice_destination(path: Text, commit: Text) -> Optional[Dict[Text, Text]]:
# type: (Text, Text) -> Optional[Dict[Text, Text]]
repo = path.split('/')[-1] repo = path.split('/')[-1]
if repo not in ["evil-master-plan", "my-super-secret-repository"]: if repo not in ["evil-master-plan", "my-super-secret-repository"]:
return dict(stream = "commits", return dict(stream = "commits",

View file

@ -31,26 +31,21 @@ client = zulip.Client(
api_key=config.ZULIP_API_KEY, api_key=config.ZULIP_API_KEY,
client="ZulipTrac/" + VERSION) client="ZulipTrac/" + VERSION)
def markdown_ticket_url(ticket, heading="ticket"): def markdown_ticket_url(ticket: Any, heading: str = "ticket") -> str:
# type: (Any, str) -> str
return "[%s #%s](%s/%s)" % (heading, ticket.id, config.TRAC_BASE_TICKET_URL, ticket.id) return "[%s #%s](%s/%s)" % (heading, ticket.id, config.TRAC_BASE_TICKET_URL, ticket.id)
def markdown_block(desc): def markdown_block(desc: str) -> str:
# type: (str) -> str
return "\n\n>" + "\n> ".join(desc.split("\n")) + "\n" return "\n\n>" + "\n> ".join(desc.split("\n")) + "\n"
def truncate(string, length): def truncate(string: str, length: int) -> str:
# type: (str, int) -> str
if len(string) <= length: if len(string) <= length:
return string return string
return string[:length - 3] + "..." return string[:length - 3] + "..."
def trac_subject(ticket): def trac_subject(ticket: Any) -> str:
# type: (Any) -> str
return truncate("#%s: %s" % (ticket.id, ticket.values.get("summary")), 60) return truncate("#%s: %s" % (ticket.id, ticket.values.get("summary")), 60)
def send_update(ticket, content): def send_update(ticket: Any, content: str) -> None:
# type: (Any, str) -> None
client.send_message({ client.send_message({
"type": "stream", "type": "stream",
"to": config.STREAM_FOR_NOTIFICATIONS, "to": config.STREAM_FOR_NOTIFICATIONS,
@ -61,8 +56,7 @@ def send_update(ticket, content):
class ZulipPlugin(Component): class ZulipPlugin(Component):
implements(ITicketChangeListener) implements(ITicketChangeListener)
def ticket_created(self, ticket): def ticket_created(self, ticket: Any) -> None:
# type: (Any) -> None
"""Called when a ticket is created.""" """Called when a ticket is created."""
content = "%s created %s in component **%s**, priority **%s**:\n" % \ content = "%s created %s in component **%s**, priority **%s**:\n" % \
(ticket.values.get("reporter"), markdown_ticket_url(ticket), (ticket.values.get("reporter"), markdown_ticket_url(ticket),
@ -74,8 +68,7 @@ class ZulipPlugin(Component):
content += "%s" % (markdown_block(ticket.values.get("description")),) content += "%s" % (markdown_block(ticket.values.get("description")),)
send_update(ticket, content) send_update(ticket, content)
def ticket_changed(self, ticket, comment, author, old_values): def ticket_changed(self, ticket: Any, comment: str, author: str, old_values: Dict[str, Any]) -> None:
# type: (Any, str, str, Dict[str, Any]) -> None
"""Called when a ticket is modified. """Called when a ticket is modified.
`old_values` is a dictionary containing the previous values of the `old_values` is a dictionary containing the previous values of the
@ -106,8 +99,7 @@ class ZulipPlugin(Component):
send_update(ticket, content) send_update(ticket, content)
def ticket_deleted(self, ticket): def ticket_deleted(self, ticket: Any) -> None:
# type: (Any) -> None
"""Called when a ticket is deleted.""" """Called when a ticket is deleted."""
content = "%s was deleted." % markdown_ticket_url(ticket, heading="Ticket") content = "%s was deleted." % markdown_ticket_url(ticket, heading="Ticket")
send_update(ticket, content) send_update(ticket, content)

View file

@ -66,8 +66,7 @@ Make sure to go the application you created and click "create my
access token" as well. Fill in the values displayed. access token" as well. Fill in the values displayed.
""" """
def write_config(config, configfile_path): def write_config(config: ConfigParser, configfile_path: str) -> None:
# type: (ConfigParser, str) -> None
with open(configfile_path, 'w') as configfile: with open(configfile_path, 'w') as configfile:
config.write(configfile) config.write(configfile)

View file

@ -82,8 +82,7 @@ else:
("tabbott-nagios-test", "a"), ("tabbott-nagios-test", "a"),
] ]
def print_status_and_exit(status): def print_status_and_exit(status: int) -> None:
# type: (int) -> None
# The output of this script is used by Nagios. Various outputs, # The output of this script is used by Nagios. Various outputs,
# e.g. true success and punting due to a SERVNAK, result in a # e.g. true success and punting due to a SERVNAK, result in a
@ -92,8 +91,7 @@ def print_status_and_exit(status):
print(status) print(status)
sys.exit(status) sys.exit(status)
def send_zulip(message): def send_zulip(message: Dict[str, str]) -> None:
# type: (Dict[str, str]) -> None
result = zulip_client.send_message(message) result = zulip_client.send_message(message)
if result["result"] != "success": if result["result"] != "success":
logger.error("Error sending zulip, args were:") logger.error("Error sending zulip, args were:")
@ -102,8 +100,7 @@ def send_zulip(message):
print_status_and_exit(1) print_status_and_exit(1)
# Returns True if and only if we "Detected server failure" sending the zephyr. # Returns True if and only if we "Detected server failure" sending the zephyr.
def send_zephyr(zwrite_args, content): def send_zephyr(zwrite_args: List[str], content: str) -> bool:
# type: (List[str], str) -> bool
p = subprocess.Popen(zwrite_args, stdin=subprocess.PIPE, p = subprocess.Popen(zwrite_args, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate(input=content.encode("utf-8")) stdout, stderr = p.communicate(input=content.encode("utf-8"))
@ -167,16 +164,14 @@ if not actually_subscribed:
# Prepare keys # Prepare keys
zhkeys = {} # type: Dict[str, Tuple[str, str]] zhkeys = {} # type: Dict[str, Tuple[str, str]]
hzkeys = {} # type: Dict[str, Tuple[str, str]] hzkeys = {} # type: Dict[str, Tuple[str, str]]
def gen_key(key_dict): def gen_key(key_dict: Dict[str, Any]) -> str:
# type: (Dict[str, Any]) -> str
bits = str(random.getrandbits(32)) bits = str(random.getrandbits(32))
while bits in key_dict: while bits in key_dict:
# Avoid the unlikely event that we get the same bits twice # Avoid the unlikely event that we get the same bits twice
bits = str(random.getrandbits(32)) bits = str(random.getrandbits(32))
return bits return bits
def gen_keys(key_dict): def gen_keys(key_dict: Dict[str, Tuple[str, str]]) -> None:
# type: (Dict[str, Tuple[str, str]]) -> None
for (stream, test) in test_streams: for (stream, test) in test_streams:
key_dict[gen_key(key_dict)] = (stream, test) key_dict[gen_key(key_dict)] = (stream, test)
@ -188,8 +183,7 @@ notices = []
# We check for new zephyrs multiple times, to avoid filling the zephyr # We check for new zephyrs multiple times, to avoid filling the zephyr
# receive queue with 30+ messages, which might result in messages # receive queue with 30+ messages, which might result in messages
# being dropped. # being dropped.
def receive_zephyrs(): def receive_zephyrs() -> None:
# type: () -> None
while True: while True:
try: try:
notice = zephyr.receive(block=False) notice = zephyr.receive(block=False)
@ -271,8 +265,7 @@ receive_zephyrs()
logger.info("Finished receiving Zephyr messages!") logger.info("Finished receiving Zephyr messages!")
all_keys = set(list(zhkeys.keys()) + list(hzkeys.keys())) all_keys = set(list(zhkeys.keys()) + list(hzkeys.keys()))
def process_keys(content_list): def process_keys(content_list: List[str]) -> Tuple[Dict[str, int], Set[str], Set[str], bool, bool]:
# type: (List[str]) -> Tuple[Dict[str, int], Set[str], Set[str], bool, bool]
# Start by filtering out any keys that might have come from # Start by filtering out any keys that might have come from
# concurrent check-mirroring processes # concurrent check-mirroring processes

View file

@ -13,8 +13,7 @@ import zulip
from typing import Set, Optional from typing import Set, Optional
def fetch_public_streams(): def fetch_public_streams() -> Optional[Set[bytes]]:
# type: () -> Optional[Set[bytes]]
public_streams = set() public_streams = set()
try: try:

View file

@ -14,8 +14,7 @@ from zephyr_mirror_backend import parse_args
from types import FrameType from types import FrameType
from typing import Any from typing import Any
def die(signal, frame): def die(signal: int, frame: FrameType) -> None:
# type: (int, FrameType) -> None
# We actually want to exit, so run os._exit (so as not to be caught and restarted) # We actually want to exit, so run os._exit (so as not to be caught and restarted)
os._exit(1) os._exit(1)
@ -40,8 +39,7 @@ if options.forward_class_messages and not options.noshard:
print("Starting parallel zephyr class mirroring bot") print("Starting parallel zephyr class mirroring bot")
jobs = list("0123456789abcdef") jobs = list("0123456789abcdef")
def run_job(shard): def run_job(shard: str) -> int:
# type: (str) -> int
subprocess.call(args + ["--shard=%s" % (shard,)]) subprocess.call(args + ["--shard=%s" % (shard,)])
return 0 return 0
for (status, job) in run_parallel(run_job, jobs, threads=16): for (status, job) in run_parallel(run_job, jobs, threads=16):

View file

@ -26,8 +26,7 @@ CURRENT_STATE = States.Startup
logger = cast(logging.Logger, None) # type: logging.Logger # FIXME cast should not be needed? logger = cast(logging.Logger, None) # type: logging.Logger # FIXME cast should not be needed?
def to_zulip_username(zephyr_username): def to_zulip_username(zephyr_username: str) -> str:
# type: (str) -> str
if "@" in zephyr_username: if "@" in zephyr_username:
(user, realm) = zephyr_username.split("@") (user, realm) = zephyr_username.split("@")
else: else:
@ -39,8 +38,7 @@ def to_zulip_username(zephyr_username):
return user.lower() + "@mit.edu" return user.lower() + "@mit.edu"
return user.lower() + "|" + realm.upper() + "@mit.edu" return user.lower() + "|" + realm.upper() + "@mit.edu"
def to_zephyr_username(zulip_username): def to_zephyr_username(zulip_username: str) -> str:
# type: (str) -> str
(user, realm) = zulip_username.split("@") (user, realm) = zulip_username.split("@")
if "|" not in user: if "|" not in user:
# Hack to make ctl's fake username setup work :) # Hack to make ctl's fake username setup work :)
@ -62,8 +60,7 @@ def to_zephyr_username(zulip_username):
# characters (our assumed minimum linewrapping threshold for Zephyr) # characters (our assumed minimum linewrapping threshold for Zephyr)
# or (3) the first word of the next line is longer than this entire # or (3) the first word of the next line is longer than this entire
# line. # line.
def different_paragraph(line, next_line): def different_paragraph(line: str, next_line: str) -> bool:
# type: (str, str) -> bool
words = next_line.split() words = next_line.split()
return (len(line + " " + words[0]) < len(next_line) * 0.8 or return (len(line + " " + words[0]) < len(next_line) * 0.8 or
len(line + " " + words[0]) < 50 or len(line + " " + words[0]) < 50 or
@ -71,8 +68,7 @@ def different_paragraph(line, next_line):
# Linewrapping algorithm based on: # Linewrapping algorithm based on:
# http://gcbenison.wordpress.com/2011/07/03/a-program-to-intelligently-remove-carriage-returns-so-you-can-paste-text-without-having-it-look-awful/ #ignorelongline # http://gcbenison.wordpress.com/2011/07/03/a-program-to-intelligently-remove-carriage-returns-so-you-can-paste-text-without-having-it-look-awful/ #ignorelongline
def unwrap_lines(body): def unwrap_lines(body: str) -> str:
# type: (str) -> str
lines = body.split("\n") lines = body.split("\n")
result = "" result = ""
previous_line = lines[0] previous_line = lines[0]
@ -95,8 +91,7 @@ def unwrap_lines(body):
result += previous_line result += previous_line
return result return result
def send_zulip(zeph): def send_zulip(zeph: Dict[str, str]) -> Dict[str, str]:
# type: (Dict[str, str]) -> Dict[str, str]
message = {} message = {}
if options.forward_class_messages: if options.forward_class_messages:
message["forged"] = "yes" message["forged"] = "yes"
@ -128,8 +123,7 @@ def send_zulip(zeph):
return zulip_client.send_message(message) return zulip_client.send_message(message)
def send_error_zulip(error_msg): def send_error_zulip(error_msg: str) -> None:
# type: (str) -> None
message = {"type": "private", message = {"type": "private",
"sender": zulip_account_email, "sender": zulip_account_email,
"to": zulip_account_email, "to": zulip_account_email,
@ -138,8 +132,7 @@ def send_error_zulip(error_msg):
zulip_client.send_message(message) zulip_client.send_message(message)
current_zephyr_subs = set() current_zephyr_subs = set()
def zephyr_bulk_subscribe(subs): def zephyr_bulk_subscribe(subs: List[Tuple[str, str, str]]) -> None:
# type: (List[Tuple[str, str, str]]) -> None
try: try:
zephyr._z.subAll(subs) zephyr._z.subAll(subs)
except OSError: except OSError:
@ -174,8 +167,7 @@ def zephyr_bulk_subscribe(subs):
else: else:
current_zephyr_subs.add(cls) current_zephyr_subs.add(cls)
def update_subscriptions(): def update_subscriptions() -> None:
# type: () -> None
try: try:
f = open(options.stream_file_path) f = open(options.stream_file_path)
public_streams = json.loads(f.read()) public_streams = json.loads(f.read())
@ -198,8 +190,7 @@ def update_subscriptions():
if len(classes_to_subscribe) > 0: if len(classes_to_subscribe) > 0:
zephyr_bulk_subscribe(list(classes_to_subscribe)) zephyr_bulk_subscribe(list(classes_to_subscribe))
def maybe_kill_child(): def maybe_kill_child() -> None:
# type: () -> None
try: try:
if child_pid is not None: if child_pid is not None:
os.kill(child_pid, signal.SIGTERM) os.kill(child_pid, signal.SIGTERM)
@ -207,8 +198,7 @@ def maybe_kill_child():
# We don't care if the child process no longer exists, so just log the error # We don't care if the child process no longer exists, so just log the error
logger.exception("") logger.exception("")
def maybe_restart_mirroring_script(): def maybe_restart_mirroring_script() -> None:
# type: () -> None
if os.stat(os.path.join(options.stamp_path, "stamps", "restart_stamp")).st_mtime > start_time or \ if os.stat(os.path.join(options.stamp_path, "stamps", "restart_stamp")).st_mtime > start_time or \
((options.user == "tabbott" or options.user == "tabbott/extra") and ((options.user == "tabbott" or options.user == "tabbott/extra") and
os.stat(os.path.join(options.stamp_path, "stamps", "tabbott_stamp")).st_mtime > start_time): os.stat(os.path.join(options.stamp_path, "stamps", "tabbott_stamp")).st_mtime > start_time):
@ -227,8 +217,7 @@ def maybe_restart_mirroring_script():
logger.exception("Error restarting mirroring script; trying again... Traceback:") logger.exception("Error restarting mirroring script; trying again... Traceback:")
time.sleep(1) time.sleep(1)
def process_loop(log): def process_loop(log: Optional[IO[Any]]) -> None:
# type: (Optional[IO[Any]]) -> None
restart_check_count = 0 restart_check_count = 0
last_check_time = time.time() last_check_time = time.time()
while True: while True:
@ -267,8 +256,7 @@ def process_loop(log):
except Exception: except Exception:
logger.exception("Error updating subscriptions from Zulip:") logger.exception("Error updating subscriptions from Zulip:")
def parse_zephyr_body(zephyr_data, notice_format): def parse_zephyr_body(zephyr_data: str, notice_format: str) -> Tuple[str, str]:
# type: (str, str) -> Tuple[str, str]
try: try:
(zsig, body) = zephyr_data.split("\x00", 1) (zsig, body) = zephyr_data.split("\x00", 1)
if (notice_format == 'New transaction [$1] entered in $2\nFrom: $3 ($5)\nSubject: $4' or if (notice_format == 'New transaction [$1] entered in $2\nFrom: $3 ($5)\nSubject: $4' or
@ -284,8 +272,7 @@ def parse_zephyr_body(zephyr_data, notice_format):
body = body.replace('\x00', '') body = body.replace('\x00', '')
return (zsig, body) return (zsig, body)
def parse_crypt_table(zephyr_class, instance): def parse_crypt_table(zephyr_class: Text, instance: str) -> Optional[str]:
# type: (Text, str) -> Optional[str]
try: try:
crypt_table = open(os.path.join(os.environ["HOME"], ".crypt-table")) crypt_table = open(os.path.join(os.environ["HOME"], ".crypt-table"))
except OSError: except OSError:
@ -306,8 +293,7 @@ def parse_crypt_table(zephyr_class, instance):
return groups["keypath"] return groups["keypath"]
return None return None
def decrypt_zephyr(zephyr_class, instance, body): def decrypt_zephyr(zephyr_class: Text, instance: str, body: str) -> str:
# type: (Text, str, str) -> str
keypath = parse_crypt_table(zephyr_class, instance) keypath = parse_crypt_table(zephyr_class, instance)
if keypath is None: if keypath is None:
# We can't decrypt it, so we just return the original body # We can't decrypt it, so we just return the original body
@ -337,8 +323,7 @@ def decrypt_zephyr(zephyr_class, instance, body):
signal.signal(signal.SIGCHLD, signal.SIG_IGN) signal.signal(signal.SIGCHLD, signal.SIG_IGN)
return decrypted # type: ignore # bytes, expecting str return decrypted # type: ignore # bytes, expecting str
def process_notice(notice, log): def process_notice(notice: Any, log: Optional[IO[Any]]) -> None:
# type: (Any, Optional[IO[Any]]) -> None
(zsig, body) = parse_zephyr_body(notice.message, notice.format) (zsig, body) = parse_zephyr_body(notice.message, notice.format)
is_personal = False is_personal = False
is_huddle = False is_huddle = False
@ -436,8 +421,7 @@ def process_notice(notice, log):
finally: finally:
os._exit(0) os._exit(0)
def decode_unicode_byte_strings(zeph): def decode_unicode_byte_strings(zeph: Dict[str, Any]) -> Dict[str, str]:
# type: (Dict[str, Any]) -> Dict[str, str]
# 'Any' can be of any type of text that is converted to str. # 'Any' can be of any type of text that is converted to str.
for field in zeph.keys(): for field in zeph.keys():
if isinstance(zeph[field], str): if isinstance(zeph[field], str):
@ -448,14 +432,12 @@ def decode_unicode_byte_strings(zeph):
zeph[field] = decoded zeph[field] = decoded
return zeph return zeph
def quit_failed_initialization(message): def quit_failed_initialization(message: str) -> str:
# type: (str) -> str
logger.error(message) logger.error(message)
maybe_kill_child() maybe_kill_child()
sys.exit(1) sys.exit(1)
def zephyr_init_autoretry(): def zephyr_init_autoretry() -> None:
# type: () -> None
backoff = zulip.RandomExponentialBackoff() backoff = zulip.RandomExponentialBackoff()
while backoff.keep_going(): while backoff.keep_going():
try: try:
@ -470,8 +452,7 @@ def zephyr_init_autoretry():
quit_failed_initialization("Could not initialize Zephyr library, quitting!") quit_failed_initialization("Could not initialize Zephyr library, quitting!")
def zephyr_load_session_autoretry(session_path): def zephyr_load_session_autoretry(session_path: str) -> None:
# type: (str) -> None
backoff = zulip.RandomExponentialBackoff() backoff = zulip.RandomExponentialBackoff()
while backoff.keep_going(): while backoff.keep_going():
try: try:
@ -486,8 +467,7 @@ def zephyr_load_session_autoretry(session_path):
quit_failed_initialization("Could not load saved Zephyr session, quitting!") quit_failed_initialization("Could not load saved Zephyr session, quitting!")
def zephyr_subscribe_autoretry(sub): def zephyr_subscribe_autoretry(sub: Tuple[str, str, str]) -> None:
# type: (Tuple[str, str, str]) -> None
backoff = zulip.RandomExponentialBackoff() backoff = zulip.RandomExponentialBackoff()
while backoff.keep_going(): while backoff.keep_going():
try: try:
@ -502,8 +482,7 @@ def zephyr_subscribe_autoretry(sub):
quit_failed_initialization("Could not subscribe to personals, quitting!") quit_failed_initialization("Could not subscribe to personals, quitting!")
def zephyr_to_zulip(options): def zephyr_to_zulip(options: Any) -> None:
# type: (Any) -> None
if options.use_sessions and os.path.exists(options.session_path): if options.use_sessions and os.path.exists(options.session_path):
logger.info("Loading old session") logger.info("Loading old session")
zephyr_load_session_autoretry(options.session_path) zephyr_load_session_autoretry(options.session_path)
@ -554,8 +533,7 @@ def zephyr_to_zulip(options):
else: else:
process_loop(None) process_loop(None)
def send_zephyr(zwrite_args, content): def send_zephyr(zwrite_args: List[str], content: str) -> Tuple[int, str]:
# type: (List[str], str) -> Tuple[int, str]
p = subprocess.Popen(zwrite_args, stdin=subprocess.PIPE, p = subprocess.Popen(zwrite_args, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate(input=content.encode("utf-8")) stdout, stderr = p.communicate(input=content.encode("utf-8"))
@ -571,16 +549,13 @@ def send_zephyr(zwrite_args, content):
logger.warning("stderr: " + stderr) # type: ignore # str + bytes logger.warning("stderr: " + stderr) # type: ignore # str + bytes
return (p.returncode, stderr) # type: ignore # bytes vs str return (p.returncode, stderr) # type: ignore # bytes vs str
def send_authed_zephyr(zwrite_args, content): def send_authed_zephyr(zwrite_args: List[str], content: str) -> Tuple[int, str]:
# type: (List[str], str) -> Tuple[int, str]
return send_zephyr(zwrite_args, content) return send_zephyr(zwrite_args, content)
def send_unauthed_zephyr(zwrite_args, content): def send_unauthed_zephyr(zwrite_args: List[str], content: str) -> Tuple[int, str]:
# type: (List[str], str) -> Tuple[int, str]
return send_zephyr(zwrite_args + ["-d"], content) return send_zephyr(zwrite_args + ["-d"], content)
def zcrypt_encrypt_content(zephyr_class, instance, content): def zcrypt_encrypt_content(zephyr_class: str, instance: str, content: str) -> Optional[str]:
# type: (str, str, str) -> Optional[str]
keypath = parse_crypt_table(zephyr_class, instance) keypath = parse_crypt_table(zephyr_class, instance)
if keypath is None: if keypath is None:
return None return None
@ -605,8 +580,7 @@ def zcrypt_encrypt_content(zephyr_class, instance, content):
encrypted, _ = p.communicate(input=content) # type: ignore # Optional[bytes] vs string encrypted, _ = p.communicate(input=content) # type: ignore # Optional[bytes] vs string
return encrypted # type: ignore # bytes, expecting Optional[str] return encrypted # type: ignore # bytes, expecting Optional[str]
def forward_to_zephyr(message): def forward_to_zephyr(message: Dict[str, Any]) -> None:
# type: (Dict[str, Any]) -> None
# 'Any' can be of any type of text # 'Any' can be of any type of text
support_heading = "Hi there! This is an automated message from Zulip." support_heading = "Hi there! This is an automated message from Zulip."
support_closing = """If you have any questions, please be in touch through the \ support_closing = """If you have any questions, please be in touch through the \
@ -735,8 +709,7 @@ received it, Zephyr users did not. The error message from zwrite was:
%s""" % (support_heading, stderr, support_closing)) %s""" % (support_heading, stderr, support_closing))
return return
def maybe_forward_to_zephyr(message): def maybe_forward_to_zephyr(message: Dict[str, Any]) -> None:
# type: (Dict[str, Any]) -> None
# The key string can be used to direct any type of text. # The key string can be used to direct any type of text.
if (message["sender_email"] == zulip_account_email): if (message["sender_email"] == zulip_account_email):
if not ((message["type"] == "stream") or if not ((message["type"] == "stream") or
@ -758,8 +731,7 @@ def maybe_forward_to_zephyr(message):
# whole process # whole process
logger.exception("Error forwarding message:") logger.exception("Error forwarding message:")
def zulip_to_zephyr(options): def zulip_to_zephyr(options: int) -> None:
# type: (int) -> None
# Sync messages from zulip to zephyr # Sync messages from zulip to zephyr
logger.info("Starting syncing messages.") logger.info("Starting syncing messages.")
while True: while True:
@ -769,8 +741,7 @@ def zulip_to_zephyr(options):
logger.exception("Error syncing messages:") logger.exception("Error syncing messages:")
time.sleep(1) time.sleep(1)
def subscribed_to_mail_messages(): def subscribed_to_mail_messages() -> bool:
# type: () -> bool
# In case we have lost our AFS tokens and those won't be able to # In case we have lost our AFS tokens and those won't be able to
# parse the Zephyr subs file, first try reading in result of this # parse the Zephyr subs file, first try reading in result of this
# query from the environment so we can avoid the filesystem read. # query from the environment so we can avoid the filesystem read.
@ -784,8 +755,7 @@ def subscribed_to_mail_messages():
os.environ["HUMBUG_FORWARD_MAIL_ZEPHYRS"] = "False" os.environ["HUMBUG_FORWARD_MAIL_ZEPHYRS"] = "False"
return False return False
def add_zulip_subscriptions(verbose): def add_zulip_subscriptions(verbose: bool) -> None:
# type: (bool) -> None
zephyr_subscriptions = set() zephyr_subscriptions = set()
skipped = set() skipped = set()
for (cls, instance, recipient) in parse_zephyr_subs(verbose=verbose): for (cls, instance, recipient) in parse_zephyr_subs(verbose=verbose):
@ -871,12 +841,10 @@ to these .zephyrs.subs lines, please do so via the Zulip
web interface. web interface.
""")) + "\n") """)) + "\n")
def valid_stream_name(name): def valid_stream_name(name: str) -> bool:
# type: (str) -> bool
return name != "" return name != ""
def parse_zephyr_subs(verbose=False): def parse_zephyr_subs(verbose: bool = False) -> Set[Tuple[str, str, str]]:
# type: (bool) -> Set[Tuple[str, str, str]]
zephyr_subscriptions = set() # type: Set[Tuple[str, str, str]] zephyr_subscriptions = set() # type: Set[Tuple[str, str, str]]
subs_file = os.path.join(os.environ["HOME"], ".zephyr.subs") subs_file = os.path.join(os.environ["HOME"], ".zephyr.subs")
if not os.path.exists(subs_file): if not os.path.exists(subs_file):
@ -904,8 +872,7 @@ def parse_zephyr_subs(verbose=False):
zephyr_subscriptions.add((cls.strip(), instance.strip(), recipient.strip())) zephyr_subscriptions.add((cls.strip(), instance.strip(), recipient.strip()))
return zephyr_subscriptions return zephyr_subscriptions
def open_logger(): def open_logger() -> logging.Logger:
# type: () -> logging.Logger
if options.log_path is not None: if options.log_path is not None:
log_file = options.log_path log_file = options.log_path
elif options.forward_class_messages: elif options.forward_class_messages:
@ -930,8 +897,7 @@ def open_logger():
logger.addHandler(file_handler) logger.addHandler(file_handler)
return logger return logger
def configure_logger(logger, direction_name): def configure_logger(logger: logging.Logger, direction_name: Optional[str]) -> None:
# type: (logging.Logger, Optional[str]) -> None
if direction_name is None: if direction_name is None:
log_format = "%(message)s" log_format = "%(message)s"
else: else:
@ -945,8 +911,7 @@ def configure_logger(logger, direction_name):
for handler in root_logger.handlers: for handler in root_logger.handlers:
handler.setFormatter(formatter) handler.setFormatter(formatter)
def parse_args(): def parse_args() -> Tuple[Any, ...]:
# type: () -> Tuple[Any, ...]
parser = optparse.OptionParser() parser = optparse.OptionParser()
parser.add_option('--forward-class-messages', parser.add_option('--forward-class-messages',
default=False, default=False,
@ -1029,8 +994,7 @@ def parse_args():
default=os.path.join(os.environ["HOME"], "Private", ".humbug-api-key")) default=os.path.join(os.environ["HOME"], "Private", ".humbug-api-key"))
return parser.parse_args() return parser.parse_args()
def die_gracefully(signal, frame): def die_gracefully(signal: int, frame: FrameType) -> None:
# type: (int, FrameType) -> None
if CURRENT_STATE == States.ZulipToZephyr or CURRENT_STATE == States.ChildSending: if CURRENT_STATE == States.ZulipToZephyr or CURRENT_STATE == States.ChildSending:
# this is a child process, so we want os._exit (no clean-up necessary) # this is a child process, so we want os._exit (no clean-up necessary)
os._exit(1) os._exit(1)

View file

@ -10,8 +10,7 @@ import itertools
with open("README.md") as fh: with open("README.md") as fh:
long_description = fh.read() long_description = fh.read()
def version(): def version() -> str:
# type: () -> str
version_py = os.path.join(os.path.dirname(__file__), "zulip", "__init__.py") version_py = os.path.join(os.path.dirname(__file__), "zulip", "__init__.py")
with open(version_py) as in_handle: with open(version_py) as in_handle:
version_line = next(itertools.dropwhile(lambda x: not x.startswith("__version__"), version_line = next(itertools.dropwhile(lambda x: not x.startswith("__version__"),
@ -19,8 +18,7 @@ def version():
version = version_line.split('=')[-1].strip().replace('"', '') version = version_line.split('=')[-1].strip().replace('"', '')
return version return version
def recur_expand(target_root, dir): def recur_expand(target_root: Any, dir: Any) -> Generator[Tuple[str, List[str]], None, None]:
# type: (Any, Any) -> Generator[Tuple[str, List[str]], None, None]
for root, _, files in os.walk(dir): for root, _, files in os.walk(dir):
paths = [os.path.join(root, f) for f in files] paths = [os.path.join(root, f) for f in files]
if len(paths): if len(paths):

View file

@ -12,8 +12,7 @@ from unittest.mock import patch
class TestDefaultArguments(TestCase): class TestDefaultArguments(TestCase):
def test_invalid_arguments(self): def test_invalid_arguments(self) -> None:
# type: () -> None
parser = zulip.add_default_arguments(argparse.ArgumentParser(usage="lorem ipsum")) parser = zulip.add_default_arguments(argparse.ArgumentParser(usage="lorem ipsum"))
with self.assertRaises(SystemExit) as cm: # type: ignore # error: "assertRaises" doesn't match argument types with self.assertRaises(SystemExit) as cm: # type: ignore # error: "assertRaises" doesn't match argument types
with patch('sys.stderr', new=io.StringIO()) as mock_stderr: with patch('sys.stderr', new=io.StringIO()) as mock_stderr:
@ -30,8 +29,7 @@ Zulip API configuration:
""")) """))
@patch('os.path.exists', return_value=False) @patch('os.path.exists', return_value=False)
def test_config_path_with_tilde(self, mock_os_path_exists): def test_config_path_with_tilde(self, mock_os_path_exists: bool) -> None:
# type: (bool) -> None
parser = zulip.add_default_arguments(argparse.ArgumentParser(usage="lorem ipsum")) parser = zulip.add_default_arguments(argparse.ArgumentParser(usage="lorem ipsum"))
test_path = '~/zuliprc' test_path = '~/zuliprc'
args = parser.parse_args(['--config-file', test_path]) args = parser.parse_args(['--config-file', test_path])

View file

@ -33,41 +33,35 @@ requests_json_is_function = callable(requests.Response.json)
API_VERSTRING = "v1/" API_VERSTRING = "v1/"
class CountingBackoff: class CountingBackoff:
def __init__(self, maximum_retries=10, timeout_success_equivalent=None, delay_cap=90.0): def __init__(self, maximum_retries: int = 10, timeout_success_equivalent: Optional[float] = None, delay_cap: float = 90.0) -> None:
# type: (int, Optional[float], float) -> None
self.number_of_retries = 0 self.number_of_retries = 0
self.maximum_retries = maximum_retries self.maximum_retries = maximum_retries
self.timeout_success_equivalent = timeout_success_equivalent self.timeout_success_equivalent = timeout_success_equivalent
self.last_attempt_time = 0.0 self.last_attempt_time = 0.0
self.delay_cap = delay_cap self.delay_cap = delay_cap
def keep_going(self): def keep_going(self) -> bool:
# type: () -> bool
self._check_success_timeout() self._check_success_timeout()
return self.number_of_retries < self.maximum_retries return self.number_of_retries < self.maximum_retries
def succeed(self): def succeed(self) -> None:
# type: () -> None
self.number_of_retries = 0 self.number_of_retries = 0
self.last_attempt_time = time.time() self.last_attempt_time = time.time()
def fail(self): def fail(self) -> None:
# type: () -> None
self._check_success_timeout() self._check_success_timeout()
self.number_of_retries = min(self.number_of_retries + 1, self.number_of_retries = min(self.number_of_retries + 1,
self.maximum_retries) self.maximum_retries)
self.last_attempt_time = time.time() self.last_attempt_time = time.time()
def _check_success_timeout(self): def _check_success_timeout(self) -> None:
# type: () -> None
if (self.timeout_success_equivalent is not None and if (self.timeout_success_equivalent is not None and
self.last_attempt_time != 0 and self.last_attempt_time != 0 and
time.time() - self.last_attempt_time > self.timeout_success_equivalent): time.time() - self.last_attempt_time > self.timeout_success_equivalent):
self.number_of_retries = 0 self.number_of_retries = 0
class RandomExponentialBackoff(CountingBackoff): class RandomExponentialBackoff(CountingBackoff):
def fail(self): def fail(self) -> None:
# type: () -> None
super().fail() super().fail()
# Exponential growth with ratio sqrt(2); compute random delay # Exponential growth with ratio sqrt(2); compute random delay
# between x and 2x where x is growing exponentially # between x and 2x where x is growing exponentially
@ -80,16 +74,17 @@ class RandomExponentialBackoff(CountingBackoff):
print(message) print(message)
time.sleep(delay) time.sleep(delay)
def _default_client(): def _default_client() -> str:
# type: () -> str
return "ZulipPython/" + __version__ return "ZulipPython/" + __version__
def add_default_arguments(parser, patch_error_handling=True, allow_provisioning=False): def add_default_arguments(
# type: (argparse.ArgumentParser, bool, bool) -> argparse.ArgumentParser parser: argparse.ArgumentParser,
patch_error_handling: bool = True,
allow_provisioning: bool = False,
) -> argparse.ArgumentParser:
if patch_error_handling: if patch_error_handling:
def custom_error_handling(self, message): def custom_error_handling(self: argparse.ArgumentParser, message: str) -> None:
# type: (argparse.ArgumentParser, str) -> None
self.print_help(sys.stderr) self.print_help(sys.stderr)
self.exit(2, '{}: error: {}\n'.format(self.prog, message)) self.exit(2, '{}: error: {}\n'.format(self.prog, message))
parser.error = types.MethodType(custom_error_handling, parser) # type: ignore # patching function parser.error = types.MethodType(custom_error_handling, parser) # type: ignore # patching function
@ -154,8 +149,7 @@ def add_default_arguments(parser, patch_error_handling=True, allow_provisioning=
# except for the fact that is uses the deprecated `optparse` module. # except for the fact that is uses the deprecated `optparse` module.
# We still keep it for legacy support of out-of-tree bots and integrations # We still keep it for legacy support of out-of-tree bots and integrations
# depending on it. # depending on it.
def generate_option_group(parser, prefix=''): def generate_option_group(parser: optparse.OptionParser, prefix: str = '') -> optparse.OptionGroup:
# type: (optparse.OptionParser, str) -> optparse.OptionGroup
logging.warning("""zulip.generate_option_group is based on optparse, which logging.warning("""zulip.generate_option_group is based on optparse, which
is now deprecated. We recommend migrating to argparse and is now deprecated. We recommend migrating to argparse and
using zulip.add_default_arguments instead.""") using zulip.add_default_arguments instead.""")
@ -209,8 +203,7 @@ def generate_option_group(parser, prefix=''):
file).''') file).''')
return group return group
def init_from_options(options, client=None): def init_from_options(options: Any, client: Optional[str] = None) -> 'Client':
# type: (Any, Optional[str]) -> Client
if getattr(options, 'provision', False): if getattr(options, 'provision', False):
requirements_path = os.path.abspath(os.path.join(sys.path[0], 'requirements.txt')) requirements_path = os.path.abspath(os.path.join(sys.path[0], 'requirements.txt'))
@ -238,8 +231,7 @@ def init_from_options(options, client=None):
client_cert=options.client_cert, client_cert=options.client_cert,
client_cert_key=options.client_cert_key) client_cert_key=options.client_cert_key)
def get_default_config_filename(): def get_default_config_filename() -> Optional[str]:
# type: () -> Optional[str]
if os.environ.get("HOME") is None: if os.environ.get("HOME") is None:
return None return None
@ -250,8 +242,7 @@ def get_default_config_filename():
" mv ~/.humbugrc ~/.zuliprc\n") " mv ~/.humbugrc ~/.zuliprc\n")
return config_file return config_file
def validate_boolean_field(field): def validate_boolean_field(field: Optional[Text]) -> Union[bool, None]:
# type: (Optional[Text]) -> Union[bool, None]
if not isinstance(field, str): if not isinstance(field, str):
return None return None
@ -277,12 +268,11 @@ class UnrecoverableNetworkError(ZulipError):
pass pass
class Client: class Client:
def __init__(self, email=None, api_key=None, config_file=None, def __init__(self, email: Optional[str] = None, api_key: Optional[str] = None, config_file: Optional[str] = None,
verbose=False, retry_on_errors=True, verbose: bool = False, retry_on_errors: bool = True,
site=None, client=None, site: Optional[str] = None, client: Optional[str] = None,
cert_bundle=None, insecure=None, cert_bundle: Optional[str] = None, insecure: Optional[bool] = None,
client_cert=None, client_cert_key=None): client_cert: Optional[str] = None, client_cert_key: Optional[str] = None) -> None:
# type: (Optional[str], Optional[str], Optional[str], bool, bool, Optional[str], Optional[str], Optional[str], Optional[bool], Optional[str], Optional[str]) -> None
if client is None: if client is None:
client = _default_client() client = _default_client()
@ -406,8 +396,7 @@ class Client:
self.has_connected = False self.has_connected = False
def ensure_session(self): def ensure_session(self) -> None:
# type: () -> None
# Check if the session has been created already, and return # Check if the session has been created already, and return
# immediately if so. # immediately if so.
@ -429,8 +418,7 @@ class Client:
session.headers.update({"User-agent": self.get_user_agent()}) session.headers.update({"User-agent": self.get_user_agent()})
self.session = session self.session = session
def get_user_agent(self): def get_user_agent(self) -> str:
# type: () -> str
vendor = '' vendor = ''
vendor_version = '' vendor_version = ''
try: try:
@ -454,9 +442,8 @@ class Client:
vendor_version=vendor_version, vendor_version=vendor_version,
) )
def do_api_query(self, orig_request, url, method="POST", def do_api_query(self, orig_request: Mapping[str, Any], url: str, method: str = "POST",
longpolling=False, files=None, timeout=None): longpolling: bool = False, files: Optional[List[IO[Any]]] = None, timeout: Optional[float] = None) -> Dict[str, Any]:
# type: (Mapping[str, Any], str, str, bool, Optional[List[IO[Any]]], Optional[float]) -> Dict[str, Any]
if files is None: if files is None:
files = [] files = []
@ -490,8 +477,7 @@ class Client:
'failures': 0, 'failures': 0,
} # type: Dict[str, Any] } # type: Dict[str, Any]
def error_retry(error_string): def error_retry(error_string: str) -> bool:
# type: (str) -> bool
if not self.retry_on_errors or query_state["failures"] >= 10: if not self.retry_on_errors or query_state["failures"] >= 10:
return False return False
if self.verbose: if self.verbose:
@ -507,8 +493,7 @@ class Client:
query_state["failures"] += 1 query_state["failures"] += 1
return True return True
def end_error_retry(succeeded): def end_error_retry(succeeded: bool) -> None:
# type: (bool) -> None
if query_state["had_error_retry"] and self.verbose: if query_state["had_error_retry"] and self.verbose:
if succeeded: if succeeded:
print("Success!") print("Success!")
@ -589,9 +574,8 @@ class Client:
return {'msg': "Unexpected error from the server", "result": "http-error", return {'msg': "Unexpected error from the server", "result": "http-error",
"status_code": res.status_code} "status_code": res.status_code}
def call_endpoint(self, url=None, method="POST", request=None, def call_endpoint(self, url: Optional[str] = None, method: str = "POST", request: Optional[Dict[str, Any]] = None,
longpolling=False, files=None, timeout=None): longpolling: bool = False, files: Optional[List[IO[Any]]] = None, timeout: Optional[float] = None) -> Dict[str, Any]:
# type: (Optional[str], str, Optional[Dict[str, Any]], bool, Optional[List[IO[Any]]], Optional[float]) -> Dict[str, Any]
if request is None: if request is None:
request = dict() request = dict()
marshalled_request = {} marshalled_request = {}
@ -602,13 +586,16 @@ class Client:
return self.do_api_query(marshalled_request, versioned_url, method=method, return self.do_api_query(marshalled_request, versioned_url, method=method,
longpolling=longpolling, files=files, timeout=timeout) longpolling=longpolling, files=files, timeout=timeout)
def call_on_each_event(self, callback, event_types=None, narrow=None): def call_on_each_event(
# type: (Callable[[Dict[str, Any]], None], Optional[List[str]], Optional[List[List[str]]]) -> None self,
callback: Callable[[Dict[str, Any]], None],
event_types: Optional[List[str]] = None,
narrow: Optional[List[List[str]]] = None,
) -> None:
if narrow is None: if narrow is None:
narrow = [] narrow = []
def do_register(): def do_register() -> Tuple[str, int]:
# type: () -> Tuple[str, int]
while True: while True:
if event_types is None: if event_types is None:
res = self.register() res = self.register()
@ -666,16 +653,13 @@ class Client:
last_event_id = max(last_event_id, int(event['id'])) last_event_id = max(last_event_id, int(event['id']))
callback(event) callback(event)
def call_on_each_message(self, callback): def call_on_each_message(self, callback: Callable[[Dict[str, Any]], None]) -> None:
# type: (Callable[[Dict[str, Any]], None]) -> None def event_callback(event: Dict[str, Any]) -> None:
def event_callback(event):
# type: (Dict[str, Any]) -> None
if event['type'] == 'message': if event['type'] == 'message':
callback(event['message']) callback(event['message'])
self.call_on_each_event(event_callback, ['message']) self.call_on_each_event(event_callback, ['message'])
def get_messages(self, message_filters): def get_messages(self, message_filters: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
''' '''
See examples/get-messages for example usage See examples/get-messages for example usage
''' '''
@ -685,8 +669,7 @@ class Client:
request=message_filters request=message_filters
) )
def check_messages_match_narrow(self, **request): def check_messages_match_narrow(self, **request: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -702,8 +685,7 @@ class Client:
request=request request=request
) )
def get_raw_message(self, message_id): def get_raw_message(self, message_id: int) -> Dict[str, str]:
# type: (int) -> Dict[str, str]
''' '''
See examples/get-raw-message for example usage See examples/get-raw-message for example usage
''' '''
@ -712,8 +694,7 @@ class Client:
method='GET' method='GET'
) )
def send_message(self, message_data): def send_message(self, message_data: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
''' '''
See examples/send-message for example usage. See examples/send-message for example usage.
''' '''
@ -722,8 +703,7 @@ class Client:
request=message_data, request=message_data,
) )
def upload_file(self, file): def upload_file(self, file: IO[Any]) -> Dict[str, Any]:
# type: (IO[Any]) -> Dict[str, Any]
''' '''
See examples/upload-file for example usage. See examples/upload-file for example usage.
''' '''
@ -732,8 +712,7 @@ class Client:
files=[file] files=[file]
) )
def get_attachments(self): def get_attachments(self) -> Dict[str, Any]:
# type: () -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -745,8 +724,7 @@ class Client:
method='GET' method='GET'
) )
def update_message(self, message_data): def update_message(self, message_data: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
''' '''
See examples/edit-message for example usage. See examples/edit-message for example usage.
''' '''
@ -756,8 +734,7 @@ class Client:
request=message_data, request=message_data,
) )
def delete_message(self, message_id): def delete_message(self, message_id: int) -> Dict[str, Any]:
# type: (int) -> Dict[str, Any]
''' '''
See examples/delete-message for example usage. See examples/delete-message for example usage.
''' '''
@ -766,8 +743,7 @@ class Client:
method='DELETE' method='DELETE'
) )
def update_message_flags(self, update_data): def update_message_flags(self, update_data: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
''' '''
See examples/update-flags for example usage. See examples/update-flags for example usage.
''' '''
@ -777,8 +753,7 @@ class Client:
request=update_data request=update_data
) )
def mark_all_as_read(self): def mark_all_as_read(self) -> Dict[str, Any]:
# type: () -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -790,8 +765,7 @@ class Client:
method='POST', method='POST',
) )
def mark_stream_as_read(self, stream_id): def mark_stream_as_read(self, stream_id: int) -> Dict[str, Any]:
# type: (int) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -804,8 +778,7 @@ class Client:
request={'stream_id': stream_id}, request={'stream_id': stream_id},
) )
def mark_topic_as_read(self, stream_id, topic_name): def mark_topic_as_read(self, stream_id: int, topic_name: str) -> Dict[str, Any]:
# type: (int, str) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -821,8 +794,7 @@ class Client:
}, },
) )
def get_message_history(self, message_id): def get_message_history(self, message_id: int) -> Dict[str, Any]:
# type: (int) -> Dict[str, Any]
''' '''
See examples/message-history for example usage. See examples/message-history for example usage.
''' '''
@ -831,8 +803,7 @@ class Client:
method='GET' method='GET'
) )
def add_reaction(self, reaction_data): def add_reaction(self, reaction_data: Dict[str, str]) -> Dict[str, Any]:
# type: (Dict[str, str]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -850,8 +821,7 @@ class Client:
request=reaction_data, request=reaction_data,
) )
def remove_reaction(self, reaction_data): def remove_reaction(self, reaction_data: Dict[str, str]) -> Dict[str, Any]:
# type: (Dict[str, str]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -869,8 +839,7 @@ class Client:
request=reaction_data, request=reaction_data,
) )
def get_realm_emoji(self): def get_realm_emoji(self) -> Dict[str, Any]:
# type: () -> Dict[str, Any]
''' '''
See examples/realm-emoji for example usage. See examples/realm-emoji for example usage.
''' '''
@ -879,8 +848,7 @@ class Client:
method='GET' method='GET'
) )
def upload_custom_emoji(self, emoji_name, file_obj): def upload_custom_emoji(self, emoji_name: str, file_obj: IO[Any]) -> Dict[str, Any]:
# type: (str, IO[Any]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -893,8 +861,7 @@ class Client:
files=[file_obj] files=[file_obj]
) )
def get_realm_filters(self): def get_realm_filters(self) -> Dict[str, Any]:
# type: () -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -906,8 +873,7 @@ class Client:
method='GET', method='GET',
) )
def add_realm_filter(self, pattern, url_format_string): def add_realm_filter(self, pattern: str, url_format_string: str) -> Dict[str, Any]:
# type: (str, str) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -923,8 +889,7 @@ class Client:
}, },
) )
def remove_realm_filter(self, filter_id): def remove_realm_filter(self, filter_id: int) -> Dict[str, Any]:
# type: (int) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -936,8 +901,7 @@ class Client:
method='DELETE', method='DELETE',
) )
def get_server_settings(self): def get_server_settings(self) -> Dict[str, Any]:
# type: () -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -949,8 +913,7 @@ class Client:
method='GET', method='GET',
) )
def get_events(self, **request): def get_events(self, **request: Any) -> Dict[str, Any]:
# type: (**Any) -> Dict[str, Any]
''' '''
See the register() method for example usage. See the register() method for example usage.
''' '''
@ -961,8 +924,12 @@ class Client:
request=request, request=request,
) )
def register(self, event_types=None, narrow=None, **kwargs): def register(
# type: (Optional[Iterable[str]], Optional[List[List[str]]], **Any) -> Dict[str, Any] self,
event_types: Optional[Iterable[str]] = None,
narrow: Optional[List[List[str]]] = None,
**kwargs: Any
) -> Dict[str, Any]:
''' '''
Example usage: Example usage:
@ -986,8 +953,7 @@ class Client:
request=request, request=request,
) )
def deregister(self, queue_id, timeout=None): def deregister(self, queue_id: str, timeout: Optional[float] = None) -> Dict[str, Any]:
# type: (str, Optional[float]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1005,8 +971,7 @@ class Client:
timeout=timeout, timeout=timeout,
) )
def get_profile(self, request=None): def get_profile(self, request: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
# type: (Optional[Dict[str, Any]]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1019,8 +984,7 @@ class Client:
request=request, request=request,
) )
def get_user_presence(self, email): def get_user_presence(self, email: str) -> Dict[str, Any]:
# type: (str) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1032,8 +996,7 @@ class Client:
method='GET', method='GET',
) )
def get_realm_presence(self): def get_realm_presence(self) -> Dict[str, Any]:
# type: () -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1045,8 +1008,7 @@ class Client:
method='GET', method='GET',
) )
def update_presence(self, request): def update_presence(self, request: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1063,8 +1025,7 @@ class Client:
request=request, request=request,
) )
def get_streams(self, **request): def get_streams(self, **request: Any) -> Dict[str, Any]:
# type: (**Any) -> Dict[str, Any]
''' '''
See examples/get-public-streams for example usage. See examples/get-public-streams for example usage.
''' '''
@ -1074,8 +1035,7 @@ class Client:
request=request, request=request,
) )
def update_stream(self, stream_data): def update_stream(self, stream_data: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
''' '''
See examples/edit-stream for example usage. See examples/edit-stream for example usage.
''' '''
@ -1086,8 +1046,7 @@ class Client:
request=stream_data, request=stream_data,
) )
def delete_stream(self, stream_id): def delete_stream(self, stream_id: int) -> Dict[str, Any]:
# type: (int) -> Dict[str, Any]
''' '''
See examples/delete-stream for example usage. See examples/delete-stream for example usage.
''' '''
@ -1096,8 +1055,7 @@ class Client:
method='DELETE', method='DELETE',
) )
def add_default_stream(self, stream_id): def add_default_stream(self, stream_id: int) -> Dict[str, Any]:
# type: (int) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1111,8 +1069,7 @@ class Client:
request={'stream_id': stream_id}, request={'stream_id': stream_id},
) )
def get_user_by_id(self, user_id, **request): def get_user_by_id(self, user_id: int, **request: Any) -> Dict[str, Any]:
# type: (int, **Any) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1126,8 +1083,7 @@ class Client:
request=request, request=request,
) )
def deactivate_user_by_id(self, user_id): def deactivate_user_by_id(self, user_id: int) -> Dict[str, Any]:
# type: (int) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1140,8 +1096,7 @@ class Client:
method='DELETE', method='DELETE',
) )
def reactivate_user_by_id(self, user_id): def reactivate_user_by_id(self, user_id: int) -> Dict[str, Any]:
# type: (int) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1154,8 +1109,7 @@ class Client:
method='POST', method='POST',
) )
def update_user_by_id(self, user_id, **request): def update_user_by_id(self, user_id: int, **request: Any) -> Dict[str, Any]:
# type: (int, **Any) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1173,8 +1127,7 @@ class Client:
request=request request=request
) )
def get_members(self, request=None): def get_members(self, request: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
# type: (Optional[Dict[str, Any]]) -> Dict[str, Any]
''' '''
See examples/list-members for example usage. See examples/list-members for example usage.
''' '''
@ -1184,8 +1137,7 @@ class Client:
request=request, request=request,
) )
def get_alert_words(self): def get_alert_words(self) -> Dict[str, Any]:
# type: () -> Dict[str, Any]
''' '''
See examples/alert-words for example usage. See examples/alert-words for example usage.
''' '''
@ -1194,8 +1146,7 @@ class Client:
method='GET' method='GET'
) )
def add_alert_words(self, alert_words): def add_alert_words(self, alert_words: List[str]) -> Dict[str, Any]:
# type: (List[str]) -> Dict[str, Any]
''' '''
See examples/alert-words for example usage. See examples/alert-words for example usage.
''' '''
@ -1207,8 +1158,7 @@ class Client:
} }
) )
def remove_alert_words(self, alert_words): def remove_alert_words(self, alert_words: List[str]) -> Dict[str, Any]:
# type: (List[str]) -> Dict[str, Any]
''' '''
See examples/alert-words for example usage. See examples/alert-words for example usage.
''' '''
@ -1220,8 +1170,7 @@ class Client:
} }
) )
def list_subscriptions(self, request=None): def list_subscriptions(self, request: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
# type: (Optional[Dict[str, Any]]) -> Dict[str, Any]
''' '''
See examples/list-subscriptions for example usage. See examples/list-subscriptions for example usage.
''' '''
@ -1231,8 +1180,7 @@ class Client:
request=request, request=request,
) )
def add_subscriptions(self, streams, **kwargs): def add_subscriptions(self, streams: Iterable[Dict[str, Any]], **kwargs: Any) -> Dict[str, Any]:
# type: (Iterable[Dict[str, Any]], **Any) -> Dict[str, Any]
''' '''
See examples/subscribe for example usage. See examples/subscribe for example usage.
''' '''
@ -1246,8 +1194,7 @@ class Client:
request=request, request=request,
) )
def remove_subscriptions(self, streams, principals=None): def remove_subscriptions(self, streams: Iterable[str], principals: Optional[Iterable[str]] = None) -> Dict[str, Any]:
# type: (Iterable[str], Optional[Iterable[str]]) -> Dict[str, Any]
''' '''
See examples/unsubscribe for example usage. See examples/unsubscribe for example usage.
''' '''
@ -1264,8 +1211,7 @@ class Client:
request=request, request=request,
) )
def mute_topic(self, request): def mute_topic(self, request: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
''' '''
See examples/mute-topic for example usage. See examples/mute-topic for example usage.
''' '''
@ -1275,8 +1221,7 @@ class Client:
request=request request=request
) )
def update_subscription_settings(self, subscription_data): def update_subscription_settings(self, subscription_data: List[Dict[str, Any]]) -> Dict[str, Any]:
# type: (List[Dict[str, Any]]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1298,8 +1243,7 @@ class Client:
request={'subscription_data': subscription_data} request={'subscription_data': subscription_data}
) )
def update_notification_settings(self, notification_settings): def update_notification_settings(self, notification_settings: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1315,8 +1259,7 @@ class Client:
request=notification_settings, request=notification_settings,
) )
def get_stream_id(self, stream): def get_stream_id(self, stream: str) -> Dict[str, Any]:
# type: (str) -> Dict[str, Any]
''' '''
Example usage: client.get_stream_id('devel') Example usage: client.get_stream_id('devel')
''' '''
@ -1328,8 +1271,7 @@ class Client:
request=None, request=None,
) )
def get_stream_topics(self, stream_id): def get_stream_topics(self, stream_id: int) -> Dict[str, Any]:
# type: (int) -> Dict[str, Any]
''' '''
See examples/get-stream-topics for example usage. See examples/get-stream-topics for example usage.
''' '''
@ -1338,8 +1280,7 @@ class Client:
method='GET' method='GET'
) )
def get_user_groups(self): def get_user_groups(self) -> Dict[str, Any]:
# type: () -> Dict[str, Any]
''' '''
Example usage: Example usage:
>>> client.get_user_groups() >>> client.get_user_groups()
@ -1350,8 +1291,7 @@ class Client:
method='GET', method='GET',
) )
def create_user_group(self, group_data): def create_user_group(self, group_data: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
>>> client.create_user_group({ >>> client.create_user_group({
@ -1367,8 +1307,7 @@ class Client:
request=group_data, request=group_data,
) )
def update_user_group(self, group_data): def update_user_group(self, group_data: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1385,8 +1324,7 @@ class Client:
request=group_data, request=group_data,
) )
def remove_user_group(self, group_id): def remove_user_group(self, group_id: int) -> Dict[str, Any]:
# type: (int) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1398,8 +1336,7 @@ class Client:
method='DELETE', method='DELETE',
) )
def update_user_group_members(self, group_data): def update_user_group_members(self, group_data: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1415,8 +1352,7 @@ class Client:
request=group_data, request=group_data,
) )
def get_subscribers(self, **request): def get_subscribers(self, **request: Any) -> Dict[str, Any]:
# type: (**Any) -> Dict[str, Any]
''' '''
Example usage: client.get_subscribers(stream='devel') Example usage: client.get_subscribers(stream='devel')
''' '''
@ -1432,8 +1368,7 @@ class Client:
request=request, request=request,
) )
def render_message(self, request=None): def render_message(self, request: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
# type: (Optional[Dict[str, Any]]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1446,8 +1381,7 @@ class Client:
request=request, request=request,
) )
def create_user(self, request=None): def create_user(self, request: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
# type: (Optional[Dict[str, Any]]) -> Dict[str, Any]
''' '''
See examples/create-user for example usage. See examples/create-user for example usage.
''' '''
@ -1457,8 +1391,7 @@ class Client:
request=request, request=request,
) )
def update_storage(self, request): def update_storage(self, request: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1472,8 +1405,7 @@ class Client:
request=request, request=request,
) )
def get_storage(self, request=None): def get_storage(self, request: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
# type: (Optional[Dict[str, Any]]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
@ -1489,8 +1421,7 @@ class Client:
request=request, request=request,
) )
def set_typing_status(self, request): def set_typing_status(self, request: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
''' '''
Example usage: Example usage:
>>> client.set_typing_status({ >>> client.set_typing_status({
@ -1510,21 +1441,18 @@ class ZulipStream:
A Zulip stream-like object A Zulip stream-like object
""" """
def __init__(self, type, to, subject, **kwargs): def __init__(self, type: str, to: str, subject: str, **kwargs: Any) -> None:
# type: (str, str, str, **Any) -> None
self.client = Client(**kwargs) self.client = Client(**kwargs)
self.type = type self.type = type
self.to = to self.to = to
self.subject = subject self.subject = subject
def write(self, content): def write(self, content: str) -> None:
# type: (str) -> None
message = {"type": self.type, message = {"type": self.type,
"to": self.to, "to": self.to,
"subject": self.subject, "subject": self.subject,
"content": content} "content": content}
self.client.send_message(message) self.client.send_message(message)
def flush(self): def flush(self) -> None:
# type: () -> None
pass pass

View file

@ -4,8 +4,7 @@ import zulip
import argparse import argparse
def main(): def main() -> None:
# type: () -> None
usage = """zulip-api-examples [script_name] usage = """zulip-api-examples [script_name]
Prints the path to the Zulip API example scripts.""" Prints the path to the Zulip API example scripts."""

View file

@ -14,8 +14,7 @@ Example: edit-stream --stream-id=3 --history-public-to-subscribers
""" """
def quote(string): def quote(string: str) -> str:
# type: (str) -> str
return '"{}"'.format(string) return '"{}"'.format(string)

View file

@ -20,8 +20,7 @@ options = parser.parse_args()
client = zulip.init_from_options(options) client = zulip.init_from_options(options)
def print_event(event): def print_event(event: Dict[str, Any]) -> None:
# type: (Dict[str, Any]) -> None
print(event) print(event)
# This is a blocking call, and will continuously poll for new events # This is a blocking call, and will continuously poll for new events

View file

@ -20,8 +20,7 @@ options = parser.parse_args()
client = zulip.init_from_options(options) client = zulip.init_from_options(options)
def print_message(message): def print_message(message: Dict[str, Any]) -> None:
# type: (Dict[str, Any]) -> None
print(message) print(message)
# This is a blocking call, and will continuously poll for new messages # This is a blocking call, and will continuously poll for new messages

View file

@ -51,17 +51,14 @@ streams_to_watch = ['new members']
# These streams will cause anyone who sends a message there to be removed from the watchlist # These streams will cause anyone who sends a message there to be removed from the watchlist
streams_to_cancel = ['development help'] streams_to_cancel = ['development help']
def get_watchlist(): def get_watchlist() -> List[Any]:
# type: () -> List[Any]
storage = client.get_storage() storage = client.get_storage()
return list(storage['storage'].values()) return list(storage['storage'].values())
def set_watchlist(watchlist): def set_watchlist(watchlist: List[str]) -> None:
# type: (List[str]) -> None
client.update_storage({'storage': dict(enumerate(watchlist))}) client.update_storage({'storage': dict(enumerate(watchlist))})
def handle_event(event): def handle_event(event: Dict[str, Any]) -> None:
# type: (Dict[str, Any]) -> None
try: try:
if event['type'] == 'realm_user' and event['op'] == 'add': if event['type'] == 'realm_user' and event['op'] == 'add':
watchlist = get_watchlist() watchlist = get_watchlist()
@ -87,8 +84,7 @@ def handle_event(event):
print(err) print(err)
def start_event_handler(): def start_event_handler() -> None:
# type: () -> None
print("Starting event handler...") print("Starting event handler...")
client.call_on_each_event(handle_event, event_types=['realm_user', 'message']) client.call_on_each_event(handle_event, event_types=['realm_user', 'message'])

View file

@ -13,8 +13,7 @@ logging.basicConfig()
log = logging.getLogger('zulip-send') log = logging.getLogger('zulip-send')
def do_send_message(client, message_data): def do_send_message(client: zulip.Client, message_data: Dict[str, Any]) -> bool:
# type: (zulip.Client, Dict[str, Any]) -> bool
'''Sends a message and optionally prints status about the same.''' '''Sends a message and optionally prints status about the same.'''
if message_data['type'] == 'stream': if message_data['type'] == 'stream':
@ -30,8 +29,7 @@ def do_send_message(client, message_data):
log.error(response['msg']) log.error(response['msg'])
return False return False
def main(): def main() -> int:
# type: () -> int
usage = """zulip-send [options] [recipient...] usage = """zulip-send [options] [recipient...]
Sends a message to specified recipients. Sends a message to specified recipients.

View file

@ -78,8 +78,7 @@ except ImportError:
from importlib import import_module from importlib import import_module
# Manual dependency check # Manual dependency check
def check_dependency_manually(module_name, version=None): def check_dependency_manually(module_name: str, version: Optional[str] = None) -> None:
# type: (str, Optional[str]) -> None
try: try:
module = import_module(module_name) # type: Any module = import_module(module_name) # type: Any
if version is not None: if version is not None:

View file

@ -9,16 +9,14 @@ import glob
import pip import pip
from typing import Iterator from typing import Iterator
def get_bot_paths(): def get_bot_paths() -> Iterator[str]:
# type: () -> Iterator[str]
current_dir = os.path.dirname(os.path.abspath(__file__)) current_dir = os.path.dirname(os.path.abspath(__file__))
bots_dir = os.path.join(current_dir, "bots") bots_dir = os.path.join(current_dir, "bots")
bots_subdirs = map(lambda d: os.path.abspath(d), glob.glob(bots_dir + '/*')) bots_subdirs = map(lambda d: os.path.abspath(d), glob.glob(bots_dir + '/*'))
paths = filter(lambda d: os.path.isdir(d), bots_subdirs) paths = filter(lambda d: os.path.isdir(d), bots_subdirs)
return paths return paths
def provision_bot(path_to_bot, force): def provision_bot(path_to_bot: str, force: bool) -> None:
# type: (str, bool) -> None
req_path = os.path.join(path_to_bot, 'requirements.txt') req_path = os.path.join(path_to_bot, 'requirements.txt')
if os.path.isfile(req_path): if os.path.isfile(req_path):
bot_name = os.path.basename(path_to_bot) bot_name = os.path.basename(path_to_bot)
@ -36,8 +34,7 @@ def provision_bot(path_to_bot, force):
logging.info('Installed dependencies successfully.') logging.info('Installed dependencies successfully.')
def parse_args(available_bots): def parse_args(available_bots: Iterator[str]) -> argparse.Namespace:
# type: (Iterator[str]) -> argparse.Namespace
usage = """ usage = """
Installs dependencies of bots in the bots/<bot_name> Installs dependencies of bots in the bots/<bot_name>
directories. Add a requirements.txt file in a bot's folder directories. Add a requirements.txt file in a bot's folder
@ -71,8 +68,7 @@ Example: ./provision.py helloworld xkcd wikipedia
return parser.parse_args() return parser.parse_args()
def main(): def main() -> None:
# type: () -> None
options = parse_args(available_bots=get_bot_paths()) options = parse_args(available_bots=get_bot_paths())
if not options.quiet: if not options.quiet:

View file

@ -7,8 +7,7 @@ from unittest.mock import patch
from typing import Any, Dict, List from typing import Any, Dict, List
@contextmanager @contextmanager
def mock_http_conversation(http_data): def mock_http_conversation(http_data: Dict[str, Any]) -> Any:
# type: (Dict[str, Any]) -> Any
""" """
Use this context manager to mock and verify a bot's HTTP Use this context manager to mock and verify a bot's HTTP
requests to the third-party API (and provide the correct requests to the third-party API (and provide the correct
@ -18,8 +17,7 @@ def mock_http_conversation(http_data):
http_data should be fixtures data formatted like the data http_data should be fixtures data formatted like the data
in zulip_bots/zulip_bots/bots/giphy/fixtures/test_normal.json in zulip_bots/zulip_bots/bots/giphy/fixtures/test_normal.json
""" """
def get_response(http_response, http_headers, is_raw_response): def get_response(http_response: Dict[str, Any], http_headers: Dict[str, Any], is_raw_response: bool) -> Any:
# type: (Dict[str, Any], Dict[str, Any], bool) -> Any
"""Creates a fake `requests` Response with a desired HTTP response and """Creates a fake `requests` Response with a desired HTTP response and
response headers. response headers.
""" """
@ -31,8 +29,7 @@ def mock_http_conversation(http_data):
mock_result.status_code = http_headers.get('status', 200) mock_result.status_code = http_headers.get('status', 200)
return mock_result return mock_result
def assert_called_with_fields(mock_result, http_request, fields, meta): def assert_called_with_fields(mock_result: Any, http_request: Dict[str, Any], fields: List[str], meta: Dict[str, Any]) -> None:
# type: (Any, Dict[str, Any], List[str], Dict[str, Any]) -> None
"""Calls `assert_called_with` on a mock object using an HTTP request. """Calls `assert_called_with` on a mock object using an HTTP request.
Uses `fields` to determine which keys to look for in HTTP request and Uses `fields` to determine which keys to look for in HTTP request and
to test; if a key is in `fields`, e.g., 'headers', it will be used in to test; if a key is in `fields`, e.g., 'headers', it will be used in
@ -101,10 +98,8 @@ def mock_http_conversation(http_data):
) )
@contextmanager @contextmanager
def mock_request_exception(): def mock_request_exception() -> Any:
# type: () -> Any def assert_mock_called(mock_result: Any) -> None:
def assert_mock_called(mock_result):
# type: (Any) -> None
assert mock_result.called assert mock_result.called
with patch('requests.get') as mock_get: with patch('requests.get') as mock_get:

View file

@ -15,8 +15,7 @@ directory structure is currently:
fixtures/ fixtures/
''' '''
def get_bot_message_handler(bot_name): def get_bot_message_handler(bot_name: str) -> Any:
# type: (str) -> Any
# message_handler is of type 'Any', since it can contain any bot's # message_handler is of type 'Any', since it can contain any bot's
# handler class. Eventually, we want bot's handler classes to # handler class. Eventually, we want bot's handler classes to
# inherit from a common prototype specifying the handle_message # inherit from a common prototype specifying the handle_message
@ -24,8 +23,7 @@ def get_bot_message_handler(bot_name):
lib_module = import_module('zulip_bots.bots.{bot}.{bot}'.format(bot=bot_name)) # type: Any lib_module = import_module('zulip_bots.bots.{bot}.{bot}'.format(bot=bot_name)) # type: Any
return lib_module.handler_class() return lib_module.handler_class()
def read_bot_fixture_data(bot_name, test_name): def read_bot_fixture_data(bot_name: str, test_name: str) -> Dict[str, Any]:
# type: (str, str) -> Dict[str, Any]
base_path = os.path.realpath(os.path.join(os.path.dirname( base_path = os.path.realpath(os.path.join(os.path.dirname(
os.path.abspath(__file__)), 'bots', bot_name, 'fixtures')) os.path.abspath(__file__)), 'bots', bot_name, 'fixtures'))
http_data_path = os.path.join(base_path, '{}.json'.format(test_name)) http_data_path = os.path.join(base_path, '{}.json'.format(test_name))

View file

@ -53,13 +53,11 @@ class StubBotHandler:
def update_message(self, message: Dict[str, Any]) -> None: def update_message(self, message: Dict[str, Any]) -> None:
self.message_server.update(message) self.message_server.update(message)
def upload_file_from_path(self, file_path): def upload_file_from_path(self, file_path: str) -> Dict[str, Any]:
# type: (str) -> Dict[str, Any]
with open(file_path, 'rb') as file: with open(file_path, 'rb') as file:
return self.message_server.upload_file(file) return self.message_server.upload_file(file)
def upload_file(self, file): def upload_file(self, file: IO[Any]) -> Dict[str, Any]:
# type: (IO[Any]) -> Dict[str, Any]
return self.message_server.upload_file(file) return self.message_server.upload_file(file)
class BotQuitException(Exception): class BotQuitException(Exception):
@ -132,8 +130,7 @@ class BotTestCase(unittest.TestCase):
return bot, bot_handler return bot, bot_handler
def get_response(self, message): def get_response(self, message: Dict[str, Any]) -> Dict[str, Any]:
# type: (Dict[str, Any]) -> Dict[str, Any]
bot, bot_handler = self._get_handlers() bot, bot_handler = self._get_handlers()
bot_handler.reset_transcript() bot_handler.reset_transcript()
bot.handle_message(message, bot_handler) bot.handle_message(message, bot_handler)

View file

@ -56,8 +56,7 @@ class TestDefaultArguments(TestCase):
expected_bot_dir_path = '/path/to' expected_bot_dir_path = '/path/to'
self._test_adding_bot_parent_dir_to_sys_path(bot_qualifier=bot_path, bot_dir_path=expected_bot_dir_path) self._test_adding_bot_parent_dir_to_sys_path(bot_qualifier=bot_path, bot_dir_path=expected_bot_dir_path)
def _test_adding_bot_parent_dir_to_sys_path(self, bot_qualifier, bot_dir_path): def _test_adding_bot_parent_dir_to_sys_path(self, bot_qualifier: str, bot_dir_path: str) -> None:
# type: (str, str) -> None
with patch('sys.argv', ['zulip-run-bot', bot_qualifier, '--config-file', '/path/to/config']): with patch('sys.argv', ['zulip-run-bot', bot_qualifier, '--config-file', '/path/to/config']):
with patch('zulip_bots.finder.import_module_from_source', return_value=mock.Mock()): with patch('zulip_bots.finder.import_module_from_source', return_value=mock.Mock()):
with patch('zulip_bots.run.run_message_handler_for_bot'): with patch('zulip_bots.run.run_message_handler_for_bot'):

View file

@ -59,8 +59,7 @@ except ImportError:
from importlib import import_module from importlib import import_module
# Manual dependency check # Manual dependency check
def check_dependency_manually(module_name, version=None): def check_dependency_manually(module_name: str, version: Optional[str] = None) -> None:
# type: (str, Optional[str]) -> None
try: try:
module = import_module(module_name) # type: Any module = import_module(module_name) # type: Any
if version is not None: if version is not None: