mypy clean
authorScott Gasch <[email protected]>
Thu, 21 Jan 2021 23:11:59 +0000 (15:11 -0800)
committerScott Gasch <[email protected]>
Thu, 21 Jan 2021 23:11:59 +0000 (15:11 -0800)
26 files changed:
camera_trigger.py
chooser.py
cnn_rss_renderer.py
constants.py
gcal_renderer.py
gcal_trigger.py
gdata_oauth.py
generic_news_rss_renderer.py
gkeep_renderer.py
google_news_rss_renderer.py
health_renderer.py
kiosk.py
local_photos_mirror_renderer.py
mynorthwest_rss_renderer.py
myq_renderer.py
myq_trigger.py
reddit_renderer.py
seattletimes_rss_renderer.py
stevens_renderer.py
stock_renderer.py
stranger_renderer.py
trigger.py
twitter_renderer.py
utils.py
weather_renderer.py
wsj_rss_renderer.py

index 620a5b2fd62f27587cac6c096583eb94f42e90a5..b47a26ed4dfd9023ff45de49503808a757b7be4a 100644 (file)
@@ -4,7 +4,7 @@ from datetime import datetime
 import glob
 import os
 import time
-from typing import List, Tuple
+from typing import List, Tuple, Optional
 
 import trigger
 import utils
@@ -39,7 +39,7 @@ class any_camera_trigger(trigger.trigger):
             priority += trigger.trigger.PRIORITY_LOW
         return priority
 
-    def get_triggered_page_list(self) -> List[Tuple[str, int]]:
+    def get_triggered_page_list(self) -> Optional[List[Tuple[str, int]]]:
         """Return a list of triggered pages with priorities."""
         triggers = []
         num_cameras_with_recent_triggers = 0
@@ -78,10 +78,9 @@ class any_camera_trigger(trigger.trigger):
                         self.triggers_in_the_past_seven_min[camera] <= 4
                         or num_cameras_with_recent_triggers > 1
                     ):
-                        ts = utils.timestamp()
-                        priority = self.choose_priority(camera, age)
+                        priority = self.choose_priority(camera, int(age))
                         print(
-                            f"{ts}: ****** {camera}[{priority}] CAMERA TRIGGER ******"
+                            f"{utils.timestamp()}: *** {camera}[{priority}] CAMERA TRIGGER ***"
                         )
                         triggers.append(
                             (
@@ -90,7 +89,7 @@ class any_camera_trigger(trigger.trigger):
                             )
                         )
                     else:
-                        print(f"{ts}: Camera {camera} too spammy, squelching it")
+                        print(f"{utils.timestamp()}: Camera {camera} too spammy, squelching it")
         except Exception as e:
             print(e)
             pass
index 35d38b5ad9ede0e4a3f1170dc31051fcb0f06c70..743aa3a625aab18e7fbec57ab69c05394cad03d3 100644 (file)
@@ -8,7 +8,7 @@ import random
 import re
 import sys
 import time
-from typing import Callable, List, Optional, Set, Tuple
+from typing import Any, Callable, List, Optional, Set, Tuple
 
 import constants
 import trigger
@@ -43,21 +43,21 @@ class chooser(ABC):
         return filenames
 
     @abstractmethod
-    def choose_next_page(self) -> str:
+    def choose_next_page(self) -> Any:
         pass
 
 
 class weighted_random_chooser(chooser):
     """Chooser that does it via weighted RNG."""
 
-    def __init__(self, filter_list: List[Callable[[str], bool]]) -> None:
+    def __init__(self, filter_list: Optional[List[Callable[[str], bool]]]) -> None:
         self.last_choice = ""
         self.valid_filename = re.compile("([^_]+)_(\d+)_([^\.]+)\.html")
         self.pages: Optional[List[str]] = None
         self.count = 0
-        self.filter_list = filter_list
-        if filter_list is None:
-            self.filter_list = []
+        self.filter_list: List[Callable[[str], bool]] = []
+        if filter_list is not None:
+            self.filter_list.extend(filter_list)
         self.filter_list.append(self.dont_choose_page_twice_in_a_row_filter)
 
     def dont_choose_page_twice_in_a_row_filter(self, choice: str) -> bool:
@@ -66,7 +66,7 @@ class weighted_random_chooser(chooser):
         self.last_choice = choice
         return True
 
-    def choose_next_page(self) -> str:
+    def choose_next_page(self) -> Any:
         if self.pages is None or self.count % 100 == 0:
             self.pages = self.get_page_list()
 
@@ -110,13 +110,13 @@ class weighted_random_chooser_with_triggers(weighted_random_chooser):
 
     def __init__(
         self,
-        trigger_list: List[trigger.trigger],
+        trigger_list: Optional[List[trigger.trigger]],
         filter_list: List[Callable[[str], bool]],
     ) -> None:
         weighted_random_chooser.__init__(self, filter_list)
-        self.trigger_list = trigger_list
-        if trigger_list is None:
-            self.trigger_list = []
+        self.trigger_list: List[trigger.trigger] = []
+        if trigger_list is not None:
+            self.trigger_list.extend(trigger_list)
         self.page_queue: Set[Tuple[str, int]] = set(())
 
     def check_for_triggers(self) -> bool:
index ae00dc54f879ac63a81fb600e32528a1cd0078ab..9bec2a8f354a209f8c35a2221a1e2d052565ffca 100644 (file)
@@ -2,7 +2,7 @@
 
 import generic_news_rss_renderer
 import re
-from typing import Dict, List
+from typing import Dict, List, Optional
 import xml
 
 
@@ -33,7 +33,7 @@ class cnn_rss_renderer(generic_news_rss_renderer.generic_news_rss_renderer):
         description = re.sub("<[^>]+>", "", description)
         return description
 
-    def find_image(self, item: xml.etree.ElementTree.Element) -> str:
+    def find_image(self, item: xml.etree.ElementTree.Element) -> Optional[str]:
         image = item.findtext("media:thumbnail")
         if image is not None:
             image_url = image.get("url")
index b1bedc0a77ab3fcf30ea3971ed968ba6d43aa0f2..1e79b0775e634d5330302e02372753e0701744f5 100644 (file)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 
-refresh_period_sec = 22
-render_period_sec = 30
+refresh_period_sec = 22.0
+render_period_sec = 30.0
 pages_dir = "/usr/local/export/www/kiosk/pages"
 
 seconds_per_minute = 60
index 37f8c8e50671b32e2c432ead74221c01359fe58f..11f530451e2bd4f58763fcc306176aefff6aaade 100644 (file)
@@ -4,12 +4,12 @@
 contents of several Google calendars."""
 
 import datetime
-import gdata
+import gdata  # type: ignore
 import gdata_oauth
-from oauth2client.client import AccessTokenRefreshError
+from oauth2client.client import AccessTokenRefreshError  # type: ignore
 import os
 import time
-from typing import Dict, List, Tuple
+from typing import Dict, List, Optional, Tuple
 
 import constants
 import file_writer
@@ -39,13 +39,13 @@ class gcal_renderer(renderer.debuggable_abstaining_renderer):
 
         def __init__(
             self,
-            start_time: datetime.datetime,
-            end_time: datetime.datetime,
+            start_time: Optional[datetime.datetime],
+            end_time: Optional[datetime.datetime],
             summary: str,
             calendar: str,
         ) -> None:
             if start_time is None:
-                assert end_time is None
+                assert(end_time is None)
             self.start_time = start_time
             self.end_time = end_time
             self.summary = summary
@@ -87,8 +87,8 @@ class gcal_renderer(renderer.debuggable_abstaining_renderer):
         super(gcal_renderer, self).__init__(name_to_timeout_dict, True)
         self.oauth = oauth
         self.client = self.oauth.calendar_service()
-        self.sortable_events = []
-        self.countdown_events = []
+        self.sortable_events: List[gcal_renderer.comparable_event] = []
+        self.countdown_events: List[gcal_renderer.comparable_event] = []
 
     def debug_prefix(self) -> str:
         return "gcal"
@@ -100,25 +100,20 @@ class gcal_renderer(renderer.debuggable_abstaining_renderer):
         elif key == "Look For Triggered Events":
             return self.look_for_triggered_events()
         else:
-            raise error("Unexpected operation")
+            raise Exception("Unexpected operation")
 
     def get_min_max_timewindow(self) -> Tuple[str, str]:
         now = datetime.datetime.now()
-        time_min = now - datetime.timedelta(1)
-        time_max = now + datetime.timedelta(95)
-        time_min, time_max = list(
-            map(
-                lambda x: datetime.datetime.strftime(x, "%Y-%m-%dT%H:%M:%SZ"),
-                (time_min, time_max),
-            )
-        )
-        print(type(time_min))
-        self.debug_print("time_min is %s" % time_min)
-        self.debug_print("time_max is %s" % time_max)
+        _time_min = now - datetime.timedelta(1)
+        _time_max = now + datetime.timedelta(95)
+        time_min = datetime.datetime.strftime(_time_min, "%Y-%m-%dT%H:%M:%SZ")
+        time_max = datetime.datetime.strftime(_time_max, "%Y-%m-%dT%H:%M:%SZ")
+        self.debug_print(f"time_min is {time_min}")
+        self.debug_print(f"time_max is {time_max}")
         return (time_min, time_max)
 
     @staticmethod
-    def parse_date(date_str: str) -> datetime.datetime:
+    def parse_date(date_str: str) -> Optional[datetime.datetime]:
         retval = None
         try:
             _ = date_str.get("date")
@@ -225,6 +220,8 @@ class gcal_renderer(renderer.debuggable_abstaining_renderer):
                 timestamps = {}
                 for event in upcoming_countdown_events:
                     eventstamp = event.start_time
+                    if eventstamp is None:
+                        return False
                     name = event.friendly_name()
                     delta = eventstamp - now
                     x = int(delta.total_seconds())
@@ -251,8 +248,8 @@ class gcal_renderer(renderer.debuggable_abstaining_renderer):
                         )
                 g.write("</ul>")
                 g.write("<SCRIPT>\nlet timestampMap = new Map([")
-                for x in list(timestamps.keys()):
-                    g.write(f'    ["{x}", {timestamps[x] * 1000.0}],\n')
+                for _ in list(timestamps.keys()):
+                    g.write(f'    ["{_}", {timestamps[_] * 1000.0}],\n')
                 g.write("]);\n\n")
                 g.write(
                     """
@@ -305,6 +302,8 @@ var fn = setInterval(function() {
             count = 0
             for event in self.sortable_events:
                 eventstamp = event.start_time
+                if eventstamp is None:
+                    return False
                 delta = eventstamp - now
                 x = int(delta.total_seconds())
                 if x > 0 and x <= constants.seconds_per_minute * 3:
index b843433ac94ec6c7bf1ff28f5d35ab724ef964e8..36e46e62e81a11ea2ab356dc4139201d5b187cca 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 
-from typing import Optional, Tuple
+from typing import List, Optional, Tuple
 
 import constants
 import globals
@@ -8,10 +8,10 @@ import trigger
 
 
 class gcal_trigger(trigger.trigger):
-    def get_triggered_page_list(self) -> Optional[Tuple[str, int]]:
+    def get_triggered_page_list(self) -> Optional[List[Tuple[str, int]]]:
         if globals.get("gcal_triggered"):
             print("****** gcal has an imminent upcoming event. ******")
-            return (constants.gcal_imminent_pagename, trigger.trigger.PRIORITY_HIGH)
+            return [(constants.gcal_imminent_pagename, trigger.trigger.PRIORITY_HIGH)]
         else:
             return None
 
index 43223e99bdc5912f17e92c244fa256bba188d2b2..7e8b336db29380387b11808c0cecf0d352db4455 100644 (file)
@@ -15,12 +15,13 @@ except ImportError:
 import os.path
 import json
 import time
-from oauth2client.client import OAuth2Credentials
-import gdata.calendar.service
-import gdata.docs.service
-import gdata.photos.service, gdata.photos
-from googleapiclient.discovery import build
-import httplib2
+from typing import Dict, Optional
+from oauth2client.client import OAuth2Credentials  # type: ignore
+import gdata.calendar.service  # type: ignore
+import gdata.docs.service  # type: ignore
+import gdata.photos.service, gdata.photos  # type: ignore
+from googleapiclient.discovery import build  # type: ignore
+import httplib2  # type: ignore
 from googleapiclient.discovery import build
 import datetime
 import ssl
@@ -31,10 +32,10 @@ class OAuth:
         print("gdata: initializing oauth token...")
         self.client_id = client_id
         self.client_secret = client_secret
-        self.user_code = None
+        self.user_code: Optional[str] = None
         # print 'Client id: %s' % (client_id)
         # print 'Client secret: %s' % (client_secret)
-        self.token = None
+        self.token: Optional[Dict] = None
         self.device_code = None
         self.verfication_url = None
         self.token_file = "client_secrets.json"
@@ -51,8 +52,8 @@ class OAuth:
         self.host = "accounts.google.com"
         self.reset_connection()
         self.load_token()
-        self.last_action = 0
-        self.ssl_ctx = None
+        self.last_action = 0.0
+        self.ssl_ctx: Optional[ssl.SSLContext] = None
 
     # this setup is isolated because it eventually generates a BadStatusLine
     # exception, after which we always get httplib.CannotSendRequest errors.
@@ -82,7 +83,7 @@ class OAuth:
             print("gdata: we have no token.")
         return self.token is not None
 
-    def get_user_code(self) -> str:
+    def get_user_code(self) -> Optional[str]:
         self.conn.request(
             "POST",
             "/o/oauth2/device/code",
@@ -99,6 +100,7 @@ class OAuth:
             self.verification_url = data["verification_url"]
             self.retry_interval = data["interval"]
         else:
+            self.user_code = None
             print(f"gdata: {response.status}")
             print(response.read())
             sys.exit(-1)
@@ -144,6 +146,9 @@ class OAuth:
         else:
             print("gdata: trying to refresh oauth token...")
         self.reset_connection()
+        if self.token is None:
+            return False
+
         refresh_token = self.token["refresh_token"]
         self.conn.request(
             "POST",
@@ -162,7 +167,7 @@ class OAuth:
         response = self.conn.getresponse()
         self.last_action = time.time()
         if response.status == 200:
-            data = json.loads(response.read())
+            data: Dict = json.loads(response.read())
             if "access_token" in data:
                 self.token = data
                 # in fact we NEVER get a new refresh token at this point
index 34c48210c9ce4b3069710e27db9d6ecf783a0113..71cf7ed2d0ceee7191f69d818684e5b65dfc17ab 100644 (file)
@@ -274,9 +274,7 @@ a:active {
                     ts = parse(pubdate)
                     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
-                ):
+                if self.item_is_interesting_for_article(title, description, item):
                     longblurb = blurb
                     longblurb += "<BR>"
                     longblurb += description
index 79ea4c7f602111d076ca5e872250fbc4ec419b18..4a3725b29d5867808297bb4557e34079114b80c6 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 
-import gkeepapi
+import gkeepapi  # type: ignore
 import os
 import re
 from typing import List, Dict
@@ -40,7 +40,7 @@ class gkeep_renderer(renderer.debuggable_abstaining_renderer):
     def debug_prefix(self) -> str:
         return "gkeep"
 
-    def periodic_render(self: str, key) -> bool:
+    def periodic_render(self, key: str) -> bool:
         strikethrough = re.compile("(\u2611[^\n]*)\n", re.UNICODE)
         linkify = re.compile(r".*(https?:\/\/\S+).*")
 
@@ -71,7 +71,7 @@ class gkeep_renderer(renderer.debuggable_abstaining_renderer):
                     if length > max_length:
                         max_length = length
                     leading_spaces = len(x) - len(x.lstrip(" "))
-                    leading_spaces /= 2
+                    leading_spaces //= 2
                     leading_spaces = int(leading_spaces)
                     x = x.lstrip(" ")
                     # self.debug_print(" * (%d) '%s'" % (leading_spaces, x))
index 9cf38767cfba62cb44c436c0e8a4bc25957e83c5..2faeff2d01a0a0361180a07d8e17496aaaad6be1 100644 (file)
@@ -1,8 +1,8 @@
 #!/usr/bin/env python3
 
-from bs4 import BeautifulSoup
+from bs4 import BeautifulSoup  # type: ignore
 import re
-from typing import Dict, List
+from typing import Dict, List, Optional
 import xml
 
 import generic_news_rss_renderer
@@ -33,8 +33,11 @@ class google_news_rss_renderer(generic_news_rss_renderer.generic_news_rss_render
     def find_description(self, item: xml.etree.ElementTree.Element) -> str:
         descr = item.findtext("description")
         source = item.findtext("source")
-        if source is not None:
-            descr = descr + " (%s)" % source
+        if descr is not None:
+            if source is not None:
+                descr = descr + f" (source)"
+        else:
+            descr = ""
         return descr
 
     def munge_description_internal(self, descr: str) -> str:
@@ -52,7 +55,7 @@ class google_news_rss_renderer(generic_news_rss_renderer.generic_news_rss_render
         descr = str(soup)
         return self.munge_description_internal(descr)
 
-    def find_image(self, item: xml.etree.ElementTree.Element) -> str:
+    def find_image(self, item: xml.etree.ElementTree.Element) -> Optional[str]:
         return None
 
     def should_use_https(self) -> bool:
index 774e0babc893967a83b9562f2f7e60e53de2d803..038e315c48144435255cec9720e1b90be0e731fa 100644 (file)
@@ -78,9 +78,9 @@ class periodic_health_renderer(renderer.debuggable_abstaining_renderer):
                 name = x.replace(timestamps, "")
                 name = name.replace("last_", "")
                 name = name.replace("_", "&nbsp;")
-                ts = utils.describe_duration_briefly(age)
+                duration = utils.describe_duration_briefly(int(age))
 
-                self.debug_print(f"{name} is {ts} old.")
+                self.debug_print(f"{name} is {duration} old.")
                 f.write(f"{name}<BR>\n<B>{ts}</B> old.\n")
                 f.write("</FONT></CENTER>\n</TD>\n\n")
                 n += 1
index 2ef090ac1e029c8f89dcc2884532e2130ac14429..ffcd7863d1a75f0a9bb67cacc3b0ace27a083322 100755 (executable)
--- a/kiosk.py
+++ b/kiosk.py
@@ -1,11 +1,13 @@
 #!/usr/bin/env python3
 
-import sys
-import traceback
+from datetime import datetime
 import os
+import sys
 from threading import Thread
 import time
-from datetime import datetime
+import traceback
+from typing import Optional
+
 import constants
 import renderer
 import renderer
@@ -34,7 +36,7 @@ def thread_change_current() -> None:
     page_chooser = chooser.weighted_random_chooser_with_triggers(
         trigger_catalog.get_triggers(), [filter_news_during_dinnertime]
     )
-    swap_page_target = 0
+    swap_page_target = 0.0
     last_page = ""
     while True:
         now = time.time()
@@ -311,11 +313,6 @@ def thread_invoke_renderers() -> None:
                 print(
                     f"renderer[{utils.timestamp()}] unknown exception in {r.get_name()}, swallowing it."
                 )
-            except Error as e:
-                traceback.print_exc()
-                print(
-                    f"renderer[{utils.timestamp()}] unknown error in {r.get_name()}, swallowing it."
-                )
             delta = time.time() - now
             if delta > 1.0:
                 print(
@@ -329,8 +326,8 @@ def thread_invoke_renderers() -> None:
 
 if __name__ == "__main__":
     logging.basicConfig()
-    changer_thread = None
-    renderer_thread = None
+    changer_thread: Optional[Thread] = None
+    renderer_thread: Optional[Thread] = None
     while True:
         if changer_thread is None or not changer_thread.is_alive():
             print(
@@ -345,4 +342,3 @@ if __name__ == "__main__":
             renderer_thread = Thread(target=thread_invoke_renderers, args=())
             renderer_thread.start()
         time.sleep(60)
-    print("Should never get here.")
index 55927af124788e617fe43271fd80c04185907dd0..390d6eabef0eab0d6230ad3075e10d012c4623a7 100644 (file)
@@ -3,7 +3,7 @@
 import os
 import random
 import re
-from typing import List, Dict
+from typing import List, Dict, Set
 
 import file_writer
 import renderer
@@ -63,7 +63,7 @@ class local_photos_mirror_renderer(renderer.debuggable_abstaining_renderer):
 
     def __init__(self, name_to_timeout_dict: Dict[str, int]) -> None:
         super(local_photos_mirror_renderer, self).__init__(name_to_timeout_dict, False)
-        self.candidate_photos = set()
+        self.candidate_photos: Set[str] = set()
 
     def debug_prefix(self) -> str:
         return "local_photos_mirror"
@@ -74,7 +74,7 @@ class local_photos_mirror_renderer(renderer.debuggable_abstaining_renderer):
         elif key == "Choose Photo":
             return self.choose_photo()
         else:
-            raise error("Unexpected operation")
+            raise Exception("Unexpected operation")
 
     def album_is_in_whitelist(self, name: str) -> bool:
         for wlalbum in self.album_whitelist:
index fb82f63393274f96e79951f9f63838a56e1a4496..caaee4de73c35e7ef99b8ec1212af2bdfde63846 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 
-from typing import Dict, List
+from typing import Dict, List, Optional
 import xml
 
 import generic_news_rss_renderer as gnrssr
@@ -28,7 +28,7 @@ class mynorthwest_rss_renderer(gnrssr.generic_news_rss_renderer):
     def get_details_page_prefix(self) -> str:
         return f"mynorthwest-details-{self.page_title}"
 
-    def find_image(self, item: xml.etree.ElementTree.Element) -> str:
+    def find_image(self, item: xml.etree.ElementTree.Element) -> Optional[str]:
         image = item.findtext("media:content")
         if image is not None:
             image_url = image.get("url")
index 1958c5ed2b8482cfe2498eb0583a0beba0479bee..df696f1f92304ab660f94992aca5df735c44334c 100644 (file)
@@ -4,8 +4,8 @@ from aiohttp import ClientSession
 import asyncio
 import datetime
 from dateutil.parser import parse
-import pymyq
-from typing import Dict, List
+import pymyq  # type: ignore
+from typing import Dict, List, Optional
 
 import constants
 import file_writer
@@ -17,8 +17,8 @@ import utils
 class garage_door_renderer(renderer.debuggable_abstaining_renderer):
     def __init__(self, name_to_timeout_dict: Dict[str, int]) -> None:
         super(garage_door_renderer, self).__init__(name_to_timeout_dict, False)
-        self.doors = None
-        self.last_update = None
+        self.doors: Optional[Dict] = None
+        self.last_update: Optional[datetime.datetime] = None
 
     def debug_prefix(self) -> str:
         return "myq"
@@ -30,7 +30,7 @@ class garage_door_renderer(renderer.debuggable_abstaining_renderer):
         elif key == "Update Page":
             return self.update_page()
         else:
-            raise error("Unknown operaiton")
+            raise Exception("Unknown operaiton")
 
     async def poll_myq(self) -> bool:
         async with ClientSession() as websession:
@@ -38,6 +38,7 @@ class garage_door_renderer(renderer.debuggable_abstaining_renderer):
                 secrets.myq_username, secrets.myq_password, websession
             )
             self.doors = myq.devices
+            assert(self.doors is not None)
             return len(self.doors) > 0
 
     def update_page(self) -> bool:
@@ -79,7 +80,9 @@ class garage_door_renderer(renderer.debuggable_abstaining_renderer):
         else:
             return str(state) + ", an unknown state for the door."
 
-    def do_door(self, name: str) -> str:
+    def do_door(self, name: str) -> Optional[str]:
+        if self.doors is None:
+            return None
         for key in self.doors:
             door = self.doors[key]
             if door.name == name:
@@ -94,7 +97,7 @@ class garage_door_renderer(renderer.debuggable_abstaining_renderer):
                 delta = (now - ts).total_seconds()
                 now = datetime.datetime.now()
                 is_night = now.hour <= 7 or now.hour >= 21
-                duration = utils.describe_duration_briefly(delta)
+                duration = utils.describe_duration_briefly(int(delta))
                 width = 0
                 if is_night and door.state == "open":
                     color = "border-color: #ff0000;"
index c18ebdc3707b2d8c0610e671c3a8c205e524b9aa..fd256696446dbe5c60e0d6ae96abbda317b325fe 100644 (file)
@@ -3,13 +3,13 @@
 import constants
 import globals
 import trigger
-from typing import Optional, Tuple
+from typing import List, Optional, Tuple
 
 
 class myq_trigger(trigger.trigger):
-    def get_triggered_page_list(self) -> Optional[Tuple[str, int]]:
+    def get_triggered_page_list(self) -> Optional[List[Tuple[str, int]]]:
         if globals.get("myq_triggered"):
             print("****** MyQ garage door is open page trigger ******")
-            return (constants.myq_pagename, trigger.trigger.PRIORITY_HIGH)
+            return [(constants.myq_pagename, trigger.trigger.PRIORITY_HIGH)]
         else:
             return None
index 097bd82f55f44dc85508eb54cef2037e0c1b27d3..a4050a20f0f47dcd9b2e2ed6ce294c34d2b5411c 100644 (file)
@@ -1,8 +1,8 @@
 #!/usr/bin/env python3
 
-import praw
+import praw  # type: ignore
 import random
-from typing import Callable, Dict, List
+from typing import Callable, Dict, Iterable, List, Set
 
 import constants
 import file_writer
@@ -10,7 +10,6 @@ import grab_bag
 import page_builder
 import profanity_filter
 import renderer
-import renderer_catalog
 import secrets
 
 
@@ -24,7 +23,7 @@ class reddit_renderer(renderer.debuggable_abstaining_renderer):
         *,
         min_votes: int = 20,
         font_size: int = 24,
-        additional_filters: List[Callable[[str], bool]] = [],
+        additional_filters: Iterable[Callable[[str], bool]] = [],
     ):
         super(reddit_renderer, self).__init__(name_to_timeout_dict, True)
         self.subreddit_list = subreddit_list
@@ -36,9 +35,11 @@ class reddit_renderer(renderer.debuggable_abstaining_renderer):
         self.min_votes = min_votes
         self.font_size = font_size
         self.messages = grab_bag.grab_bag()
-        self.filters = [profanity_filter.profanity_filter().contains_bad_words]
+        self.filters: List[Callable[..., bool]] = [
+            profanity_filter.profanity_filter().contains_bad_words
+        ]
         self.filters.extend(additional_filters)
-        self.deduper = set()
+        self.deduper: Set[str] = set()
 
     def debug_prefix(self) -> str:
         x = ""
@@ -53,26 +54,27 @@ class reddit_renderer(renderer.debuggable_abstaining_renderer):
         elif key == "Shuffle":
             return self.shuffle_messages()
         else:
-            raise error("Unexpected operation")
+            raise Exception("Unexpected operation")
 
     def append_message(self, messages: List[str]) -> None:
         for msg in messages:
-            if msg.title in self.deduper:
+            title = str(msg.title)
+            if title in self.deduper:
                 continue
             filtered = ""
-            for filter in self.filters:
-                if filter(msg.title) is True:
-                    filtered = filter.__name__
+            for filt in self.filters:
+                if filt(title) is True:
+                    filtered = filt.__name__
                     break
             if filtered != "":
-                print(f'Filter {filtered} struck down "{msg.title}"')
+                print(f'Filter {filtered} struck down "{title}"')
                 continue
             if msg.ups < self.min_votes:
-                print(f'"{msg.title}" doesn\'t have enough upvotes to be interesting')
+                print(f'"{title}" doesn\'t have enough upvotes to be interesting')
                 continue
 
             try:
-                self.deduper.add(msg.title)
+                self.deduper.add(title)
                 content = f"{msg.ups}"
                 if (
                     msg.thumbnail != "self"
@@ -91,7 +93,7 @@ class reddit_renderer(renderer.debuggable_abstaining_renderer):
 
     <!-- The content and author: -->
     <TD>
-      <B>{msg.title}</B><BR><FONT COLOR=#bbbbbb>({msg.author})</FONT>
+      <B>{title}</B><BR><FONT COLOR=#bbbbbb>({msg.author})</FONT>
     </TD>
   </TR>
 </TABLE>"""
@@ -99,7 +101,7 @@ class reddit_renderer(renderer.debuggable_abstaining_renderer):
             except:
                 self.debug_print("Unexpected exception, skipping message.")
 
-    def scrape_reddit(self) -> None:
+    def scrape_reddit(self) -> bool:
         self.deduper.clear()
         self.messages.clear()
         for subreddit in self.subreddit_list:
@@ -169,6 +171,7 @@ class quotes_reddit_renderer(reddit_renderer):
 
 
 class showerthoughts_reddit_renderer(reddit_renderer):
+    @staticmethod
     def dont_tell_me_about_gift_cards(msg: str) -> bool:
         return not "IMPORTANT PSA: No, you did not win a gift card" in msg
 
@@ -199,6 +202,6 @@ class lifeprotips_reddit_renderer(reddit_renderer):
         )
 
 
-# x = reddit_renderer({"Test", 1234}, ["seattle","bellevue"], min_votes=50, font_size=24)
-# x.periodic_render("Scrape")
-# x.periodic_render("Shuffle")
+#x = reddit_renderer({"Test", 1234}, ["seattle","bellevue"], min_votes=50, font_size=24)
+#x.periodic_render("Scrape")
+#x.periodic_render("Shuffle")
index 81f5a16d4efaf1858d38736ec944e4ee98392744..13a9416e5e8b0884963540e3c098d19bf12a4441 100644 (file)
@@ -68,7 +68,7 @@ class seattletimes_rss_renderer(gnrss.generic_news_rss_renderer):
                 details[detail.tag] = detail.text
         if "category" not in details:
             self.debug_print("No category in details?!")
-            self.debug_print(details)
+            self.debug_print(details.__repr__())
             return False
         interesting = False
         for x in seattletimes_rss_renderer.interesting_categories:
index 6d8768ee387a12ecd50dfb68471bb2d8d97c27ef..bba06030b90e7924327f5238ebc8bf0f3e2957be 100644 (file)
@@ -43,13 +43,16 @@ class stevens_pass_conditions_renderer(renderer.debuggable_abstaining_renderer):
                             for x in item.getchildren():
                                 if x.tag == "description":
                                     text = x.text
-                                    text = text.replace(
-                                        "<strong>Stevens Pass US2</strong><br/>", ""
-                                    )
-                                    text = text.replace("<br/><br/>", "<BR>")
-                                    text = text.replace(
-                                        "<strong>Elevation Meters:</strong>1238<BR>", ""
-                                    )
-                                    f.write("<P>\n%s\n" % text)
+                                    if text is not None:
+                                        text = text.replace(
+                                            "<strong>Stevens Pass US2</strong><br/>", ""
+                                        )
+                                        text = text.replace("<br/><br/>", "<BR>")
+                                        text = text.replace(
+                                            "<strong>Elevation Meters:</strong>1238<BR>", ""
+                                        )
+                                    else:
+                                        text = ""
+                                    f.write(f"<P>\n{text}\n")
                     return True
         return False
index 16273858ad3353e426f42ddfbe6b4670633adcb4..fda43bd99b2f08b7380ea023287eb5758a3f7c44 100644 (file)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 
-from typing import Dict, List, Tuple
-import yfinance as yf
+from typing import Dict, List, Optional, Tuple
+import yfinance as yf  # type: ignore
 
 import file_writer
 import renderer
@@ -30,7 +30,7 @@ class stock_quote_renderer(renderer.debuggable_abstaining_renderer):
         return info["shortName"]
 
     @staticmethod
-    def get_price(ticker: yf.ticker.Ticker) -> float:
+    def get_price(ticker: yf.ticker.Ticker) -> Optional[float]:
         """Get most recent price of a ticker."""
         keys = [
             "bid",
index a68c88df72c812b8afc8257247d53f8b9f4da4cb..d0f2722e111f3baea70f832b7a5d5be47da6c6e6 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 
-from bs4 import BeautifulSoup
+from bs4 import BeautifulSoup  # type: ignore
 import datetime
 import http.client
 import random
@@ -12,7 +12,6 @@ import grab_bag
 import page_builder
 import profanity_filter
 import renderer
-import renderer_catalog
 
 
 class stranger_events_renderer(renderer.debuggable_abstaining_renderer):
@@ -31,7 +30,7 @@ class stranger_events_renderer(renderer.debuggable_abstaining_renderer):
         elif key == "Shuffle Events":
             return self.shuffle_events()
         else:
-            raise error("Unknown operaiton")
+            raise Exception("Unknown operaiton")
 
     def get_style(self):
         return """
@@ -122,7 +121,7 @@ class stranger_events_renderer(renderer.debuggable_abstaining_renderer):
                 response = self.conn.getresponse()
                 if response.status != 200:
                     self.debug_print("Connection failed, status %d" % (response.status))
-                    self.debug_print(response.getheaders())
+                    self.debug_print(str(response.getheaders()))
                     continue
                 raw = response.read()
             except:
@@ -134,22 +133,22 @@ class stranger_events_renderer(renderer.debuggable_abstaining_renderer):
                 text = x.get_text()
                 if filter.contains_bad_words(text):
                     continue
-                raw = str(x)
-                raw = raw.replace(
+                raw_str = str(x)
+                raw_str = raw_str.replace(
                     'src="/', 'align="left" src="https://www.thestranger.com/'
                 )
-                raw = raw.replace('href="/', 'href="https://www.thestranger.com/')
-                raw = raw.replace("FREE", "Free")
-                raw = raw.replace("Save Event", "")
-                raw = re.sub("^\s*$", "", raw, 0, re.MULTILINE)
-                raw = re.sub(
+                raw_str = raw_str.replace('href="/', 'href="https://www.thestranger.com/')
+                raw_str = raw_str.replace("FREE", "Free")
+                raw_str = raw_str.replace("Save Event", "")
+                raw_str = re.sub("^\s*$", "", raw_str, 0, re.MULTILINE)
+                raw_str = re.sub(
                     '<span[^<>]*class="calendar-post-ticket"[^<>]*>.*</#span>',
                     "",
-                    raw,
+                    raw_str,
                     0,
                     re.DOTALL | re.IGNORECASE,
                 )
-                self.events.add(raw)
+                self.events.add(raw_str)
             self.debug_print(f"fetched {self.events.size()} events so far.")
         return self.events.size() > 0
 
index f2302da6f8ac39215dafedd9fd8f1d3926119d0c..842230bf184f1edc784d6a1581ffe905b5772d94 100644 (file)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 
 from abc import ABC, abstractmethod
-from typing import List, Tuple
+from typing import List, Tuple, Optional
 
 
 class trigger(ABC):
@@ -12,5 +12,5 @@ class trigger(ABC):
     PRIORITY_LOW = 0
 
     @abstractmethod
-    def get_triggered_page_list(self) -> List[Tuple[str, int]]:
+    def get_triggered_page_list(self) -> Optional[List[Tuple[str, int]]]:
         pass
index f2859f138ac3fd6b4e06c73132db469ece4f58dd..e595a9ad0c23f0980665f8da10fb5414446ded90 100644 (file)
@@ -2,7 +2,7 @@
 
 import random
 import re
-import tweepy
+import tweepy  # type: ignore
 from typing import Dict, List
 
 import file_writer
@@ -15,8 +15,8 @@ class twitter_renderer(renderer.debuggable_abstaining_renderer):
     def __init__(self, name_to_timeout_dict: Dict[str, int]) -> None:
         super(twitter_renderer, self).__init__(name_to_timeout_dict, False)
         self.debug = True
-        self.tweets_by_author = {}
-        self.handles_by_author = {}
+        self.tweets_by_author: Dict[str, List[tweepy.models.Status]] = {}
+        self.handles_by_author: Dict[str, str] = {}
         self.filter = profanity_filter.profanity_filter()
         self.urlfinder = re.compile(
             "((http|https)://[\-A-Za-z0-9\\.]+/[\?\&\-A-Za-z0-9_\\.]+)"
@@ -54,7 +54,7 @@ class twitter_renderer(renderer.debuggable_abstaining_renderer):
         elif key == "Shuffle Tweets":
             return self.shuffle_tweets()
         else:
-            raise error("Unexpected operation")
+            raise Exception("Unexpected operation")
 
     def fetch_tweets(self) -> bool:
         try:
@@ -104,12 +104,12 @@ class twitter_renderer(renderer.debuggable_abstaining_renderer):
 
 # Test
 # t = twitter_renderer(
-#    {"Fetch Tweets" : 1,
-#     "Shuffle Tweets" : 1})
+#     {"Fetch Tweets" : 1,
+#      "Shuffle Tweets" : 1})
 # x = "bla bla bla https://t.co/EjWnT3UA9U bla bla"
 # x = t.linkify(x)
-# print x
+# print(x)
 # if t.fetch_tweets() == 0:
-#    print("Error fetching tweets, none fetched.")
+#     print("Error fetching tweets, none fetched.")
 # else:
-#    t.shuffle_tweets()
+#     t.shuffle_tweets()
index f486a8218661a7cf5b733e2a61e3030e53931205..8f8551399cdc6a1e0c01f924af90fdf2e1150ec6 100644 (file)
--- a/utils.py
+++ b/utils.py
@@ -12,7 +12,7 @@ def timestamp() -> str:
     return t.strftime("%d/%b/%Y:%H:%M:%S%Z")
 
 
-def describe_age_of_file(filename) -> str:
+def describe_age_of_file(filename: str) -> str:
     try:
         now = time.time()
         ts = os.stat(filename).st_ctime
@@ -23,7 +23,7 @@ def describe_age_of_file(filename) -> str:
         return "?????"
 
 
-def describe_age_of_file_briefly(filename) -> str:
+def describe_age_of_file_briefly(filename: str) -> str:
     try:
         now = time.time()
         ts = os.stat(filename).st_ctime
index fbb3ed8170f873da0a3223088b8c60462a858c1a..452b06922077441d9052038d5264cd19c82044a1 100644 (file)
@@ -65,6 +65,7 @@ class weather_renderer(renderer.debuggable_abstaining_renderer):
             return f"{self.describe_magnitude(magnitude)} rain"
         elif snow > 0:
             return f"{self.describe_magnitude(magnitude)} snow"
+        return "rain"
 
     def fix_caps(self, s: str) -> str:
         r = ""
@@ -97,7 +98,7 @@ class weather_renderer(renderer.debuggable_abstaining_renderer):
         seen_snow = False
         cloud_count = 0
         clear_count = 0
-        total_snow = 0
+        total_snow = 0.0
         count = min(len(conditions), len(rain), len(snow))
         for x in range(0, count):
             seen_rain = rain[x] > 0
@@ -216,7 +217,7 @@ class weather_renderer(renderer.debuggable_abstaining_renderer):
         descr = self.fix_caps(descr)
         return descr
 
-    def fetch_weather(self) -> None:
+    def fetch_weather(self) -> bool:
         if self.file_prefix == "stevens":
             text_location = "Stevens Pass, WA"
             param = "lat=47.74&lon=-121.08"
@@ -266,10 +267,10 @@ class weather_renderer(renderer.debuggable_abstaining_renderer):
             ts = {}
             highs = {}
             lows = {}
-            wind = {}
-            conditions = {}
-            rain = {}
-            snow = {}
+            wind: Dict[str, List[float]] = {}
+            conditions: Dict[str, List[str]] = {}
+            rain: Dict[str, List[float]] = {}
+            snow: Dict[str, List[float]] = {}
             for x in range(0, count):
                 data = parsed_json["list"][x]
                 dt = data["dt_txt"]  # 2019-10-07 18:00:00
index 587c5510132d4e02c3667cebb06acbf50ae55a8b..5feb0ecb0b29f1a7ead4af723aed3ae9f3442785 100644 (file)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 
 import xml
-from typing import Dict, List
+from typing import Dict, List, Optional
 
 import generic_news_rss_renderer as gnrssr
 
@@ -28,11 +28,7 @@ class wsj_rss_renderer(gnrssr.generic_news_rss_renderer):
     def get_details_page_prefix(self) -> str:
         return f"wsj-details-{self.page_title}"
 
-    def find_image(self, item: xml.etree.ElementTree.Element) -> str:
-        image = item.findtext("image")
-        if image is not None:
-            url = image.get("url")
-            return url
+    def find_image(self, item: xml.etree.ElementTree.Element) -> Optional[str]:
         return None
 
     def should_use_https(self) -> bool:
@@ -61,5 +57,5 @@ class wsj_rss_renderer(gnrssr.generic_news_rss_renderer):
 #    [ "/rss/RSSWorldNews.xml" ],
 #    "Test" )
 # if x.fetch_news() == 0:
-#    print "Error fetching news, no items fetched."
+#    print("Error fetching news, no items fetched.")
 # x.shuffle_news()