Since this thing is on the innerwebs I suppose it should have a
[python_utils.git] / cached / weather_data.py
index 94a01313375f5a6e6ecc5b485c873e9280c54db5..29a1d544b042101042eeb9d0a1eb781de094e427 100644 (file)
@@ -1,12 +1,19 @@
 #!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# © Copyright 2021-2022, Scott Gasch
+
+"""How's the weather?"""
 
-from dataclasses import dataclass
 import datetime
 import json
 import logging
 import os
-from typing import List
 import urllib.request
+from dataclasses import dataclass
+from typing import Any, List
+
+from overrides import overrides
 
 import argparse_utils
 import config
@@ -23,34 +30,33 @@ cfg = config.add_commandline_args(
 cfg.add_argument(
     '--weather_data_cachefile',
     type=str,
-    default=f'{os.environ["HOME"]}/.weather_summary_cache',
+    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,
-    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 WeatherData:
-    date: datetime.date              # The date
-    high: float                      # The predicted high in F
-    low: float                       # The predicted low in F
-    precipitation_inchs: 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):
-    def __init__(self,
-                 weather_data = None):
+    def __init__(self, weather_data=None):
         if weather_data is not None:
             self.weather_data = weather_data
             return
@@ -70,7 +76,7 @@ class CachedWeatherData(persistent.Persistent):
             "Sand": "🏜️",
             "Ash": "🌋",
             "Squall": "🌬",
-            "Tornado": "🌪️"
+            "Tornado": "🌪️",
         }
         now = datetime.datetime.now()
         dates = set()
@@ -78,7 +84,7 @@ class CachedWeatherData(persistent.Persistent):
         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'
@@ -105,13 +111,13 @@ class CachedWeatherData(persistent.Persistent):
         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_inchs = 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(
@@ -132,9 +138,9 @@ class CachedWeatherData(persistent.Persistent):
                 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
@@ -158,43 +164,45 @@ class CachedWeatherData(persistent.Persistent):
         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_item(conditions[dt])
+            most_common_condition = list_utils.most_common(conditions[dt])
             icon = icon_by_condition.get(most_common_condition, '?')
             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_inchs = 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
-    def load(cls):
+    @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
+
             with open(config.config['weather_data_cachefile'], 'rb') as rf:
                 weather_data = pickle.load(rf)
                 return cls(weather_data)
         return None
 
-    def save(self):
+    @overrides
+    def save(self) -> bool:
         import pickle
+
         with open(config.config['weather_data_cachefile'], 'wb') as wf:
             pickle.dump(
                 self.weather_data,
                 wf,
                 pickle.HIGHEST_PROTOCOL,
             )
+        return True