Adding type annotations and fixing up formatting.
[kiosk.git] / generic_news_rss_renderer.py
index 3bc5f1be147026b7cac5f95eddfc569951f6e506..e73db4e7f983db3321432a06c74166236eb71149 100644 (file)
@@ -1,20 +1,31 @@
+#!/usr/bin/env python3
+
+from abc import abstractmethod
 import datetime
 from dateutil.parser import parse
+import http.client
+import random
+import re
+from typing import Dict, List
+import xml.etree.ElementTree as ET
+
 import file_writer
 import grab_bag
 import renderer
-import http.client
 import page_builder
 import profanity_filter
-import random
-import re
-import xml.etree.ElementTree as ET
 
 
 class generic_news_rss_renderer(renderer.debuggable_abstaining_renderer):
-    def __init__(self, name_to_timeout_dict, feed_site, feed_uris, page_title):
+    def __init__(
+        self,
+        name_to_timeout_dict: Dict[str, int],
+        feed_site: str,
+        feed_uris: List[str],
+        page_title: str,
+    ):
         super(generic_news_rss_renderer, self).__init__(name_to_timeout_dict, False)
-        self.debug = 1
+        self.debug = True
         self.feed_site = feed_site
         self.feed_uris = feed_uris
         self.page_title = page_title
@@ -22,76 +33,83 @@ class generic_news_rss_renderer(renderer.debuggable_abstaining_renderer):
         self.details = grab_bag.grab_bag()
         self.filter = profanity_filter.profanity_filter()
 
-    def debug_prefix(self):
+    @abstractmethod
+    def debug_prefix(self) -> str:
         pass
 
-    def get_headlines_page_prefix(self):
+    @abstractmethod
+    def get_headlines_page_prefix(self) -> str:
         pass
 
-    def get_details_page_prefix(self):
+    @abstractmethod
+    def get_details_page_prefix(self) -> str:
         pass
 
-    def get_headlines_page_priority(self):
+    def get_headlines_page_priority(self) -> str:
         return "4"
 
-    def get_details_page_priority(self):
+    def get_details_page_priority(self) -> str:
         return "6"
 
-    def should_use_https(self):
+    @abstractmethod
+    def should_use_https(self) -> bool:
         pass
 
-    def should_profanity_filter(self):
+    def should_profanity_filter(self) -> bool:
         return False
 
-    def find_title(self, item):
+    def find_title(self, item: ET.Element) -> str:
         return item.findtext("title")
 
-    def munge_title(self, title):
+    def munge_title(self, title: str) -> str:
         return title
 
-    def find_description(self, item):
+    def find_description(self, item: ET.Element) -> str:
         return item.findtext("description")
 
-    def munge_description(self, description):
+    def munge_description(self, description: str) -> str:
         description = re.sub("<[^>]+>", "", description)
         return description
 
-    def find_link(self, item):
+    def find_link(self, item: ET.Element) -> str:
         return item.findtext("link")
 
-    def munge_link(self, link):
+    def munge_link(self, link: str) -> str:
         return link
 
-    def find_image(self, item):
+    def find_image(self, item: ET.Element) -> str:
         return item.findtext("image")
 
-    def munge_image(self, image):
+    def munge_image(self, image: str) -> str:
         return image
 
-    def find_pubdate(self, item):
+    def find_pubdate(self, item: ET.Element) -> str:
         return item.findtext("pubDate")
 
-    def munge_pubdate(self, pubdate):
+    def munge_pubdate(self, pubdate: str) -> str:
         return pubdate
 
-    def item_is_interesting_for_headlines(self, title, description, item):
+    def item_is_interesting_for_headlines(
+        self, title: str, description: str, item: ET.Element
+    ) -> bool:
         return True
 
-    def is_item_older_than_n_days(self, item, n):
+    def is_item_older_than_n_days(self, item: ET.Element, n: int) -> bool:
         pubdate = self.find_pubdate(item)
-        if pubdate is not None:
-            pubdate = parse(pubdate)
-            tzinfo = pubdate.tzinfo
-            now = datetime.datetime.now(tzinfo)
-            delta = (now - pubdate).total_seconds() / (60 * 60 * 24)
-            if delta > n:
-                return True
-        return False
-
-    def item_is_interesting_for_article(self, title, description, item):
+        if pubdate is None:
+            return False
+        pubdate = parse(pubdate)
+        tzinfo = pubdate.tzinfo
+        now = datetime.datetime.now(tzinfo)
+        delta = (now - pubdate).total_seconds() / (60 * 60 * 24)
+        return delta > n
+
+    def item_is_interesting_for_article(
+        self, title: str, description: str, item: ET.Element
+    ) -> bool:
         return True
 
-    def periodic_render(self, key):
+    def periodic_render(self, key: str) -> bool:
         if key == "Fetch News":
             return self.fetch_news()
         elif key == "Shuffle News":
@@ -99,7 +117,7 @@ class generic_news_rss_renderer(renderer.debuggable_abstaining_renderer):
         else:
             raise error("Unexpected operation")
 
-    def shuffle_news(self):
+    def shuffle_news(self) -> bool:
         headlines = page_builder.page_builder()
         headlines.set_layout(page_builder.page_builder.LAYOUT_FOUR_ITEMS)
         headlines.set_title("%s" % self.page_title)
@@ -129,12 +147,9 @@ a:active {
 }
 </STYLE>"""
         )
-        f = file_writer.file_writer(
-            "%s_%s_25900.html"
-            % (self.get_headlines_page_prefix(), self.get_headlines_page_priority())
-        )
-        headlines.render_html(f)
-        f.close()
+        _ = f"{self.get_headlines_page_prefix()}_{self.get_headlines_page_priority()}_25900.html"
+        with file_writer.file_writer(_) as f:
+            headlines.render_html(f)
 
         details = page_builder.page_builder()
         details.set_layout(page_builder.page_builder.LAYOUT_ONE_ITEM)
@@ -158,24 +173,21 @@ a:active {
 }
 </STYLE>"""
         )
-        details.set_title("%s" % self.page_title)
+        details.set_title(f"{self.page_title}")
         subset = self.details.subset(1)
         if subset is None:
             self.debug_print("Not enough details to choose from.")
             return False
         for msg in subset:
             blurb = msg
-            blurb += u"</TD>"
+            blurb += "</TD>"
             details.add_item(blurb)
-        g = file_writer.file_writer(
-            "%s_%s_86400.html"
-            % (self.get_details_page_prefix(), self.get_details_page_priority())
-        )
-        details.render_html(g)
-        g.close()
+        _ = f"{self.get_details_page_prefix()}_{self.get_details_page_priority()}_86400.html"
+        with file_writer.file_writer(_) as g:
+            details.render_html(g)
         return True
 
-    def fetch_news(self):
+    def fetch_news(self) -> bool:
         count = 0
         self.news.clear()
         self.details.clear()
@@ -205,10 +217,7 @@ a:active {
 
             if response.status != 200:
                 print(
-                    (
-                        "%s: RSS fetch_news error, response: %d"
-                        % (self.page_title, response.status)
-                    )
+                    f"{self.page_title}: RSS fetch_news error, response: {response.status}"
                 )
                 self.debug_print(response.read())
                 return False
@@ -232,48 +241,44 @@ a:active {
                 if title is None or not self.item_is_interesting_for_headlines(
                     title, description, item
                 ):
-                    self.debug_print('Item "%s" is not interesting' % title)
+                    self.debug_print(f'Item "{title}" is not interesting')
                     continue
 
                 if self.should_profanity_filter() and (
                     self.filter.contains_bad_words(title)
                     or self.filter.contains_bad_words(description)
                 ):
-                    self.debug_print('Found bad words in item "%s"' % title)
+                    self.debug_print(f'Found bad words in item "{title}"')
                     continue
 
-                blurb = u"""<DIV style="padding:8px;
+                blurb = """<DIV style="padding:8px;
                                  font-size:34pt;
                                  -webkit-column-break-inside:avoid;">"""
                 if image is not None:
-                    blurb += u'<IMG SRC="%s" ALIGN=LEFT HEIGHT=115 ' % image
-                    blurb += u'style="padding:8px;">'
+                    blurb += f'<IMG SRC="{image}" ALIGN=LEFT HEIGHT=115 '
+                    blurb += 'style="padding:8px;">'
 
                 if link is None:
-                    blurb += u"<P><B>%s</B>" % title
+                    blurb += f"<P><B>{title}</B>"
                 else:
-                    blurb += u'<P><B><A HREF="%s">%s</A></B>' % (link, title)
+                    blurb += f'<P><B><A HREF="{link}">{title}</A></B>'
 
                 pubdate = self.find_pubdate(item)
                 if pubdate is not None:
                     pubdate = self.munge_pubdate(pubdate)
                     ts = parse(pubdate)
-                    blurb += u"  <FONT COLOR=#cccccc>%s</FONT>" % (
-                        ts.strftime("%b&nbsp;%d")
-                    )
+                    blurb += f'  <FONT COLOR=#cccccc>{ts.strftime("%b&nbsp;%d")}</FONT>'
 
                 if description is not None and self.item_is_interesting_for_article(
                     title, description, item
                 ):
                     longblurb = blurb
-
-                    longblurb += u"<BR>"
+                    longblurb += "<BR>"
                     longblurb += description
-                    longblurb += u"</DIV>"
+                    longblurb += "</DIV>"
                     longblurb = longblurb.replace("font-size:34pt", "font-size:44pt")
                     self.details.add(longblurb)
-
-                blurb += u"</DIV>"
+                blurb += "</DIV>"
                 self.news.add(blurb)
                 count += 1
         return count > 0