Make subdirs type clean too.
authorScott <[email protected]>
Wed, 2 Feb 2022 21:09:30 +0000 (13:09 -0800)
committerScott <[email protected]>
Wed, 2 Feb 2022 21:09:30 +0000 (13:09 -0800)
15 files changed:
cached/weather_data.py
cached/weather_forecast.py
collect/bst.py
collect/shared_dict.py
dateparse/dateparse_utils.py
datetime_utils.py
ml/model_trainer.py
smart_home/chromecasts.py
smart_home/device.py
smart_home/lights.py
smart_home/outlets.py
smart_home/registry.py
type/centcount.py
type/money.py
type/rate.py

index 45b6e6efc5d9d3c4cbd07f0996633d835aa4a98a..7b86d029c607074d0fb88949ff9e50641d169401 100644 (file)
@@ -1,12 +1,12 @@
 #!/usr/bin/env python3
 
 #!/usr/bin/env python3
 
-from dataclasses import dataclass
 import datetime
 import json
 import logging
 import os
 import datetime
 import json
 import logging
 import os
-from typing import Any, List
 import urllib.request
 import urllib.request
+from dataclasses import dataclass
+from typing import Any, List
 
 from overrides import overrides
 
 
 from overrides import overrides
 
@@ -27,32 +27,31 @@ cfg.add_argument(
     type=str,
     default=f'{os.environ["HOME"]}/cache/.weather_summary_cache',
     metavar='FILENAME',
     type=str,
     default=f'{os.environ["HOME"]}/cache/.weather_summary_cache',
     metavar='FILENAME',
-    help='File in which to cache weather data'
+    help='File in which to cache weather data',
 )
 cfg.add_argument(
     '--weather_data_stalest_acceptable',
     type=argparse_utils.valid_duration,
 )
 cfg.add_argument(
     '--weather_data_stalest_acceptable',
     type=argparse_utils.valid_duration,
-    default=datetime.timedelta(seconds=7200),   # 2 hours
+    default=datetime.timedelta(seconds=7200),  # 2 hours
     metavar='DURATION',
     metavar='DURATION',
-    help='Maximum acceptable age of cached data.  If zero, forces a refetch'
+    help='Maximum acceptable age of cached data.  If zero, forces a refetch',
 )
 
 
 @dataclass
 class WeatherData:
 )
 
 
 @dataclass
 class WeatherData:
-    date: datetime.date              # The date
-    high: float                      # The predicted high in F
-    low: float                       # The predicted low in F
-    precipitation_inches: float      # Number of inches of precipitation / day
-    conditions: List[str]            # Conditions per ~3h window
-    most_common_condition: str       # The most common condition
-    icon: str                        # An icon to represent it
+    date: datetime.date  # The date
+    high: float  # The predicted high in F
+    low: float  # The predicted low in F
+    precipitation_inches: float  # Number of inches of precipitation / day
+    conditions: List[str]  # Conditions per ~3h window
+    most_common_condition: str  # The most common condition
+    icon: str  # An icon to represent it
 
 
 
 
[email protected]_autoloaded_singleton()
[email protected]_autoloaded_singleton()  # type: ignore
 class CachedWeatherData(persistent.Persistent):
 class CachedWeatherData(persistent.Persistent):
-    def __init__(self,
-                 weather_data = None):
+    def __init__(self, weather_data=None):
         if weather_data is not None:
             self.weather_data = weather_data
             return
         if weather_data is not None:
             self.weather_data = weather_data
             return
@@ -72,7 +71,7 @@ class CachedWeatherData(persistent.Persistent):
             "Sand": "🏜️",
             "Ash": "🌋",
             "Squall": "🌬",
             "Sand": "🏜️",
             "Ash": "🌋",
             "Squall": "🌬",
-            "Tornado": "🌪️"
+            "Tornado": "🌪️",
         }
         now = datetime.datetime.now()
         dates = set()
         }
         now = datetime.datetime.now()
         dates = set()
@@ -80,7 +79,7 @@ class CachedWeatherData(persistent.Persistent):
         lows = {}
         conditions = {}
         precip = {}
         lows = {}
         conditions = {}
         precip = {}
-        param = "id=5786882"   # Bellevue, WA
+        param = "id=5786882"  # Bellevue, WA
         key = "c0b160c49743622f62a9cd3cda0270b3"
         www = urllib.request.urlopen(
             f'http://api.openweathermap.org/data/2.5/weather?zip=98005,us&APPID={key}&units=imperial'
         key = "c0b160c49743622f62a9cd3cda0270b3"
         www = urllib.request.urlopen(
             f'http://api.openweathermap.org/data/2.5/weather?zip=98005,us&APPID={key}&units=imperial'
@@ -107,13 +106,13 @@ class CachedWeatherData(persistent.Persistent):
         if dt == now.date() and now.hour > 18 and condition == 'Clear':
             icon = '🌙'
         self.weather_data[dt] = WeatherData(
         if dt == now.date() and now.hour > 18 and condition == 'Clear':
             icon = '🌙'
         self.weather_data[dt] = WeatherData(
-            date = dt,
-            high = float(parsed_json["main"]["temp_max"]),
-            low = float(parsed_json["main"]["temp_min"]),
-            precipitation_inches = p / 25.4,
-            conditions = [condition],
-            most_common_condition = condition,
-            icon = icon,
+            date=dt,
+            high=float(parsed_json["main"]["temp_max"]),
+            low=float(parsed_json["main"]["temp_min"]),
+            precipitation_inches=p / 25.4,
+            conditions=[condition],
+            most_common_condition=condition,
+            icon=icon,
         )
 
         www = urllib.request.urlopen(
         )
 
         www = urllib.request.urlopen(
@@ -134,9 +133,9 @@ class CachedWeatherData(persistent.Persistent):
                 lows[dt] = None
                 conditions[dt] = []
             for temp in (
                 lows[dt] = None
                 conditions[dt] = []
             for temp in (
-                    data["main"]["temp"],
-                    data['main']['temp_min'],
-                    data['main']['temp_max'],
+                data["main"]["temp"],
+                data['main']['temp_min'],
+                data['main']['temp_max'],
             ):
                 if highs[dt] is None or temp > highs[dt]:
                     highs[dt] = temp
             ):
                 if highs[dt] is None or temp > highs[dt]:
                     highs[dt] = temp
@@ -160,10 +159,7 @@ class CachedWeatherData(persistent.Persistent):
         for dt in sorted(dates):
             if dt == today:
                 high = highs.get(dt, None)
         for dt in sorted(dates):
             if dt == today:
                 high = highs.get(dt, None)
-                if (
-                        high is not None and
-                        self.weather_data[today].high < high
-                ):
+                if high is not None and self.weather_data[today].high < high:
                     self.weather_data[today].high = high
                 continue
             most_common_condition = list_utils.most_common(conditions[dt])
                     self.weather_data[today].high = high
                 continue
             most_common_condition = list_utils.most_common(conditions[dt])
@@ -171,23 +167,24 @@ class CachedWeatherData(persistent.Persistent):
             if dt == now.date() and now.hour > 18 and condition == 'Clear':
                 icon = '🌙'
             self.weather_data[dt] = WeatherData(
             if dt == now.date() and now.hour > 18 and condition == 'Clear':
                 icon = '🌙'
             self.weather_data[dt] = WeatherData(
-                date = dt,
-                high = highs[dt],
-                low = lows[dt],
-                precipitation_inches = precip[dt] / 25.4,
-                conditions = conditions[dt],
-                most_common_condition = most_common_condition,
-                icon = icon
+                date=dt,
+                high=highs[dt],
+                low=lows[dt],
+                precipitation_inches=precip[dt] / 25.4,
+                conditions=conditions[dt],
+                most_common_condition=most_common_condition,
+                icon=icon,
             )
 
     @classmethod
     @overrides
     def load(cls) -> Any:
         if persistent.was_file_written_within_n_seconds(
             )
 
     @classmethod
     @overrides
     def load(cls) -> Any:
         if persistent.was_file_written_within_n_seconds(
-                config.config['weather_data_cachefile'],
-                config.config['weather_data_stalest_acceptable'].total_seconds(),
+            config.config['weather_data_cachefile'],
+            config.config['weather_data_stalest_acceptable'].total_seconds(),
         ):
             import pickle
         ):
             import pickle
+
             with open(config.config['weather_data_cachefile'], 'rb') as rf:
                 weather_data = pickle.load(rf)
                 return cls(weather_data)
             with open(config.config['weather_data_cachefile'], 'rb') as rf:
                 weather_data = pickle.load(rf)
                 return cls(weather_data)
@@ -196,6 +193,7 @@ class CachedWeatherData(persistent.Persistent):
     @overrides
     def save(self) -> bool:
         import pickle
     @overrides
     def save(self) -> bool:
         import pickle
+
         with open(config.config['weather_data_cachefile'], 'wb') as wf:
             pickle.dump(
                 self.weather_data,
         with open(config.config['weather_data_cachefile'], 'wb') as wf:
             pickle.dump(
                 self.weather_data,
index b34393832dec04120548aa08fb6822366cfc6ff6..58f53c383cb2425f1114165cfe3c151978e69cf5 100644 (file)
@@ -1,60 +1,59 @@
 #!/usr/bin/env python3
 
 #!/usr/bin/env python3
 
-from dataclasses import dataclass
 import datetime
 import logging
 import os
 import datetime
 import logging
 import os
-from typing import Any
 import urllib.request
 import urllib.request
+from dataclasses import dataclass
+from typing import Any
 
 import astral  # type: ignore
 
 import astral  # type: ignore
+import pytz
 from astral.sun import sun  # type: ignore
 from bs4 import BeautifulSoup  # type: ignore
 from overrides import overrides
 from astral.sun import sun  # type: ignore
 from bs4 import BeautifulSoup  # type: ignore
 from overrides import overrides
-import pytz
 
 import argparse_utils
 import config
 
 import argparse_utils
 import config
-import datetime_utils
 import dateparse.dateparse_utils as dp
 import dateparse.dateparse_utils as dp
+import datetime_utils
 import persistent
 import persistent
-import text_utils
 import smart_home.thermometers as temps
 import smart_home.thermometers as temps
-
+import text_utils
 
 logger = logging.getLogger(__name__)
 
 cfg = config.add_commandline_args(
     f'Cached Weather Forecast ({__file__})',
 
 logger = logging.getLogger(__name__)
 
 cfg = config.add_commandline_args(
     f'Cached Weather Forecast ({__file__})',
-    'Arguments controlling detailed weather rendering'
+    'Arguments controlling detailed weather rendering',
 )
 cfg.add_argument(
     '--weather_forecast_cachefile',
     type=str,
     default=f'{os.environ["HOME"]}/cache/.weather_forecast_cache',
     metavar='FILENAME',
 )
 cfg.add_argument(
     '--weather_forecast_cachefile',
     type=str,
     default=f'{os.environ["HOME"]}/cache/.weather_forecast_cache',
     metavar='FILENAME',
-    help='File in which to cache weather data'
+    help='File in which to cache weather data',
 )
 cfg.add_argument(
     '--weather_forecast_stalest_acceptable',
     type=argparse_utils.valid_duration,
 )
 cfg.add_argument(
     '--weather_forecast_stalest_acceptable',
     type=argparse_utils.valid_duration,
-    default=datetime.timedelta(seconds=7200),   # 2 hours
+    default=datetime.timedelta(seconds=7200),  # 2 hours
     metavar='DURATION',
     metavar='DURATION',
-    help='Maximum acceptable age of cached data.  If zero, forces a refetch'
+    help='Maximum acceptable age of cached data.  If zero, forces a refetch',
 )
 
 
 @dataclass
 class WeatherForecast:
 )
 
 
 @dataclass
 class WeatherForecast:
-    date: datetime.date                # The date
-    sunrise: datetime.datetime         # Sunrise datetime
-    sunset: datetime.datetime          # Sunset datetime
-    description: str                   # Textual description of weather
+    date: datetime.date  # The date
+    sunrise: datetime.datetime  # Sunrise datetime
+    sunset: datetime.datetime  # Sunset datetime
+    description: str  # Textual description of weather
 
 
 
 
[email protected]_autoloaded_singleton()
[email protected]_autoloaded_singleton()  # type: ignore
 class CachedDetailedWeatherForecast(persistent.Persistent):
 class CachedDetailedWeatherForecast(persistent.Persistent):
-    def __init__(self, forecasts = None):
+    def __init__(self, forecasts=None):
         if forecasts is not None:
             self.forecasts = forecasts
             return
         if forecasts is not None:
             self.forecasts = forecasts
             return
@@ -82,8 +81,7 @@ class CachedDetailedWeatherForecast(persistent.Persistent):
         last_dt = now
         dt = now
         for (day, txt) in zip(
         last_dt = now
         dt = now
         for (day, txt) in zip(
-                forecast.find_all('b'),
-                forecast.find_all(class_='col-sm-10 forecast-text')
+            forecast.find_all('b'), forecast.find_all(class_='col-sm-10 forecast-text')
         ):
             last_dt = dt
             try:
         ):
             last_dt = dt
             try:
@@ -112,20 +110,21 @@ class CachedDetailedWeatherForecast(persistent.Persistent):
                 self.forecasts[dt.date()].description += '\n' + blurb
             else:
                 self.forecasts[dt.date()] = WeatherForecast(
                 self.forecasts[dt.date()].description += '\n' + blurb
             else:
                 self.forecasts[dt.date()] = WeatherForecast(
-                    date = dt,
-                    sunrise = sunrise,
-                    sunset = sunset,
-                    description = blurb,
+                    date=dt,
+                    sunrise=sunrise,
+                    sunset=sunset,
+                    description=blurb,
                 )
 
     @classmethod
     @overrides
     def load(cls) -> Any:
         if persistent.was_file_written_within_n_seconds(
                 )
 
     @classmethod
     @overrides
     def load(cls) -> Any:
         if persistent.was_file_written_within_n_seconds(
-                config.config['weather_forecast_cachefile'],
-                config.config['weather_forecast_stalest_acceptable'].total_seconds(),
+            config.config['weather_forecast_cachefile'],
+            config.config['weather_forecast_stalest_acceptable'].total_seconds(),
         ):
             import pickle
         ):
             import pickle
+
             with open(config.config['weather_forecast_cachefile'], 'rb') as rf:
                 weather_data = pickle.load(rf)
                 return cls(weather_data)
             with open(config.config['weather_forecast_cachefile'], 'rb') as rf:
                 weather_data = pickle.load(rf)
                 return cls(weather_data)
@@ -134,6 +133,7 @@ class CachedDetailedWeatherForecast(persistent.Persistent):
     @overrides
     def save(self) -> bool:
         import pickle
     @overrides
     def save(self) -> bool:
         import pickle
+
         with open(config.config['weather_forecast_cachefile'], 'wb') as wf:
             pickle.dump(
                 self.forecasts,
         with open(config.config['weather_forecast_cachefile'], 'wb') as wf:
             pickle.dump(
                 self.forecasts,
index 72a3b7738b981878b9b08eaba67bca2b33314f4f..9d6525946e8131728896d86f3400c38c5ba528e7 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 
 #!/usr/bin/env python3
 
-from typing import Any, Optional, List
+from typing import Any, Generator, List, Optional
 
 
 class Node(object):
 
 
 class Node(object):
@@ -9,8 +9,8 @@ class Node(object):
         Note: value can be anything as long as it is comparable.
         Check out @functools.total_ordering.
         """
         Note: value can be anything as long as it is comparable.
         Check out @functools.total_ordering.
         """
-        self.left = None
-        self.right = None
+        self.left: Optional[Node] = None
+        self.right: Optional[Node] = None
         self.value = value
 
 
         self.value = value
 
 
@@ -84,16 +84,18 @@ class BinarySearchTree(object):
         """Find helper"""
         if value == node.value:
             return node
         """Find helper"""
         if value == node.value:
             return node
-        elif (value < node.value and node.left is not None):
+        elif value < node.value and node.left is not None:
             return self._find(value, node.left)
             return self._find(value, node.left)
-        elif (value > node.value and node.right is not None):
+        elif value > node.value and node.right is not None:
             return self._find(value, node.right)
         return None
 
             return self._find(value, node.right)
         return None
 
-    def _parent_path(self, current: Node, target: Node):
+    def _parent_path(
+        self, current: Optional[Node], target: Node
+    ) -> List[Optional[Node]]:
         if current is None:
             return [None]
         if current is None:
             return [None]
-        ret = [current]
+        ret: List[Optional[Node]] = [current]
         if target.value == current.value:
             return ret
         elif target.value < current.value:
         if target.value == current.value:
             return ret
         elif target.value < current.value:
@@ -104,7 +106,7 @@ class BinarySearchTree(object):
             ret.extend(self._parent_path(current.right, target))
             return ret
 
             ret.extend(self._parent_path(current.right, target))
             return ret
 
-    def parent_path(self, node: Node) -> Optional[List[Node]]:
+    def parent_path(self, node: Node) -> List[Optional[Node]]:
         """Return a list of nodes representing the path from
         the tree's root to the node argument.  If the node does
         not exist in the tree for some reason, the last element
         """Return a list of nodes representing the path from
         the tree's root to the node argument.  If the node does
         not exist in the tree for some reason, the last element
@@ -456,7 +458,7 @@ class BinarySearchTree(object):
             if node.right is not None:
                 yield from self._iterate_by_depth(node.right, depth - 1)
 
             if node.right is not None:
                 yield from self._iterate_by_depth(node.right, depth - 1)
 
-    def iterate_nodes_by_depth(self, depth: int):
+    def iterate_nodes_by_depth(self, depth: int) -> Generator[Node, None, None]:
         """
         Iterate only the leaf nodes in the tree.
 
         """
         Iterate only the leaf nodes in the tree.
 
@@ -518,13 +520,16 @@ class BinarySearchTree(object):
             return x
 
         path = self.parent_path(node)
             return x
 
         path = self.parent_path(node)
+        assert path[-1]
         assert path[-1] == node
         path = path[:-1]
         path.reverse()
         for ancestor in path:
         assert path[-1] == node
         path = path[:-1]
         path.reverse()
         for ancestor in path:
+            assert ancestor
             if node != ancestor.right:
                 return ancestor
             node = ancestor
             if node != ancestor.right:
                 return ancestor
             node = ancestor
+        raise Exception()
 
     def _depth(self, node: Node, sofar: int) -> int:
         depth_left = sofar + 1
 
     def _depth(self, node: Node, sofar: int) -> int:
         depth_left = sofar + 1
@@ -569,7 +574,9 @@ class BinarySearchTree(object):
     def height(self):
         return self.depth()
 
     def height(self):
         return self.depth()
 
-    def repr_traverse(self, padding: str, pointer: str, node: Node, has_right_sibling: bool) -> str:
+    def repr_traverse(
+        self, padding: str, pointer: str, node: Optional[Node], has_right_sibling: bool
+    ) -> str:
         if node is not None:
             viz = f'\n{padding}{pointer}{node.value}'
             if has_right_sibling:
         if node is not None:
             viz = f'\n{padding}{pointer}{node.value}'
             if has_right_sibling:
@@ -583,7 +590,9 @@ class BinarySearchTree(object):
             else:
                 pointer_left = "└──"
 
             else:
                 pointer_left = "└──"
 
-            viz += self.repr_traverse(padding, pointer_left, node.left, node.right is not None)
+            viz += self.repr_traverse(
+                padding, pointer_left, node.left, node.right is not None
+            )
             viz += self.repr_traverse(padding, pointer_right, node.right, False)
             return viz
         return ""
             viz += self.repr_traverse(padding, pointer_right, node.right, False)
             return viz
         return ""
@@ -619,11 +628,14 @@ class BinarySearchTree(object):
         else:
             pointer_left = "├──"
 
         else:
             pointer_left = "├──"
 
-        ret += self.repr_traverse('', pointer_left, self.root.left, self.root.left is not None)
+        ret += self.repr_traverse(
+            '', pointer_left, self.root.left, self.root.left is not None
+        )
         ret += self.repr_traverse('', pointer_right, self.root.right, False)
         return ret
 
 
 if __name__ == '__main__':
     import doctest
         ret += self.repr_traverse('', pointer_right, self.root.right, False)
         return ret
 
 
 if __name__ == '__main__':
     import doctest
+
     doctest.testmod()
     doctest.testmod()
index 0d8e7c2f7a36aa5ddb7c54c72aecddbf56df71c3..7c84c14c073743ea1e452d58393f8f29d280ed23 100644 (file)
@@ -30,14 +30,14 @@ This class is based on https://github.com/luizalabs/shared-memory-dict
 import pickle
 from contextlib import contextmanager
 from functools import wraps
 import pickle
 from contextlib import contextmanager
 from functools import wraps
-from multiprocessing import shared_memory, RLock
+from multiprocessing import RLock, shared_memory
 from typing import (
     Any,
     Dict,
     Generator,
 from typing import (
     Any,
     Dict,
     Generator,
-    KeysView,
     ItemsView,
     Iterator,
     ItemsView,
     Iterator,
+    KeysView,
     Optional,
     ValuesView,
 )
     Optional,
     ValuesView,
 )
index be5e1b5312a7beb83fea7dc7b0137f08059879a3..7ca3cf3123a2a460c375a2ef811575d656c0d572 100755 (executable)
@@ -7,7 +7,6 @@ Parse dates in a variety of formats.
 
 import datetime
 import functools
 
 import datetime
 import functools
-import holidays  # type: ignore
 import logging
 import re
 import sys
 import logging
 import re
 import sys
@@ -16,21 +15,21 @@ from typing import Any, Callable, Dict, Optional
 import antlr4  # type: ignore
 import dateutil.easter
 import dateutil.tz
 import antlr4  # type: ignore
 import dateutil.easter
 import dateutil.tz
+import holidays  # type: ignore
 import pytz
 
 import acl
 import bootstrap
 import pytz
 
 import acl
 import bootstrap
+import decorator_utils
+from dateparse.dateparse_utilsLexer import dateparse_utilsLexer  # type: ignore
+from dateparse.dateparse_utilsListener import dateparse_utilsListener  # type: ignore
+from dateparse.dateparse_utilsParser import dateparse_utilsParser  # type: ignore
 from datetime_utils import (
     TimeUnit,
 from datetime_utils import (
     TimeUnit,
-    n_timeunits_from_base,
-    datetime_to_date,
     date_to_datetime,
     date_to_datetime,
+    datetime_to_date,
+    n_timeunits_from_base,
 )
 )
-from dateparse.dateparse_utilsLexer import dateparse_utilsLexer  # type: ignore
-from dateparse.dateparse_utilsListener import dateparse_utilsListener  # type: ignore
-from dateparse.dateparse_utilsParser import dateparse_utilsParser  # type: ignore
-import decorator_utils
-
 
 logger = logging.getLogger(__name__)
 
 
 logger = logging.getLogger(__name__)
 
@@ -225,8 +224,9 @@ class DateParser(dateparse_utilsListener):
         to timezone naive (i.e. tzinfo = None).
         """
         dt = self.datetime
         to timezone naive (i.e. tzinfo = None).
         """
         dt = self.datetime
-        if tz is not None:
-            dt = dt.replace(tzinfo=None).astimezone(tz=tz)
+        if dt is not None:
+            if tz is not None:
+                dt = dt.replace(tzinfo=None).astimezone(tz=tz)
         return dt
 
     # -- helpers --
         return dt
 
     # -- helpers --
@@ -376,25 +376,25 @@ class DateParser(dateparse_utilsListener):
 
         # Try pytz
         try:
 
         # Try pytz
         try:
-            tz = pytz.timezone(txt)
-            if tz is not None:
-                return tz
+            tz1 = pytz.timezone(txt)
+            if tz1 is not None:
+                return tz1
         except Exception:
             pass
 
         # Try dateutil
         try:
         except Exception:
             pass
 
         # Try dateutil
         try:
-            tz = dateutil.tz.gettz(txt)
-            if tz is not None:
-                return tz
+            tz2 = dateutil.tz.gettz(txt)
+            if tz2 is not None:
+                return tz2
         except Exception:
             pass
 
         # Try constructing an offset in seconds
         try:
         except Exception:
             pass
 
         # Try constructing an offset in seconds
         try:
-            sign = txt[0]
-            if sign == '-' or sign == '+':
-                sign = +1 if sign == '+' else -1
+            txt_sign = txt[0]
+            if txt_sign == '-' or txt_sign == '+':
+                sign = +1 if txt_sign == '+' else -1
                 hour = int(txt[1:3])
                 minute = int(txt[-2:])
                 offset = sign * (hour * 60 * 60) + sign * (minute * 60)
                 hour = int(txt[1:3])
                 minute = int(txt[-2:])
                 offset = sign * (hour * 60 * 60) + sign * (minute * 60)
@@ -441,6 +441,7 @@ class DateParser(dateparse_utilsListener):
         # Apply resudual adjustments to times here when we have a
         # datetime.
         self.datetime = self.datetime + self.timedelta
         # Apply resudual adjustments to times here when we have a
         # datetime.
         self.datetime = self.datetime + self.timedelta
+        assert self.datetime
         self.time = datetime.time(
             self.datetime.hour,
             self.datetime.minute,
         self.time = datetime.time(
             self.datetime.hour,
             self.datetime.minute,
@@ -547,7 +548,7 @@ class DateParser(dateparse_utilsListener):
             elif unit == TimeUnit.HOURS:
                 self.timedelta = datetime.timedelta(hours=count)
             else:
             elif unit == TimeUnit.HOURS:
                 self.timedelta = datetime.timedelta(hours=count)
             else:
-                raise ParseException()
+                raise ParseException(f'Invalid Unit: "{unit}"')
 
     def exitDeltaPlusMinusExpr(
         self, ctx: dateparse_utilsParser.DeltaPlusMinusExprContext
 
     def exitDeltaPlusMinusExpr(
         self, ctx: dateparse_utilsParser.DeltaPlusMinusExprContext
@@ -1027,6 +1028,7 @@ def main() -> None:
             logger.exception(e)
             print("Unrecognized.")
         else:
             logger.exception(e)
             print("Unrecognized.")
         else:
+            assert dt
             print(dt.strftime('%A %Y/%m/%d %H:%M:%S.%f %Z(%z)'))
     sys.exit(0)
 
             print(dt.strftime('%A %Y/%m/%d %H:%M:%S.%f %Z(%z)'))
     sys.exit(0)
 
index 6f504f6c304b850e830cffab08d0ed8be67fed39..fb859719796c5cc377e328bacf2aecf2b6bb81f6 100644 (file)
@@ -229,7 +229,7 @@ def datetime_to_time(dt: datetime.datetime) -> datetime.time:
     return datetime_to_date_and_time(dt)[1]
 
 
     return datetime_to_date_and_time(dt)[1]
 
 
-class TimeUnit(enum.Enum):
+class TimeUnit(enum.IntEnum):
     """An enum to represent units with which we can compute deltas."""
 
     MONDAYS = 0
     """An enum to represent units with which we can compute deltas."""
 
     MONDAYS = 0
index 213a1814cff5e98507e30c19e17669ab123886ce..6fc0da071e7dd6e2e026b8bbb7d7780c81015985 100644 (file)
@@ -2,7 +2,6 @@
 
 from __future__ import annotations
 
 
 from __future__ import annotations
 
-from abc import ABC, abstractmethod
 import datetime
 import glob
 import logging
 import datetime
 import glob
 import logging
@@ -10,20 +9,21 @@ import os
 import pickle
 import random
 import sys
 import pickle
 import random
 import sys
+import warnings
+from abc import ABC, abstractmethod
 from types import SimpleNamespace
 from typing import Any, List, NamedTuple, Optional, Set, Tuple
 from types import SimpleNamespace
 from typing import Any, List, NamedTuple, Optional, Set, Tuple
-import warnings
 
 import numpy as np
 from sklearn.model_selection import train_test_split  # type:ignore
 from sklearn.preprocessing import MinMaxScaler  # type: ignore
 
 
 import numpy as np
 from sklearn.model_selection import train_test_split  # type:ignore
 from sklearn.preprocessing import MinMaxScaler  # type: ignore
 
-from ansi import bold, reset
 import argparse_utils
 import config
 import argparse_utils
 import config
-from decorator_utils import timed
 import executors
 import parallelize as par
 import executors
 import parallelize as par
+from ansi import bold, reset
+from decorator_utils import timed
 
 logger = logging.getLogger(__file__)
 
 
 logger = logging.getLogger(__file__)
 
@@ -81,8 +81,8 @@ class OutputSpec(NamedTuple):
     model_filename: Optional[str]
     model_info_filename: Optional[str]
     scaler_filename: Optional[str]
     model_filename: Optional[str]
     model_info_filename: Optional[str]
     scaler_filename: Optional[str]
-    training_score: float
-    test_score: float
+    training_score: np.float64
+    test_score: np.float64
 
 
 class TrainingBlueprint(ABC):
 
 
 class TrainingBlueprint(ABC):
@@ -131,9 +131,9 @@ class TrainingBlueprint(ABC):
             modelid_to_params[model.get_id()] = str(params)
 
         best_model = None
             modelid_to_params[model.get_id()] = str(params)
 
         best_model = None
-        best_score = None
-        best_test_score = None
-        best_training_score = None
+        best_score: Optional[np.float64] = None
+        best_test_score: Optional[np.float64] = None
+        best_training_score: Optional[np.float64] = None
         best_params = None
         for model in smart_future.wait_any(models):
             params = modelid_to_params[model.get_id()]
         best_params = None
         for model in smart_future.wait_any(models):
             params = modelid_to_params[model.get_id()]
@@ -170,6 +170,9 @@ class TrainingBlueprint(ABC):
             print(msg)
             logger.info(msg)
 
             print(msg)
             logger.info(msg)
 
+        assert best_training_score
+        assert best_test_score
+        assert best_params
         (
             scaler_filename,
             model_filename,
         (
             scaler_filename,
             model_filename,
@@ -369,14 +372,14 @@ Testing set score: {test_score:.2f}%"""
                 and input_utils.yn_response("Write the model? [y,n]: ") == "y"
             ):
                 scaler_filename = f"{self.spec.basename}_scaler.sav"
                 and input_utils.yn_response("Write the model? [y,n]: ") == "y"
             ):
                 scaler_filename = f"{self.spec.basename}_scaler.sav"
-                with open(scaler_filename, "wb") as f:
-                    pickle.dump(scaler, f)
+                with open(scaler_filename, "wb") as fb:
+                    pickle.dump(scaler, fb)
                 msg = f"Wrote {scaler_filename}"
                 print(msg)
                 logger.info(msg)
                 model_filename = f"{self.spec.basename}_model.sav"
                 msg = f"Wrote {scaler_filename}"
                 print(msg)
                 logger.info(msg)
                 model_filename = f"{self.spec.basename}_model.sav"
-                with open(model_filename, "wb") as f:
-                    pickle.dump(model, f)
+                with open(model_filename, "wb") as fb:
+                    pickle.dump(model, fb)
                 msg = f"Wrote {model_filename}"
                 print(msg)
                 logger.info(msg)
                 msg = f"Wrote {model_filename}"
                 print(msg)
                 logger.info(msg)
index a5db86f3f003395d61ee2436688e44eaaa55d963..bd2a80c54c2af85c191cf5d5272df6bcc7d89cf1 100644 (file)
@@ -6,17 +6,18 @@ import atexit
 import datetime
 import logging
 import threading
 import datetime
 import logging
 import threading
+from typing import Any, List
 
 import pychromecast
 
 
 import pychromecast
 
-from decorator_utils import memoized
 import smart_home.device as dev
 import smart_home.device as dev
+from decorator_utils import memoized
 
 logger = logging.getLogger(__name__)
 
 
 class BaseChromecast(dev.Device):
 
 logger = logging.getLogger(__name__)
 
 
 class BaseChromecast(dev.Device):
-    ccasts = []
+    ccasts: List[Any] = []
     refresh_ts = None
     browser = None
     lock = threading.Lock()
     refresh_ts = None
     browser = None
     lock = threading.Lock()
@@ -25,31 +26,32 @@ class BaseChromecast(dev.Device):
         super().__init__(name.strip(), mac.strip(), keywords)
         ip = self.get_ip()
         now = datetime.datetime.now()
         super().__init__(name.strip(), mac.strip(), keywords)
         ip = self.get_ip()
         now = datetime.datetime.now()
-        with BaseChromecast.lock as l:
+        with BaseChromecast.lock:
             if (
             if (
-                    BaseChromecast.refresh_ts is None
-                    or (now - BaseChromecast.refresh_ts).total_seconds() > 60
+                BaseChromecast.refresh_ts is None
+                or (now - BaseChromecast.refresh_ts).total_seconds() > 60
             ):
                 logger.debug('Refreshing the shared chromecast info list')
                 if BaseChromecast.browser is not None:
                     BaseChromecast.browser.stop_discovery()
             ):
                 logger.debug('Refreshing the shared chromecast info list')
                 if BaseChromecast.browser is not None:
                     BaseChromecast.browser.stop_discovery()
-                BaseChromecast.ccasts, BaseChromecast.browser = pychromecast.get_chromecasts(
-                    timeout=15.0
-                )
+                (
+                    BaseChromecast.ccasts,
+                    BaseChromecast.browser,
+                ) = pychromecast.get_chromecasts(timeout=15.0)
+                assert BaseChromecast.browser
                 atexit.register(BaseChromecast.browser.stop_discovery)
                 BaseChromecast.refresh_ts = now
 
         self.cast = None
         for cc in BaseChromecast.ccasts:
                 atexit.register(BaseChromecast.browser.stop_discovery)
                 BaseChromecast.refresh_ts = now
 
         self.cast = None
         for cc in BaseChromecast.ccasts:
-            if (
-                    cc.cast_info.host == ip
-                    and cc.cast_info.cast_type != 'group'
-            ):
+            if cc.cast_info.host == ip and cc.cast_info.cast_type != 'group':
                 logger.debug(f'Found chromecast at {ip}: {cc}')
                 self.cast = cc
                 self.cast.wait(timeout=1.0)
         if self.cast is None:
                 logger.debug(f'Found chromecast at {ip}: {cc}')
                 self.cast = cc
                 self.cast.wait(timeout=1.0)
         if self.cast is None:
-            raise Exception(f'Can\'t find ccast device at {ip}, is that really a ccast device?')
+            raise Exception(
+                f'Can\'t find ccast device at {ip}, is that really a ccast device?'
+            )
 
     def is_idle(self):
         return self.cast.is_idle
 
     def is_idle(self):
         return self.cast.is_idle
@@ -116,4 +118,3 @@ class BaseChromecast(dev.Device):
             f"Chromecast({self.cast.socket_client.host!r}, port={self.cast.socket_client.port!r}, "
             f"device={self.cast.cast_info.friendly_name!r})"
         )
             f"Chromecast({self.cast.socket_client.host!r}, port={self.cast.socket_client.port!r}, "
             f"device={self.cast.cast_info.friendly_name!r})"
         )
-
index 9675b7c66ed26527d6c389854a8cf488a012f530..02717a343f433414398425c7d16e4939414ecf2a 100644 (file)
@@ -8,17 +8,17 @@ import arper
 
 class Device(object):
     def __init__(
 
 class Device(object):
     def __init__(
-            self,
-            name: str,
-            mac: str,
-            keywords: Optional[List[str]],
+        self,
+        name: str,
+        mac: str,
+        keywords: Optional[List[str]],
     ):
         self.name = name
         self.mac = mac
         self.keywords = keywords
         self.arper = arper.Arper()
         if keywords is not None:
     ):
         self.name = name
         self.mac = mac
         self.keywords = keywords
         self.arper = arper.Arper()
         if keywords is not None:
-            self.kws = keywords.split()
+            self.kws = keywords
         else:
             self.kws = []
 
         else:
             self.kws = []
 
index 64f2105ffe8a4de0864e95a14d2b05703010a2bb..240e7da84412f089702b7b50bd4ce5b5080ca0eb 100644 (file)
@@ -2,7 +2,6 @@
 
 """Utilities for dealing with the smart lights."""
 
 
 """Utilities for dealing with the smart lights."""
 
-from abc import abstractmethod
 import datetime
 import json
 import logging
 import datetime
 import json
 import logging
@@ -10,10 +9,11 @@ import os
 import re
 import subprocess
 import sys
 import re
 import subprocess
 import sys
+from abc import abstractmethod
 from typing import Any, Dict, List, Optional, Tuple
 
 from typing import Any, Dict, List, Optional, Tuple
 
-from overrides import overrides
 import tinytuya as tt
 import tinytuya as tt
+from overrides import overrides
 
 import ansi
 import argparse_utils
 
 import ansi
 import argparse_utils
@@ -21,8 +21,8 @@ import arper
 import config
 import logging_utils
 import smart_home.device as dev
 import config
 import logging_utils
 import smart_home.device as dev
-from google_assistant import ask_google, GoogleResponse
-from decorator_utils import timeout, memoized
+from decorator_utils import memoized, timeout
+from google_assistant import GoogleResponse, ask_google
 
 logger = logging.getLogger(__name__)
 
 
 logger = logging.getLogger(__name__)
 
@@ -39,9 +39,7 @@ args.add_argument(
 )
 
 
 )
 
 
-@timeout(
-    5.0, use_signals=False, error_message="Timed out waiting for tplink.py"
-)
+@timeout(5.0, use_signals=False, error_message="Timed out waiting for tplink.py")
 def tplink_light_command(command: str) -> bool:
     result = os.system(command)
     signal = result & 0xFF
 def tplink_light_command(command: str) -> bool:
     result = os.system(command)
     signal = result & 0xFF
@@ -69,9 +67,9 @@ class BaseLight(dev.Device):
     def parse_color_string(color: str) -> Optional[Tuple[int, int, int]]:
         m = re.match(
             'r#?([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])',
     def parse_color_string(color: str) -> Optional[Tuple[int, int, int]]:
         m = re.match(
             'r#?([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])',
-            color
+            color,
         )
         )
-        if m is not None and len(m.group) == 3:
+        if m is not None and len(m.groups()) == 3:
             red = int(m.group(0), 16)
             green = int(m.group(1), 16)
             blue = int(m.group(2), 16)
             red = int(m.group(0), 16)
             green = int(m.group(1), 16)
             blue = int(m.group(2), 16)
@@ -147,7 +145,9 @@ class GoogleLight(BaseLight):
         r = ask_google(f"is {self.goog_name()} on?")
         if not r.success:
             return False
         r = ask_google(f"is {self.goog_name()} on?")
         if not r.success:
             return False
-        return 'is on' in r.audio_transcription
+        if r.audio_transcription is not None:
+            return 'is on' in r.audio_transcription
+        raise Exception("Can't reach Google?!")
 
     @overrides
     def is_off(self) -> bool:
 
     @overrides
     def is_off(self) -> bool:
@@ -163,11 +163,12 @@ class GoogleLight(BaseLight):
 
         # the bookcase one is set to 40% bright
         txt = r.audio_transcription
 
         # the bookcase one is set to 40% bright
         txt = r.audio_transcription
-        m = re.search(r"(\d+)% bright", txt)
-        if m is not None:
-            return int(m.group(1))
-        if "is off" in txt:
-            return 0
+        if txt is not None:
+            m = re.search(r"(\d+)% bright", txt)
+            if m is not None:
+                return int(m.group(1))
+            if "is off" in txt:
+                return 0
         return None
 
     @overrides
         return None
 
     @overrides
@@ -301,9 +302,7 @@ class TPLinkLight(BaseLight):
     def get_children(self) -> List[str]:
         return self.children
 
     def get_children(self) -> List[str]:
         return self.children
 
-    def command(
-        self, cmd: str, child: str = None, extra_args: str = None
-    ) -> bool:
+    def command(self, cmd: str, child: str = None, extra_args: str = None) -> bool:
         cmd = self.get_cmdline(child) + f"-c {cmd}"
         if extra_args is not None:
             cmd += f" {extra_args}"
         cmd = self.get_cmdline(child) + f"-c {cmd}"
         if extra_args is not None:
             cmd += f" {extra_args}"
@@ -333,9 +332,7 @@ class TPLinkLight(BaseLight):
     def make_color(self, color: str) -> bool:
         raise NotImplementedError
 
     def make_color(self, color: str) -> bool:
         raise NotImplementedError
 
-    @timeout(
-        10.0, use_signals=False, error_message="Timed out waiting for tplink.py"
-    )
+    @timeout(10.0, use_signals=False, error_message="Timed out waiting for tplink.py")
     def get_info(self) -> Optional[Dict]:
         cmd = self.get_cmdline() + "-c info"
         out = subprocess.getoutput(cmd)
     def get_info(self) -> Optional[Dict]:
         cmd = self.get_cmdline() + "-c info"
         out = subprocess.getoutput(cmd)
index c079cfd09d9cbbfbb8595379770ebc1c8d7497ae..d4a4886dd38d1a932494e90f060f4ce884de744d 100644 (file)
@@ -2,7 +2,6 @@
 
 """Utilities for dealing with the smart outlets."""
 
 
 """Utilities for dealing with the smart outlets."""
 
-from abc import abstractmethod
 import asyncio
 import atexit
 import datetime
 import asyncio
 import atexit
 import datetime
@@ -12,10 +11,12 @@ import os
 import re
 import subprocess
 import sys
 import re
 import subprocess
 import sys
+from abc import abstractmethod
 from typing import Any, Dict, List, Optional
 
 from meross_iot.http_api import MerossHttpClient
 from meross_iot.manager import MerossManager
 from typing import Any, Dict, List, Optional
 
 from meross_iot.http_api import MerossHttpClient
 from meross_iot.manager import MerossManager
+from overrides import overrides
 
 import argparse_utils
 import config
 
 import argparse_utils
 import config
@@ -23,8 +24,8 @@ import decorator_utils
 import logging_utils
 import scott_secrets
 import smart_home.device as dev
 import logging_utils
 import scott_secrets
 import smart_home.device as dev
-from google_assistant import ask_google, GoogleResponse
-from decorator_utils import timeout, memoized
+from decorator_utils import memoized, timeout
+from google_assistant import GoogleResponse, ask_google
 
 logger = logging.getLogger(__name__)
 
 
 logger = logging.getLogger(__name__)
 
@@ -41,9 +42,7 @@ parser.add_argument(
 )
 
 
 )
 
 
-@timeout(
-    5.0, use_signals=False, error_message="Timed out waiting for tplink.py"
-)
+@timeout(5.0, use_signals=False, error_message="Timed out waiting for tplink.py")
 def tplink_outlet_command(command: str) -> bool:
     result = os.system(command)
     signal = result & 0xFF
 def tplink_outlet_command(command: str) -> bool:
     result = os.system(command)
     signal = result & 0xFF
@@ -66,7 +65,6 @@ def tplink_outlet_command(command: str) -> bool:
 class BaseOutlet(dev.Device):
     def __init__(self, name: str, mac: str, keywords: str = "") -> None:
         super().__init__(name.strip(), mac.strip(), keywords)
 class BaseOutlet(dev.Device):
     def __init__(self, name: str, mac: str, keywords: str = "") -> None:
         super().__init__(name.strip(), mac.strip(), keywords)
-        self.info = None
 
     @abstractmethod
     def turn_on(self) -> bool:
 
     @abstractmethod
     def turn_on(self) -> bool:
@@ -105,27 +103,29 @@ class TPLinkOutlet(BaseOutlet):
         )
         return cmd
 
         )
         return cmd
 
-    def command(self, cmd: str, extra_args: str = None) -> bool:
+    def command(self, cmd: str, extra_args: str = None, **kwargs) -> bool:
         cmd = self.get_cmdline() + f"-c {cmd}"
         if extra_args is not None:
             cmd += f" {extra_args}"
         return tplink_outlet_command(cmd)
 
         cmd = self.get_cmdline() + f"-c {cmd}"
         if extra_args is not None:
             cmd += f" {extra_args}"
         return tplink_outlet_command(cmd)
 
+    @overrides
     def turn_on(self) -> bool:
         return self.command('on')
 
     def turn_on(self) -> bool:
         return self.command('on')
 
+    @overrides
     def turn_off(self) -> bool:
         return self.command('off')
 
     def turn_off(self) -> bool:
         return self.command('off')
 
+    @overrides
     def is_on(self) -> bool:
         return self.get_on_duration_seconds() > 0
 
     def is_on(self) -> bool:
         return self.get_on_duration_seconds() > 0
 
+    @overrides
     def is_off(self) -> bool:
         return not self.is_on()
 
     def is_off(self) -> bool:
         return not self.is_on()
 
-    @timeout(
-        10.0, use_signals=False, error_message="Timed out waiting for tplink.py"
-    )
+    @timeout(10.0, use_signals=False, error_message="Timed out waiting for tplink.py")
     def get_info(self) -> Optional[Dict]:
         cmd = self.get_cmdline() + "-c info"
         out = subprocess.getoutput(cmd)
     def get_info(self) -> Optional[Dict]:
         cmd = self.get_cmdline() + "-c info"
         out = subprocess.getoutput(cmd)
@@ -161,7 +161,7 @@ class TPLinkOutletWithChildren(TPLinkOutlet):
             for child in self.info["children"]:
                 self.children.append(child["id"])
 
             for child in self.info["children"]:
                 self.children.append(child["id"])
 
-    # override
+    @overrides
     def get_cmdline(self, child: Optional[str] = None) -> str:
         cmd = (
             f"{config.config['smart_outlets_tplink_location']} -m {self.mac} "
     def get_cmdline(self, child: Optional[str] = None) -> str:
         cmd = (
             f"{config.config['smart_outlets_tplink_location']} -m {self.mac} "
@@ -171,10 +171,9 @@ class TPLinkOutletWithChildren(TPLinkOutlet):
             cmd += f"-x {child} "
         return cmd
 
             cmd += f"-x {child} "
         return cmd
 
-    # override
-    def command(
-        self, cmd: str, child: str = None, extra_args: str = None
-    ) -> bool:
+    @overrides
+    def command(self, cmd: str, extra_args: str = None, **kwargs) -> bool:
+        child: Optional[str] = kwargs.get('child', None)
         cmd = self.get_cmdline(child) + f"-c {cmd}"
         if extra_args is not None:
             cmd += f" {extra_args}"
         cmd = self.get_cmdline(child) + f"-c {cmd}"
         if extra_args is not None:
             cmd += f" {extra_args}"
@@ -184,9 +183,11 @@ class TPLinkOutletWithChildren(TPLinkOutlet):
     def get_children(self) -> List[str]:
         return self.children
 
     def get_children(self) -> List[str]:
         return self.children
 
+    @overrides
     def turn_on(self, child: str = None) -> bool:
         return self.command("on", child)
 
     def turn_on(self, child: str = None) -> bool:
         return self.command("on", child)
 
+    @overrides
     def turn_off(self, child: str = None) -> bool:
         return self.command("off", child)
 
     def turn_off(self, child: str = None) -> bool:
         return self.command("off", child)
 
@@ -218,22 +219,28 @@ class GoogleOutlet(BaseOutlet):
     def parse_google_response(response: GoogleResponse) -> bool:
         return response.success
 
     def parse_google_response(response: GoogleResponse) -> bool:
         return response.success
 
+    @overrides
     def turn_on(self) -> bool:
         return GoogleOutlet.parse_google_response(
     def turn_on(self) -> bool:
         return GoogleOutlet.parse_google_response(
-            ask_google('turn {self.goog_name()} on')
+            ask_google(f'turn {self.goog_name()} on')
         )
 
         )
 
+    @overrides
     def turn_off(self) -> bool:
         return GoogleOutlet.parse_google_response(
     def turn_off(self) -> bool:
         return GoogleOutlet.parse_google_response(
-            ask_google('turn {self.goog_name()} off')
+            ask_google(f'turn {self.goog_name()} off')
         )
 
         )
 
+    @overrides
     def is_on(self) -> bool:
         r = ask_google(f'is {self.goog_name()} on?')
         if not r.success:
             return False
     def is_on(self) -> bool:
         r = ask_google(f'is {self.goog_name()} on?')
         if not r.success:
             return False
-        return 'is on' in r.audio_transcription
+        if r.audio_transcription is not None:
+            return 'is on' in r.audio_transcription
+        raise Exception('Can\'t talk to Google right now!?')
 
 
+    @overrides
     def is_off(self) -> bool:
         return not self.is_on()
 
     def is_off(self) -> bool:
         return not self.is_on()
 
@@ -247,10 +254,13 @@ class MerossWrapper(object):
     this class.
 
     """
     this class.
 
     """
+
     def __init__(self):
         self.loop = asyncio.get_event_loop()
         self.email = os.environ.get('MEROSS_EMAIL') or scott_secrets.MEROSS_EMAIL
     def __init__(self):
         self.loop = asyncio.get_event_loop()
         self.email = os.environ.get('MEROSS_EMAIL') or scott_secrets.MEROSS_EMAIL
-        self.password = os.environ.get('MEROSS_PASSWORD') or scott_secrets.MEROSS_PASSWORD
+        self.password = (
+            os.environ.get('MEROSS_PASSWORD') or scott_secrets.MEROSS_PASSWORD
+        )
         self.devices = self.loop.run_until_complete(self.find_meross_devices())
         atexit.register(self.loop.close)
 
         self.devices = self.loop.run_until_complete(self.find_meross_devices())
         atexit.register(self.loop.close)
 
@@ -282,8 +292,8 @@ class MerossWrapper(object):
 class MerossOutlet(BaseOutlet):
     def __init__(self, name: str, mac: str, keywords: str = '') -> None:
         super().__init__(name, mac, keywords)
 class MerossOutlet(BaseOutlet):
     def __init__(self, name: str, mac: str, keywords: str = '') -> None:
         super().__init__(name, mac, keywords)
-        self.meross_wrapper = None
-        self.device = None
+        self.meross_wrapper: Optional[MerossWrapper] = None
+        self.device: Optional[Any] = None
 
     def lazy_initialize_device(self):
         """If we make too many calls to Meross they will block us; only talk
 
     def lazy_initialize_device(self):
         """If we make too many calls to Meross they will block us; only talk
@@ -294,23 +304,28 @@ class MerossOutlet(BaseOutlet):
             if self.device is None:
                 raise Exception(f'{self.name} is not a known Meross device?!')
 
             if self.device is None:
                 raise Exception(f'{self.name} is not a known Meross device?!')
 
+    @overrides
     def turn_on(self) -> bool:
         self.lazy_initialize_device()
     def turn_on(self) -> bool:
         self.lazy_initialize_device()
-        self.meross_wrapper.loop.run_until_complete(
-            self.device.async_turn_on()
-        )
+        assert self.meross_wrapper
+        assert self.device
+        self.meross_wrapper.loop.run_until_complete(self.device.async_turn_on())
         return True
 
         return True
 
+    @overrides
     def turn_off(self) -> bool:
         self.lazy_initialize_device()
     def turn_off(self) -> bool:
         self.lazy_initialize_device()
-        self.meross_wrapper.loop.run_until_complete(
-            self.device.async_turn_off()
-        )
+        assert self.meross_wrapper
+        assert self.device
+        self.meross_wrapper.loop.run_until_complete(self.device.async_turn_off())
         return True
 
         return True
 
+    @overrides
     def is_on(self) -> bool:
         self.lazy_initialize_device()
     def is_on(self) -> bool:
         self.lazy_initialize_device()
+        assert self.device
         return self.device.is_on()
 
         return self.device.is_on()
 
+    @overrides
     def is_off(self) -> bool:
         return not self.is_on()
     def is_off(self) -> bool:
         return not self.is_on()
index 7349081f1b9b1637e2c18db7465b29e82626f54e..16e18ba11bcc5fa19443245f02df546c68d54787 100644 (file)
@@ -8,15 +8,15 @@ import argparse_utils
 import config
 import file_utils
 import logical_search
 import config
 import file_utils
 import logical_search
-import smart_home.device as device
 import smart_home.cameras as cameras
 import smart_home.chromecasts as chromecasts
 import smart_home.cameras as cameras
 import smart_home.chromecasts as chromecasts
+import smart_home.device as device
 import smart_home.lights as lights
 import smart_home.outlets as outlets
 
 args = config.add_commandline_args(
     f"Smart Home Registry ({__file__})",
 import smart_home.lights as lights
 import smart_home.outlets as outlets
 
 args = config.add_commandline_args(
     f"Smart Home Registry ({__file__})",
-    "Args related to the smart home configuration registry."
+    "Args related to the smart home configuration registry.",
 )
 args.add_argument(
     '--smart_home_registry_file_location',
 )
 args.add_argument(
     '--smart_home_registry_file_location',
@@ -32,9 +32,9 @@ logger = logging.getLogger(__file__)
 
 class SmartHomeRegistry(object):
     def __init__(
 
 class SmartHomeRegistry(object):
     def __init__(
-            self,
-            registry_file: Optional[str] = None,
-            filters: List[str] = ['smart'],
+        self,
+        registry_file: Optional[str] = None,
+        filters: List[str] = ['smart'],
     ) -> None:
         self._macs_by_name = {}
         self._keywords_by_name = {}
     ) -> None:
         self._macs_by_name = {}
         self._keywords_by_name = {}
@@ -44,13 +44,11 @@ class SmartHomeRegistry(object):
 
         # Read the disk config file...
         if registry_file is None:
 
         # Read the disk config file...
         if registry_file is None:
-            registry_file = config.config[
-                'smart_home_registry_file_location'
-            ]
+            registry_file = config.config['smart_home_registry_file_location']
         assert file_utils.does_file_exist(registry_file)
         logger.debug(f'Reading {registry_file}')
         assert file_utils.does_file_exist(registry_file)
         logger.debug(f'Reading {registry_file}')
-        with open(registry_file, "r") as f:
-            contents = f.readlines()
+        with open(registry_file, "r") as rf:
+            contents = rf.readlines()
 
         # Parse the contents...
         for line in contents:
 
         # Parse the contents...
         for line in contents:
index 4e5b8a6aa6b4b8ef61671b93b0284825cdf75cbc..13f14b7f835d2e15679ea3bddd9499398056448e 100644 (file)
@@ -1,27 +1,18 @@
 #!/usr/bin/env python3
 
 import re
 #!/usr/bin/env python3
 
 import re
-from typing import Optional, TypeVar, Tuple
+from typing import Optional, Tuple, TypeVar
 
 import math_utils
 
 
 
 import math_utils
 
 
-T = TypeVar('T', bound='CentCount')
-
-
 class CentCount(object):
     """A class for representing monetary amounts potentially with
     different currencies meant to avoid floating point rounding
     issues by treating amount as a simple integral count of cents.
     """
 
 class CentCount(object):
     """A class for representing monetary amounts potentially with
     different currencies meant to avoid floating point rounding
     issues by treating amount as a simple integral count of cents.
     """
 
-    def __init__ (
-            self,
-            centcount,
-            currency: str = 'USD',
-            *,
-            strict_mode = False
-    ):
+    def __init__(self, centcount, currency: str = 'USD', *, strict_mode=False):
         self.strict_mode = strict_mode
         if isinstance(centcount, str):
             ret = CentCount._parse(centcount)
         self.strict_mode = strict_mode
         if isinstance(centcount, str):
             ret = CentCount._parse(centcount)
@@ -37,7 +28,7 @@ class CentCount(object):
         if not currency:
             self.currency: Optional[str] = None
         else:
         if not currency:
             self.currency: Optional[str] = None
         else:
-            self.currency: Optional[str] = currency
+            self.currency = currency
 
     def __repr__(self):
         a = float(self.centcount)
 
     def __repr__(self):
         a = float(self.centcount)
@@ -59,8 +50,7 @@ class CentCount(object):
         if isinstance(other, CentCount):
             if self.currency == other.currency:
                 return CentCount(
         if isinstance(other, CentCount):
             if self.currency == other.currency:
                 return CentCount(
-                    centcount = self.centcount + other.centcount,
-                    currency = self.currency
+                    centcount=self.centcount + other.centcount, currency=self.currency
                 )
             else:
                 raise TypeError('Incompatible currencies in add expression')
                 )
             else:
                 raise TypeError('Incompatible currencies in add expression')
@@ -74,8 +64,7 @@ class CentCount(object):
         if isinstance(other, CentCount):
             if self.currency == other.currency:
                 return CentCount(
         if isinstance(other, CentCount):
             if self.currency == other.currency:
                 return CentCount(
-                    centcount = self.centcount - other.centcount,
-                    currency = self.currency
+                    centcount=self.centcount - other.centcount, currency=self.currency
                 )
             else:
                 raise TypeError('Incompatible currencies in add expression')
                 )
             else:
                 raise TypeError('Incompatible currencies in add expression')
@@ -90,8 +79,7 @@ class CentCount(object):
             raise TypeError('can not multiply monetary quantities')
         else:
             return CentCount(
             raise TypeError('can not multiply monetary quantities')
         else:
             return CentCount(
-                centcount = int(self.centcount * float(other)),
-                currency = self.currency
+                centcount=int(self.centcount * float(other)), currency=self.currency
             )
 
     def __truediv__(self, other):
             )
 
     def __truediv__(self, other):
@@ -99,8 +87,8 @@ class CentCount(object):
             raise TypeError('can not divide monetary quantities')
         else:
             return CentCount(
             raise TypeError('can not divide monetary quantities')
         else:
             return CentCount(
-                centcount = int(float(self.centcount) / float(other)),
-                currency = self.currency
+                centcount=int(float(self.centcount) / float(other)),
+                currency=self.currency,
             )
 
     def __int__(self):
             )
 
     def __int__(self):
@@ -125,8 +113,7 @@ class CentCount(object):
         if isinstance(other, CentCount):
             if self.currency == other.currency:
                 return CentCount(
         if isinstance(other, CentCount):
             if self.currency == other.currency:
                 return CentCount(
-                    centcount = other.centcount - self.centcount,
-                    currency = self.currency
+                    centcount=other.centcount - self.centcount, currency=self.currency
                 )
             else:
                 raise TypeError('Incompatible currencies in sub expression')
                 )
             else:
                 raise TypeError('Incompatible currencies in sub expression')
@@ -135,8 +122,7 @@ class CentCount(object):
                 raise TypeError('In strict_mode only two moneys can be added')
             else:
                 return CentCount(
                 raise TypeError('In strict_mode only two moneys can be added')
             else:
                 return CentCount(
-                    centcount = int(other) - self.centcount,
-                    currency = self.currency
+                    centcount=int(other) - self.centcount, currency=self.currency
                 )
 
     __rmul__ = __mul__
                 )
 
     __rmul__ = __mul__
@@ -148,10 +134,7 @@ class CentCount(object):
         if other is None:
             return False
         if isinstance(other, CentCount):
         if other is None:
             return False
         if isinstance(other, CentCount):
-            return (
-                self.centcount == other.centcount and
-                self.currency == other.currency
-            )
+            return self.centcount == other.centcount and self.currency == other.currency
         if self.strict_mode:
             raise TypeError("In strict mode only two CentCounts can be compared")
         else:
         if self.strict_mode:
             raise TypeError("In strict mode only two CentCounts can be compared")
         else:
@@ -196,8 +179,8 @@ class CentCount(object):
     def __hash__(self):
         return self.__repr__
 
     def __hash__(self):
         return self.__repr__
 
-    CENTCOUNT_RE = re.compile("^([+|-]?)(\d+)(\.\d+)$")
-    CURRENCY_RE = re.compile("^[A-Z][A-Z][A-Z]$")
+    CENTCOUNT_RE = re.compile(r"^([+|-]?)(\d+)(\.\d+)$")
+    CURRENCY_RE = re.compile(r"^[A-Z][A-Z][A-Z]$")
 
     @classmethod
     def _parse(cls, s: str) -> Optional[Tuple[int, str]]:
 
     @classmethod
     def _parse(cls, s: str) -> Optional[Tuple[int, str]]:
@@ -220,7 +203,7 @@ class CentCount(object):
         return None
 
     @classmethod
         return None
 
     @classmethod
-    def parse(cls, s: str) -> T:
+    def parse(cls, s: str) -> 'CentCount':
         chunks = CentCount._parse(s)
         if chunks is not None:
             return CentCount(chunks[0], chunks[1])
         chunks = CentCount._parse(s)
         if chunks is not None:
             return CentCount(chunks[0], chunks[1])
index 290c2c86f91a7a6da652b21e670d2579c51ba6c0..d7e6ffa2197c629949ecae30c855df5870cc3a3a 100644 (file)
@@ -1,26 +1,23 @@
 #!/usr/bin/env python3
 
 #!/usr/bin/env python3
 
-from decimal import Decimal
 import re
 import re
-from typing import Optional, TypeVar, Tuple
+from decimal import Decimal
+from typing import Optional, Tuple, TypeVar
 
 import math_utils
 
 
 
 import math_utils
 
 
-T = TypeVar('T', bound='Money')
-
-
 class Money(object):
     """A class for representing monetary amounts potentially with
     different currencies.
     """
 
 class Money(object):
     """A class for representing monetary amounts potentially with
     different currencies.
     """
 
-    def __init__ (
-            self,
-            amount: Decimal = Decimal("0"),
-            currency: str = 'USD',
-            *,
-            strict_mode = False
+    def __init__(
+        self,
+        amount: Decimal = Decimal("0"),
+        currency: str = 'USD',
+        *,
+        strict_mode=False,
     ):
         self.strict_mode = strict_mode
         if isinstance(amount, str):
     ):
         self.strict_mode = strict_mode
         if isinstance(amount, str):
@@ -35,7 +32,7 @@ class Money(object):
         if not currency:
             self.currency: Optional[str] = None
         else:
         if not currency:
             self.currency: Optional[str] = None
         else:
-            self.currency: Optional[str] = currency
+            self.currency = currency
 
     def __repr__(self):
         a = float(self.amount)
 
     def __repr__(self):
         a = float(self.amount)
@@ -55,10 +52,7 @@ class Money(object):
     def __add__(self, other):
         if isinstance(other, Money):
             if self.currency == other.currency:
     def __add__(self, other):
         if isinstance(other, Money):
             if self.currency == other.currency:
-                return Money(
-                    amount = self.amount + other.amount,
-                    currency = self.currency
-                )
+                return Money(amount=self.amount + other.amount, currency=self.currency)
             else:
                 raise TypeError('Incompatible currencies in add expression')
         else:
             else:
                 raise TypeError('Incompatible currencies in add expression')
         else:
@@ -66,17 +60,13 @@ class Money(object):
                 raise TypeError('In strict_mode only two moneys can be added')
             else:
                 return Money(
                 raise TypeError('In strict_mode only two moneys can be added')
             else:
                 return Money(
-                    amount = self.amount + Decimal(float(other)),
-                    currency = self.currency
+                    amount=self.amount + Decimal(float(other)), currency=self.currency
                 )
 
     def __sub__(self, other):
         if isinstance(other, Money):
             if self.currency == other.currency:
                 )
 
     def __sub__(self, other):
         if isinstance(other, Money):
             if self.currency == other.currency:
-                return Money(
-                    amount = self.amount - other.amount,
-                    currency = self.currency
-                )
+                return Money(amount=self.amount - other.amount, currency=self.currency)
             else:
                 raise TypeError('Incompatible currencies in add expression')
         else:
             else:
                 raise TypeError('Incompatible currencies in add expression')
         else:
@@ -84,8 +74,7 @@ class Money(object):
                 raise TypeError('In strict_mode only two moneys can be added')
             else:
                 return Money(
                 raise TypeError('In strict_mode only two moneys can be added')
             else:
                 return Money(
-                    amount = self.amount - Decimal(float(other)),
-                    currency = self.currency
+                    amount=self.amount - Decimal(float(other)), currency=self.currency
                 )
 
     def __mul__(self, other):
                 )
 
     def __mul__(self, other):
@@ -93,8 +82,7 @@ class Money(object):
             raise TypeError('can not multiply monetary quantities')
         else:
             return Money(
             raise TypeError('can not multiply monetary quantities')
         else:
             return Money(
-                amount = self.amount * Decimal(float(other)),
-                currency = self.currency
+                amount=self.amount * Decimal(float(other)), currency=self.currency
             )
 
     def __truediv__(self, other):
             )
 
     def __truediv__(self, other):
@@ -102,8 +90,7 @@ class Money(object):
             raise TypeError('can not divide monetary quantities')
         else:
             return Money(
             raise TypeError('can not divide monetary quantities')
         else:
             return Money(
-                amount = self.amount / Decimal(float(other)),
-                currency = self.currency
+                amount=self.amount / Decimal(float(other)), currency=self.currency
             )
 
     def __float__(self):
             )
 
     def __float__(self):
@@ -124,10 +111,7 @@ class Money(object):
     def __rsub__(self, other):
         if isinstance(other, Money):
             if self.currency == other.currency:
     def __rsub__(self, other):
         if isinstance(other, Money):
             if self.currency == other.currency:
-                return Money(
-                    amount = other.amount - self.amount,
-                    currency = self.currency
-                )
+                return Money(amount=other.amount - self.amount, currency=self.currency)
             else:
                 raise TypeError('Incompatible currencies in sub expression')
         else:
             else:
                 raise TypeError('Incompatible currencies in sub expression')
         else:
@@ -135,8 +119,7 @@ class Money(object):
                 raise TypeError('In strict_mode only two moneys can be added')
             else:
                 return Money(
                 raise TypeError('In strict_mode only two moneys can be added')
             else:
                 return Money(
-                    amount = Decimal(float(other)) - self.amount,
-                    currency = self.currency
+                    amount=Decimal(float(other)) - self.amount, currency=self.currency
                 )
 
     __rmul__ = __mul__
                 )
 
     __rmul__ = __mul__
@@ -148,10 +131,7 @@ class Money(object):
         if other is None:
             return False
         if isinstance(other, Money):
         if other is None:
             return False
         if isinstance(other, Money):
-            return (
-                self.amount == other.amount and
-                self.currency == other.currency
-            )
+            return self.amount == other.amount and self.currency == other.currency
         if self.strict_mode:
             raise TypeError("In strict mode only two Moneys can be compared")
         else:
         if self.strict_mode:
             raise TypeError("In strict mode only two Moneys can be compared")
         else:
@@ -196,8 +176,8 @@ class Money(object):
     def __hash__(self):
         return self.__repr__
 
     def __hash__(self):
         return self.__repr__
 
-    AMOUNT_RE = re.compile("^([+|-]?)(\d+)(\.\d+)$")
-    CURRENCY_RE = re.compile("^[A-Z][A-Z][A-Z]$")
+    AMOUNT_RE = re.compile(r"^([+|-]?)(\d+)(\.\d+)$")
+    CURRENCY_RE = re.compile(r"^[A-Z][A-Z][A-Z]$")
 
     @classmethod
     def _parse(cls, s: str) -> Optional[Tuple[Decimal, str]]:
 
     @classmethod
     def _parse(cls, s: str) -> Optional[Tuple[Decimal, str]]:
@@ -220,7 +200,7 @@ class Money(object):
         return None
 
     @classmethod
         return None
 
     @classmethod
-    def parse(cls, s: str) -> T:
+    def parse(cls, s: str) -> 'Money':
         chunks = Money._parse(s)
         if chunks is not None:
             return Money(chunks[0], chunks[1])
         chunks = Money._parse(s)
         if chunks is not None:
             return Money(chunks[0], chunks[1])
index 3161131a027171218f297dcc654e28acd851ca27..64a472650242f863e24a02cd03cb44344db10a76 100644 (file)
@@ -5,11 +5,11 @@ from typing import Optional
 
 class Rate(object):
     def __init__(
 
 class Rate(object):
     def __init__(
-            self,
-            multiplier: Optional[float] = None,
-            *,
-            percentage: Optional[float] = None,
-            percent_change: Optional[float] = None,
+        self,
+        multiplier: Optional[float] = None,
+        *,
+        percentage: Optional[float] = None,
+        percent_change: Optional[float] = None,
     ):
         count = 0
         if multiplier is not None:
     ):
         count = 0
         if multiplier is not None:
@@ -17,7 +17,7 @@ class Rate(object):
                 multiplier = multiplier.replace('%', '')
                 m = float(multiplier)
                 m /= 100
                 multiplier = multiplier.replace('%', '')
                 m = float(multiplier)
                 m /= 100
-                self.multiplier = m
+                self.multiplier: float = m
             else:
                 self.multiplier = multiplier
             count += 1
             else:
                 self.multiplier = multiplier
             count += 1
@@ -78,10 +78,7 @@ class Rate(object):
     def __hash__(self):
         return self.multiplier
 
     def __hash__(self):
         return self.multiplier
 
-    def __repr__(self,
-                 *,
-                 relative=False,
-                 places=3):
+    def __repr__(self, *, relative=False, places=3):
         if relative:
             percentage = (self.multiplier - 1.0) * 100.0
         else:
         if relative:
             percentage = (self.multiplier - 1.0) * 100.0
         else: