X-Git-Url: https://wannabe.guru.org/gitweb/?a=blobdiff_plain;f=cached%2Fweather_forecast.py;h=b8a20ed8caa04553f01225a5a2f8f57866858ea7;hb=532df2c5b57c7517dfb3dddd8c1358fbadf8baf3;hp=ce4725d64a6c43699ab702b85d60d1f070366cf5;hpb=d2730e42f1160d45ab6c7780987b16ae83c616f6;p=python_utils.git diff --git a/cached/weather_forecast.py b/cached/weather_forecast.py index ce4725d..b8a20ed 100644 --- a/cached/weather_forecast.py +++ b/cached/weather_forecast.py @@ -1,55 +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 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 -import pytz +from overrides import overrides import argparse_utils import config -import datetime_utils import dateparse.dateparse_utils as dp +import datetime_utils import persistent +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='/home/scott/.weather_forecast_cache', + 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 -@persistent.persistent_autoloaded_singleton() -class CachedDetailedWeatherForecast(object): - def __init__(self, forecasts = None): +@persistent.persistent_autoloaded_singleton() # type: ignore +class CachedDetailedWeatherForecast(persistent.Persistent): + def __init__(self, forecasts=None): if forecasts is not None: self.forecasts = forecasts return @@ -58,15 +66,9 @@ class CachedDetailedWeatherForecast(object): self.forecasts = {} # Ask the raspberry pi about the outside temperature. - www = urllib.request.urlopen( - "http://10.0.0.75/~pi/outside_temp" + current_temp = temps.ThermometerRegistry().read_temperature( + 'house_outside', convert_to_fahrenheit=True ) - current_temp = www.read().decode("utf-8") - current_temp = float(current_temp) - current_temp *= (9/5) - current_temp += 32.0 - current_temp = round(current_temp) - www.close() # Get a weather forecast for Bellevue. www = urllib.request.urlopen( @@ -79,11 +81,12 @@ class CachedDetailedWeatherForecast(object): forecast = soup.find(id='detailed-forecast-body') parser = dp.DateParser() + said_temp = False 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,15 +96,15 @@ class CachedDetailedWeatherForecast(object): 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'] - if dt.date == now.date: - blurb = f'{day.get_text()}: The current outside tempterature is {current_temp}. ' + txt.get_text() + if dt.date() == now.date() and not said_temp and current_temp is not None: + blurb = f'{day.get_text()}: The current outside tempterature is {current_temp}. ' + blurb += txt.get_text() + said_temp = True else: blurb = f'{day.get_text()}: {txt.get_text()}' blurb = text_utils.wrap_string(blurb, 80) @@ -110,29 +113,34 @@ class CachedDetailedWeatherForecast(object): 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 - def load(cls): + @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) return None - def save(self): + @overrides + def save(self) -> bool: import pickle + with open(config.config['weather_forecast_cachefile'], 'wb') as wf: pickle.dump( self.forecasts, wf, pickle.HIGHEST_PROTOCOL, ) + return True