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:
parent
7c5f73dce9
commit
5428c5f296
12
tools/lint
12
tools/lint
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
24
tools/review
24
tools/review
|
@ -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:
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'])
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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'):
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue