# © Copyright 2021-2022, Scott Gasch
-"""How's the weather?"""
+"""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
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
import datetime_utils
import list_utils
import persistent
+import scott_secrets
+import type_utils
logger = logging.getLogger(__name__)
@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
}
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'
)
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,
@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(),
@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: