typing: Convert function type annotations to Python 3 style.

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

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

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

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

View file

@ -9,9 +9,8 @@ from typing import Any, Dict
class IRCBot(irc.bot.SingleServerIRCBot):
reactor_class = AioReactor
def __init__(self, zulip_client, stream, topic, channel,
nickname, server, nickserv_password='', port=6667):
# type: (Any, str, str, irc.bot.Channel, str, str, str, int) -> None
def __init__(self, zulip_client: Any, stream: str, topic: str, channel: irc.bot.Channel,
nickname: str, server: str, nickserv_password: str = '', port: int = 6667) -> None:
self.channel = channel # type: irc.bot.Channel
self.zulip_client = zulip_client
self.stream = stream
@ -23,13 +22,11 @@ class IRCBot(irc.bot.SingleServerIRCBot):
# Initialize IRC bot after proper connection to Zulip server has been confirmed.
irc.bot.SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname)
def zulip_sender(self, sender_string):
# type: (str) -> str
def zulip_sender(self, sender_string: str) -> str:
nick = sender_string.split("!")[0]
return nick + "@" + self.IRC_DOMAIN
def connect(self, *args, **kwargs):
# type: (*Any, **Any) -> None
def connect(self, *args: Any, **kwargs: Any) -> None:
# Taken from
# https://github.com/jaraco/irc/blob/master/irc/client_aio.py,
# 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")
def check_subscription_or_die(self):
# type: () -> None
def check_subscription_or_die(self) -> None:
resp = self.zulip_client.list_subscriptions()
if resp["result"] != "success":
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,))
exit(1)
def on_nicknameinuse(self, c, e):
# type: (ServerConnection, Event) -> None
def on_nicknameinuse(self, c: ServerConnection, e: Event) -> None:
c.nick(c.get_nickname().replace("_zulip", "__zulip"))
def on_welcome(self, c, e):
# type: (ServerConnection, Event) -> None
def on_welcome(self, c: ServerConnection, e: Event) -> None:
if len(self.nickserv_password) > 0:
msg = 'identify %s' % (self.nickserv_password,)
c.privmsg('NickServ', msg)
c.join(self.channel)
def forward_to_irc(msg):
# type: (Dict[str, Any]) -> None
def forward_to_irc(msg: Dict[str, Any]) -> None:
not_from_zulip_bot = msg["sender_email"] != self.zulip_client.email
if not not_from_zulip_bot:
# 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.start()
def on_privmsg(self, c, e):
# type: (ServerConnection, Event) -> None
def on_privmsg(self, c: ServerConnection, e: Event) -> None:
content = e.arguments[0]
sender = self.zulip_sender(e.source)
if sender.endswith("_zulip@" + self.IRC_DOMAIN):
@ -103,8 +95,7 @@ class IRCBot(irc.bot.SingleServerIRCBot):
"content": content,
}))
def on_pubmsg(self, c, e):
# type: (ServerConnection, Event) -> None
def on_pubmsg(self, c: ServerConnection, e: Event) -> None:
content = e.arguments[0]
sender = self.zulip_sender(e.source)
if sender.endswith("_zulip@" + self.IRC_DOMAIN):
@ -119,12 +110,10 @@ class IRCBot(irc.bot.SingleServerIRCBot):
"content": "**{}**: {}".format(sender, content),
}))
def on_dccmsg(self, c, e):
# type: (ServerConnection, Event) -> None
def on_dccmsg(self, c: ServerConnection, e: Event) -> None:
c.privmsg("You said: " + e.arguments[0])
def on_dccchat(self, c, e):
# type: (ServerConnection, Event) -> None
def on_dccchat(self, c: ServerConnection, e: Event) -> None:
if len(e.arguments) != 2:
return
args = e.arguments[1].split()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -79,8 +79,7 @@ if not (options.zulip_email):
zulip_client = zulip.init_from_options(options)
def get_credentials():
# type: () -> client.Credentials
def get_credentials() -> client.Credentials:
"""Gets valid user credentials from storage.
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.")
def populate_events():
# type: () -> Optional[None]
def populate_events() -> Optional[None]:
global events
credentials = get_credentials()
@ -145,8 +143,7 @@ def populate_events():
events.append((event["id"], start, "(No Title)"))
def send_reminders():
# type: () -> Optional[None]
def send_reminders() -> Optional[None]:
global sent
messages = []

View file

@ -12,8 +12,7 @@ from mercurial import ui, repository as repo
VERSION = "0.9"
def format_summary_line(web_url, user, base, tip, branch, node):
# type: (str, str, int, int, str, Text) -> Text
def format_summary_line(web_url: str, user: str, base: int, tip: int, branch: str, node: Text) -> Text:
"""
Format the first line of the message, which contains summary
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,
node=node[:12])
def format_commit_lines(web_url, repo, base, tip):
# type: (str, repo, int, int) -> str
def format_commit_lines(web_url: str, repo: repo, base: int, tip: int) -> str:
"""
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:
@ -64,8 +62,7 @@ def format_commit_lines(web_url, repo, base, tip):
return "\n".join(summary for summary in commit_summaries)
def send_zulip(email, api_key, site, stream, subject, content):
# type: (str, str, str, str, str, Text) -> None
def send_zulip(email: str, api_key: str, site: str, stream: str, subject: str, content: Text) -> None:
"""
Send a message to Zulip using the provided credentials, which should be for
a bot in most cases.
@ -83,8 +80,7 @@ def send_zulip(email, api_key, site, stream, subject, content):
client.send_message(message_data)
def get_config(ui, item):
# type: (ui, str) -> str
def get_config(ui: ui, item: str) -> str:
try:
# config returns configuration value.
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))
sys.exit(1)
def hook(ui, repo, **kwargs):
# type: (ui, repo, **Text) -> None
def hook(ui: ui, repo: repo, **kwargs: Text) -> None:
"""
Invoked by configuring a [hook] entry in .hg/hgrc.
"""

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -26,8 +26,7 @@ CURRENT_STATE = States.Startup
logger = cast(logging.Logger, None) # type: logging.Logger # FIXME cast should not be needed?
def to_zulip_username(zephyr_username):
# type: (str) -> str
def to_zulip_username(zephyr_username: str) -> str:
if "@" in zephyr_username:
(user, realm) = zephyr_username.split("@")
else:
@ -39,8 +38,7 @@ def to_zulip_username(zephyr_username):
return user.lower() + "@mit.edu"
return user.lower() + "|" + realm.upper() + "@mit.edu"
def to_zephyr_username(zulip_username):
# type: (str) -> str
def to_zephyr_username(zulip_username: str) -> str:
(user, realm) = zulip_username.split("@")
if "|" not in user:
# 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)
# or (3) the first word of the next line is longer than this entire
# line.
def different_paragraph(line, next_line):
# type: (str, str) -> bool
def different_paragraph(line: str, next_line: str) -> bool:
words = next_line.split()
return (len(line + " " + words[0]) < len(next_line) * 0.8 or
len(line + " " + words[0]) < 50 or
@ -71,8 +68,7 @@ def different_paragraph(line, next_line):
# 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
def unwrap_lines(body):
# type: (str) -> str
def unwrap_lines(body: str) -> str:
lines = body.split("\n")
result = ""
previous_line = lines[0]
@ -95,8 +91,7 @@ def unwrap_lines(body):
result += previous_line
return result
def send_zulip(zeph):
# type: (Dict[str, str]) -> Dict[str, str]
def send_zulip(zeph: Dict[str, str]) -> Dict[str, str]:
message = {}
if options.forward_class_messages:
message["forged"] = "yes"
@ -128,8 +123,7 @@ def send_zulip(zeph):
return zulip_client.send_message(message)
def send_error_zulip(error_msg):
# type: (str) -> None
def send_error_zulip(error_msg: str) -> None:
message = {"type": "private",
"sender": zulip_account_email,
"to": zulip_account_email,
@ -138,8 +132,7 @@ def send_error_zulip(error_msg):
zulip_client.send_message(message)
current_zephyr_subs = set()
def zephyr_bulk_subscribe(subs):
# type: (List[Tuple[str, str, str]]) -> None
def zephyr_bulk_subscribe(subs: List[Tuple[str, str, str]]) -> None:
try:
zephyr._z.subAll(subs)
except OSError:
@ -174,8 +167,7 @@ def zephyr_bulk_subscribe(subs):
else:
current_zephyr_subs.add(cls)
def update_subscriptions():
# type: () -> None
def update_subscriptions() -> None:
try:
f = open(options.stream_file_path)
public_streams = json.loads(f.read())
@ -198,8 +190,7 @@ def update_subscriptions():
if len(classes_to_subscribe) > 0:
zephyr_bulk_subscribe(list(classes_to_subscribe))
def maybe_kill_child():
# type: () -> None
def maybe_kill_child() -> None:
try:
if child_pid is not None:
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
logger.exception("")
def maybe_restart_mirroring_script():
# type: () -> None
def maybe_restart_mirroring_script() -> None:
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
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:")
time.sleep(1)
def process_loop(log):
# type: (Optional[IO[Any]]) -> None
def process_loop(log: Optional[IO[Any]]) -> None:
restart_check_count = 0
last_check_time = time.time()
while True:
@ -267,8 +256,7 @@ def process_loop(log):
except Exception:
logger.exception("Error updating subscriptions from Zulip:")
def parse_zephyr_body(zephyr_data, notice_format):
# type: (str, str) -> Tuple[str, str]
def parse_zephyr_body(zephyr_data: str, notice_format: str) -> Tuple[str, str]:
try:
(zsig, body) = zephyr_data.split("\x00", 1)
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', '')
return (zsig, body)
def parse_crypt_table(zephyr_class, instance):
# type: (Text, str) -> Optional[str]
def parse_crypt_table(zephyr_class: Text, instance: str) -> Optional[str]:
try:
crypt_table = open(os.path.join(os.environ["HOME"], ".crypt-table"))
except OSError:
@ -306,8 +293,7 @@ def parse_crypt_table(zephyr_class, instance):
return groups["keypath"]
return None
def decrypt_zephyr(zephyr_class, instance, body):
# type: (Text, str, str) -> str
def decrypt_zephyr(zephyr_class: Text, instance: str, body: str) -> str:
keypath = parse_crypt_table(zephyr_class, instance)
if keypath is None:
# 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)
return decrypted # type: ignore # bytes, expecting str
def process_notice(notice, log):
# type: (Any, Optional[IO[Any]]) -> None
def process_notice(notice: Any, log: Optional[IO[Any]]) -> None:
(zsig, body) = parse_zephyr_body(notice.message, notice.format)
is_personal = False
is_huddle = False
@ -436,8 +421,7 @@ def process_notice(notice, log):
finally:
os._exit(0)
def decode_unicode_byte_strings(zeph):
# type: (Dict[str, Any]) -> Dict[str, str]
def decode_unicode_byte_strings(zeph: Dict[str, Any]) -> Dict[str, str]:
# 'Any' can be of any type of text that is converted to str.
for field in zeph.keys():
if isinstance(zeph[field], str):
@ -448,14 +432,12 @@ def decode_unicode_byte_strings(zeph):
zeph[field] = decoded
return zeph
def quit_failed_initialization(message):
# type: (str) -> str
def quit_failed_initialization(message: str) -> str:
logger.error(message)
maybe_kill_child()
sys.exit(1)
def zephyr_init_autoretry():
# type: () -> None
def zephyr_init_autoretry() -> None:
backoff = zulip.RandomExponentialBackoff()
while backoff.keep_going():
try:
@ -470,8 +452,7 @@ def zephyr_init_autoretry():
quit_failed_initialization("Could not initialize Zephyr library, quitting!")
def zephyr_load_session_autoretry(session_path):
# type: (str) -> None
def zephyr_load_session_autoretry(session_path: str) -> None:
backoff = zulip.RandomExponentialBackoff()
while backoff.keep_going():
try:
@ -486,8 +467,7 @@ def zephyr_load_session_autoretry(session_path):
quit_failed_initialization("Could not load saved Zephyr session, quitting!")
def zephyr_subscribe_autoretry(sub):
# type: (Tuple[str, str, str]) -> None
def zephyr_subscribe_autoretry(sub: Tuple[str, str, str]) -> None:
backoff = zulip.RandomExponentialBackoff()
while backoff.keep_going():
try:
@ -502,8 +482,7 @@ def zephyr_subscribe_autoretry(sub):
quit_failed_initialization("Could not subscribe to personals, quitting!")
def zephyr_to_zulip(options):
# type: (Any) -> None
def zephyr_to_zulip(options: Any) -> None:
if options.use_sessions and os.path.exists(options.session_path):
logger.info("Loading old session")
zephyr_load_session_autoretry(options.session_path)
@ -554,8 +533,7 @@ def zephyr_to_zulip(options):
else:
process_loop(None)
def send_zephyr(zwrite_args, content):
# type: (List[str], str) -> Tuple[int, str]
def send_zephyr(zwrite_args: List[str], content: str) -> Tuple[int, str]:
p = subprocess.Popen(zwrite_args, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
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
return (p.returncode, stderr) # type: ignore # bytes vs str
def send_authed_zephyr(zwrite_args, content):
# type: (List[str], str) -> Tuple[int, str]
def send_authed_zephyr(zwrite_args: List[str], content: str) -> Tuple[int, str]:
return send_zephyr(zwrite_args, content)
def send_unauthed_zephyr(zwrite_args, content):
# type: (List[str], str) -> Tuple[int, str]
def send_unauthed_zephyr(zwrite_args: List[str], content: str) -> Tuple[int, str]:
return send_zephyr(zwrite_args + ["-d"], content)
def zcrypt_encrypt_content(zephyr_class, instance, content):
# type: (str, str, str) -> Optional[str]
def zcrypt_encrypt_content(zephyr_class: str, instance: str, content: str) -> Optional[str]:
keypath = parse_crypt_table(zephyr_class, instance)
if keypath is 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
return encrypted # type: ignore # bytes, expecting Optional[str]
def forward_to_zephyr(message):
# type: (Dict[str, Any]) -> None
def forward_to_zephyr(message: Dict[str, Any]) -> None:
# 'Any' can be of any type of text
support_heading = "Hi there! This is an automated message from Zulip."
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))
return
def maybe_forward_to_zephyr(message):
# type: (Dict[str, Any]) -> None
def maybe_forward_to_zephyr(message: Dict[str, Any]) -> None:
# The key string can be used to direct any type of text.
if (message["sender_email"] == zulip_account_email):
if not ((message["type"] == "stream") or
@ -758,8 +731,7 @@ def maybe_forward_to_zephyr(message):
# whole process
logger.exception("Error forwarding message:")
def zulip_to_zephyr(options):
# type: (int) -> None
def zulip_to_zephyr(options: int) -> None:
# Sync messages from zulip to zephyr
logger.info("Starting syncing messages.")
while True:
@ -769,8 +741,7 @@ def zulip_to_zephyr(options):
logger.exception("Error syncing messages:")
time.sleep(1)
def subscribed_to_mail_messages():
# type: () -> bool
def subscribed_to_mail_messages() -> bool:
# 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
# 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"
return False
def add_zulip_subscriptions(verbose):
# type: (bool) -> None
def add_zulip_subscriptions(verbose: bool) -> None:
zephyr_subscriptions = set()
skipped = set()
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.
""")) + "\n")
def valid_stream_name(name):
# type: (str) -> bool
def valid_stream_name(name: str) -> bool:
return name != ""
def parse_zephyr_subs(verbose=False):
# type: (bool) -> Set[Tuple[str, str, str]]
def parse_zephyr_subs(verbose: bool = False) -> Set[Tuple[str, str, str]]:
zephyr_subscriptions = set() # type: Set[Tuple[str, str, str]]
subs_file = os.path.join(os.environ["HOME"], ".zephyr.subs")
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()))
return zephyr_subscriptions
def open_logger():
# type: () -> logging.Logger
def open_logger() -> logging.Logger:
if options.log_path is not None:
log_file = options.log_path
elif options.forward_class_messages:
@ -930,8 +897,7 @@ def open_logger():
logger.addHandler(file_handler)
return logger
def configure_logger(logger, direction_name):
# type: (logging.Logger, Optional[str]) -> None
def configure_logger(logger: logging.Logger, direction_name: Optional[str]) -> None:
if direction_name is None:
log_format = "%(message)s"
else:
@ -945,8 +911,7 @@ def configure_logger(logger, direction_name):
for handler in root_logger.handlers:
handler.setFormatter(formatter)
def parse_args():
# type: () -> Tuple[Any, ...]
def parse_args() -> Tuple[Any, ...]:
parser = optparse.OptionParser()
parser.add_option('--forward-class-messages',
default=False,
@ -1029,8 +994,7 @@ def parse_args():
default=os.path.join(os.environ["HOME"], "Private", ".humbug-api-key"))
return parser.parse_args()
def die_gracefully(signal, frame):
# type: (int, FrameType) -> None
def die_gracefully(signal: int, frame: FrameType) -> None:
if CURRENT_STATE == States.ZulipToZephyr or CURRENT_STATE == States.ChildSending:
# this is a child process, so we want os._exit (no clean-up necessary)
os._exit(1)