Ugh. Fix a bug in the new refactor of the env var config stuff.
[python_utils.git] / cached / weather_data.py
index 7b86d029c607074d0fb88949ff9e50641d169401..91d665dbfd2e068ac2a10fc1ff867d552db3e71b 100644 (file)
@@ -1,4 +1,13 @@
 #!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# © Copyright 2021-2022, Scott Gasch
+
+"""A cache of weather data for Bellevue, WA.
+:class:`CachedWeatherData` class that derives from :class:`Persistent`
+so that, on creation, the decorator transparently pulls in data from
+disk, if possible, to avoid a network request.
+"""
 
 import datetime
 import json
@@ -6,7 +15,7 @@ import logging
 import os
 import urllib.request
 from dataclasses import dataclass
-from typing import Any, List
+from typing import Any, Dict, List, Optional
 
 from overrides import overrides
 
@@ -15,6 +24,8 @@ import config
 import datetime_utils
 import list_utils
 import persistent
+import scott_secrets
+import type_utils
 
 logger = logging.getLogger(__name__)
 
@@ -40,18 +51,38 @@ cfg.add_argument(
 
 @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 of the forecast"""
+
+    high: float
+    """The predicted high temperature in F"""
+
+    low: float
+    """The predicted low temperature 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 of the day"""
+
+    icon: str
+    """An icon representing the most common condition of the day"""
 
 
 @persistent.persistent_autoloaded_singleton()  # type: ignore
 class CachedWeatherData(persistent.Persistent):
-    def __init__(self, weather_data=None):
+    def __init__(self, weather_data: Dict[datetime.date, WeatherData] = None):
+        """C'tor.  Do not pass a dict except for testing purposes.
+
+        The @persistent_autoloaded_singleton decorator handles
+        invoking our load and save methods at construction time for
+        you.
+        """
+
         if weather_data is not None:
             self.weather_data = weather_data
             return
@@ -75,12 +106,12 @@ class CachedWeatherData(persistent.Persistent):
         }
         now = datetime.datetime.now()
         dates = set()
-        highs = {}
-        lows = {}
-        conditions = {}
-        precip = {}
+        highs: Dict[datetime.date, Optional[float]] = {}
+        lows: Dict[datetime.date, Optional[float]] = {}
+        conditions: Dict[datetime.date, List[str]] = {}
+        precip: Dict[datetime.date, float] = {}
         param = "id=5786882"  # Bellevue, WA
-        key = "c0b160c49743622f62a9cd3cda0270b3"
+        key = scott_secrets.OPEN_WEATHER_MAP_KEY
         www = urllib.request.urlopen(
             f'http://api.openweathermap.org/data/2.5/weather?zip=98005,us&APPID={key}&units=imperial'
         )
@@ -168,8 +199,8 @@ class CachedWeatherData(persistent.Persistent):
                 icon = '🌙'
             self.weather_data[dt] = WeatherData(
                 date=dt,
-                high=highs[dt],
-                low=lows[dt],
+                high=type_utils.unwrap_optional(highs[dt]),
+                low=type_utils.unwrap_optional(lows[dt]),
                 precipitation_inches=precip[dt] / 25.4,
                 conditions=conditions[dt],
                 most_common_condition=most_common_condition,
@@ -179,6 +210,15 @@ class CachedWeatherData(persistent.Persistent):
     @classmethod
     @overrides
     def load(cls) -> Any:
+
+        """Depending on whether we have fresh data persisted either uses that
+        data to instantiate the class or makes an HTTP request to fetch the
+        necessary data.
+
+        Note that because this is a subclass of Persistent this is taken
+        care of automatically.
+        """
+
         if persistent.was_file_written_within_n_seconds(
             config.config['weather_data_cachefile'],
             config.config['weather_data_stalest_acceptable'].total_seconds(),
@@ -192,6 +232,11 @@ class CachedWeatherData(persistent.Persistent):
 
     @overrides
     def save(self) -> bool:
+        """
+        Saves the current data to disk if required.  Again, because this is
+        a subclass of Persistent this is taken care of for you.
+        """
+
         import pickle
 
         with open(config.config['weather_data_cachefile'], 'wb') as wf: