Since this thing is on the innerwebs I suppose it should have a
[python_utils.git] / cached / weather_forecast.py
index b34393832dec04120548aa08fb6822366cfc6ff6..b8a20ed8caa04553f01225a5a2f8f57866858ea7 100644 (file)
@@ -1,60 +1,63 @@
 #!/usr/bin/env python3
 
-from dataclasses import dataclass
+# © Copyright 2021-2022, Scott Gasch
+
+"""How's the weather going to be tomorrow?"""
+
 import datetime
 import logging
 import os
-from typing import Any
 import urllib.request
+from dataclasses import dataclass
+from typing import Any
 
 import astral  # type: ignore
+import pytz
 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 datetime_utils
 import dateparse.dateparse_utils as dp
+import datetime_utils
 import persistent
-import text_utils
 import smart_home.thermometers as temps
-
+import text_utils
 
 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',
-    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,
-    default=datetime.timedelta(seconds=7200),   # 2 hours
+    default=datetime.timedelta(seconds=7200),  # 2 hours
     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:
-    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):
-    def __init__(self, forecasts = None):
+    def __init__(self, forecasts=None):
         if forecasts is not None:
             self.forecasts = forecasts
             return
@@ -82,8 +85,8 @@ class CachedDetailedWeatherForecast(persistent.Persistent):
         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:
@@ -93,9 +96,7 @@ class CachedDetailedWeatherForecast(persistent.Persistent):
             assert dt is not None
 
             # Compute sunrise/sunset times on dt.
-            city = astral.LocationInfo(
-                "Bellevue", "USA", "US/Pacific", 47.653, -122.171
-            )
+            city = astral.LocationInfo("Bellevue", "USA", "US/Pacific", 47.653, -122.171)
             s = sun(city.observer, date=dt, tzinfo=pytz.timezone("US/Pacific"))
             sunrise = s['sunrise']
             sunset = s['sunset']
@@ -112,20 +113,21 @@ class CachedDetailedWeatherForecast(persistent.Persistent):
                 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(
-                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
+
             with open(config.config['weather_forecast_cachefile'], 'rb') as rf:
                 weather_data = pickle.load(rf)
                 return cls(weather_data)
@@ -134,6 +136,7 @@ class CachedDetailedWeatherForecast(persistent.Persistent):
     @overrides
     def save(self) -> bool:
         import pickle
+
         with open(config.config['weather_forecast_cachefile'], 'wb') as wf:
             pickle.dump(
                 self.forecasts,