Skip to content

Commit 25ce2f5

Browse files
author
Oren Leaffer
committed
bugdown: add some type annotations
Had to add some "type: ignore" because the pattern used in match doesn't affect the type returned. A fix for this issue has been pushed to typeshed - python/typeshed#244
1 parent 732b826 commit 25ce2f5

File tree

1 file changed

+28
-3
lines changed

1 file changed

+28
-3
lines changed

zerver/lib/bugdown/__init__.py

+28-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import absolute_import
22
# Zulip's main markdown implementation. See docs/markdown.md for
33
# detailed documentation on our markdown syntax.
4-
from typing import Any, Optional
4+
from typing import Any, Optional, Callable, Union, Dict
55
from typing.re import Match
66

77
import markdown
@@ -19,6 +19,7 @@
1919
import itertools
2020
from six.moves import urllib
2121
import xml.etree.cElementTree as etree
22+
from xml.etree.cElementTree import ElementTree
2223

2324
from collections import defaultdict
2425

@@ -38,13 +39,15 @@
3839
import zerver.lib.mention as mention
3940
import six
4041
from six.moves import range
42+
from six import text_type
4143

4244

4345
# Format version of the bugdown rendering; stored along with rendered
4446
# messages so that we can efficiently determine what needs to be re-rendered
4547
version = 1
4648

4749
def list_of_tlds():
50+
# type: () -> List[str]
4851
# HACK we manually blacklist .py
4952
blacklist = ['PY\n', ]
5053

@@ -56,6 +59,7 @@ def list_of_tlds():
5659
return tlds
5760

5861
def walk_tree(root, processor, stop_after_first=False):
62+
# type: (ElementTree, Callable[[ElementTree], Optional[ElementTree]], bool) -> List[ElementTree]
5963
results = []
6064
stack = [root]
6165

@@ -76,6 +80,8 @@ def walk_tree(root, processor, stop_after_first=False):
7680
# height is not actually used
7781
def add_a(root, url, link, height="", title=None, desc=None,
7882
class_attr="message_inline_image"):
83+
# type: ignore # (ElementTree, text_type, text_type, text_type, Optional[text_type], Optional[text_type], text_type) -> None
84+
# above ignored until mypy picks up https://github.com/python/typeshed/pull/244
7985
title = title if title is not None else url_filename(link)
8086
title = title if title else ""
8187
desc = desc if desc is not None else ""
@@ -98,6 +104,7 @@ def add_a(root, url, link, height="", title=None, desc=None,
98104

99105
@cache_with_key(lambda tweet_id: tweet_id, cache_name="database", with_statsd_key="tweet_data")
100106
def fetch_tweet_data(tweet_id):
107+
# type: (text_type) -> Optional[Dict[text_type, text_type]]
101108
if settings.TEST_SUITE:
102109
from . import testing_mocks
103110
res = testing_mocks.twitter(tweet_id)
@@ -158,6 +165,7 @@ def fetch_tweet_data(tweet_id):
158165
META_END_RE = re.compile('^/meta[ >]')
159166

160167
def fetch_open_graph_image(url):
168+
# type: (str) -> Optional[Dict[str, Any]]
161169
in_head = False
162170
# HTML will auto close meta tags, when we start the next tag add a closing tag if it has not been closed yet.
163171
last_closed = True
@@ -223,21 +231,23 @@ def fetch_open_graph_image(url):
223231
return {'image': image, 'title': title, 'desc': desc}
224232

225233
def get_tweet_id(url):
234+
# type: (str) -> Union[bool, str]
226235
parsed_url = urllib.parse.urlparse(url)
227236
if not (parsed_url.netloc == 'twitter.com' or parsed_url.netloc.endswith('.twitter.com')):
228-
return False
237+
return False # TODO: probably should return None instead and change return type to Optional[str]
229238
to_match = parsed_url.path
230239
# In old-style twitter.com/#!/wdaher/status/1231241234-style URLs, we need to look at the fragment instead
231240
if parsed_url.path == '/' and len(parsed_url.fragment) > 5:
232241
to_match= parsed_url.fragment
233242

234243
tweet_id_match = re.match(r'^!?/.*?/status(es)?/(?P<tweetid>\d{10,18})(/photo/[0-9])?/?$', to_match)
235244
if not tweet_id_match:
236-
return False
245+
return False # TODO: probably should return None instead and change return type to Optional[str]
237246
return tweet_id_match.group("tweetid")
238247

239248
class InlineHttpsProcessor(markdown.treeprocessors.Treeprocessor):
240249
def run(self, root):
250+
# type: (ElementTree) -> None
241251
# Get all URLs from the blob
242252
found_imgs = walk_tree(root, lambda e: e if e.tag == "img" else None)
243253
for img in found_imgs:
@@ -252,11 +262,13 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor):
252262
TWITTER_MAX_TO_PREVIEW = 3
253263

254264
def __init__(self, md, bugdown):
265+
# type: (markdown.Markdown, Bugdown) -> None
255266
# Passing in bugdown for access to config to check if realm is zulip.com
256267
self.bugdown = bugdown
257268
markdown.treeprocessors.Treeprocessor.__init__(self, md)
258269

259270
def is_image(self, url):
271+
# type: (str) -> bool
260272
if not settings.INLINE_IMAGE_PREVIEW:
261273
return False
262274
parsed_url = urllib.parse.urlparse(url)
@@ -267,6 +279,8 @@ def is_image(self, url):
267279
return False
268280

269281
def dropbox_image(self, url):
282+
# type: (str) -> Optional[Dict]
283+
# TODO: specify details of returned Dict
270284
parsed_url = urllib.parse.urlparse(url)
271285
if (parsed_url.netloc == 'dropbox.com' or parsed_url.netloc.endswith('.dropbox.com')):
272286
is_album = parsed_url.path.startswith('/sc/') or parsed_url.path.startswith('/photos/')
@@ -311,6 +325,7 @@ def dropbox_image(self, url):
311325
return None
312326

313327
def youtube_image(self, url):
328+
# type: (str) -> Optional[str]
314329
if not settings.INLINE_IMAGE_PREVIEW:
315330
return None
316331
# Youtube video id extraction regular expression from http://pastebin.com/KyKAFv1s
@@ -404,6 +419,7 @@ def set_text(text):
404419
return p
405420

406421
def twitter_link(self, url):
422+
# type: (str) -> Optional[markdown.util.etree.Element]
407423
tweet_id = get_tweet_id(url)
408424

409425
if not tweet_id:
@@ -475,6 +491,7 @@ def twitter_link(self, url):
475491
return None
476492

477493
def run(self, root):
494+
# type: (ElementTree) -> None
478495
# Get all URLs from the blob
479496
found_urls = walk_tree(root, lambda e: e.get("href") if e.tag == "a" else None)
480497

@@ -520,6 +537,7 @@ def run(self, root):
520537

521538
class Avatar(markdown.inlinepatterns.Pattern):
522539
def handleMatch(self, match):
540+
# type: (Match) -> markdown.util.etree.Element
523541
img = markdown.util.etree.Element('img')
524542
email_address = match.group('email')
525543
img.set('class', 'message_body_gravatar')
@@ -540,6 +558,7 @@ def handleMatch(self, match):
540558

541559

542560
def make_emoji(emoji_name, src, display_string):
561+
# type: (str, str, str) -> markdown.util.etree.Element
543562
elt = markdown.util.etree.Element('img')
544563
elt.set('src', src)
545564
elt.set('class', 'emoji')
@@ -569,6 +588,7 @@ class StreamSubscribeButton(markdown.inlinepatterns.Pattern):
569588
# This markdown extension has required javascript in
570589
# static/js/custom_markdown.js
571590
def handleMatch(self, match):
591+
# type: (Match) -> markdown.util.etree.Element
572592
stream_name = match.group('stream_name')
573593
stream_name = stream_name.replace('\\)', ')').replace('\\\\', '\\')
574594

@@ -590,6 +610,7 @@ class ModalLink(markdown.inlinepatterns.Pattern):
590610
A pattern that allows including in-app modal links in messages.
591611
"""
592612
def handleMatch(self, match):
613+
# type: (Match) -> markdown.util.etree.Element
593614
relative_url = match.group('relative_url')
594615
text = match.group('text')
595616

@@ -603,6 +624,8 @@ def handleMatch(self, match):
603624

604625
upload_re = re.compile(r"^(?:https://%s.s3.amazonaws.com|/user_uploads/\d+)/[^/]*/([^/]*)$" % (settings.S3_BUCKET,))
605626
def url_filename(url):
627+
# type: ignore # (text_type) -> text_type
628+
# above ignored until mypy picks up https://github.com/python/typeshed/pull/244
606629
"""Extract the filename if a URL is an uploaded file, or return the original URL"""
607630
match = upload_re.match(url)
608631
if match:
@@ -611,13 +634,15 @@ def url_filename(url):
611634
return url
612635

613636
def fixup_link(link, target_blank=True):
637+
# type: (markdown.util.etree.Element, bool) -> None
614638
"""Set certain attributes we want on every link."""
615639
if target_blank:
616640
link.set('target', '_blank')
617641
link.set('title', url_filename(link.get('href')))
618642

619643

620644
def sanitize_url(url):
645+
# type: (text_type) -> text_type
621646
"""
622647
Sanitize a url against xss attacks.
623648
See the docstring on markdown.inlinepatterns.LinkPattern.sanitize_url.

0 commit comments

Comments
 (0)