diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | fscache.py | 32 | ||||
-rwxr-xr-x | meteor.py | 73 |
3 files changed, 73 insertions, 33 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/fscache.py b/fscache.py new file mode 100644 index 0000000..d52ff62 --- /dev/null +++ b/fscache.py @@ -0,0 +1,32 @@ +import os +import time +from pathlib import Path + +class CacheMiss(Exception): + pass + +class Cache: + def __init__(self, name, ttl=3600): + self.ttl = ttl + try: + self.cache_dir = Path(os.environ['XDG_CACHE_HOME'])/name + except KeyError: + self.cache_dir = Path(os.environ['HOME'])/'.cache'/name + self.cache_dir.mkdir(exist_ok=True) + + def write(self, key, value): + # TODO gracefully handle keys with '/' by creating directories or rewriting + loc = self.cache_dir/key + loc.write_text(value, errors='ignore') + + def read(self, key): + loc = self.cache_dir/key + + if not loc.exists(): + raise CacheMiss + + age = time.time() - loc.stat().st_mtime + if age > self.ttl: + raise CacheMiss + + return loc.read_text() diff --git a/meteor.py b/meteor.py index f19a834..fb3179c 100755 --- a/meteor.py +++ b/meteor.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from argparse import ArgumentParser import requests +import fscache as fs # doco is at https://www.weather.gov/documentation/services-web-api # openAPI spec is at https://api.weather.gov/openapi.json @@ -53,35 +54,49 @@ def print_alerts(county, zone): # can construct the headline ourseves as f'{event} issued {sent} [until {end}] by {senderName}' print() -def print_forecast(forecast_url): +def print_forecast(point): # TODO pick up gridId AFC (AER/ALU), AFG, AJK, BOX< CAE, DLH, FSD, HGX, HNX, LIX, LWX, MAF, MFR, MLB, MRX, MTR, PIH and reduce gridX and gridY by 1 - known issue - forecast = requests.get(forecast_url, headers=headers).json() - # TODO retry on 500 - known issue - # TODO query param units=si - # TODO cache for like four hours per location, forecast doesn't change that frequently - - # TODO replace the whole thing with /gridpoints/{point.gridId}/{gridX},{gridY} and create our own forecast - # there is lots more information there that would be useful - # probabilityOfPrecipitation is the neatest one that's not in .../forecast or .../forecast/hourly - # but there's also relativeHumidity, apparentTemperature (and, separately, windChill), skyCover, windGust - # downside, it appears to always convert the F/mph measurements into C/kph for no reason. e.g. all temps are measured in 9ths of C - - print(f'{term.BOLD}forecast') - print(f'===================={term.RESET}') - periods = forecast.get('periods') - # print today and tonight in long format - for period in periods[:2]: - print(f'{term.BOLD}{period.get("name")}{term.RESET}:') - print(f' {period.get("temperature")}°{period.get("temperatureUnit")}, wind {period.get("windSpeed")} {period.get("windDirection")}') - print(f' {period.get("detailedForecast")}') - # print further forecasted periods in short format - for period in periods[2:]: - # TODO parse isDaytime true/false to squish a day together - print(f'{term.BOLD}{period.get("name").ljust(15)}{term.RESET}: {period.get("shortForecast")} - {period.get("temperature")}°{period.get("temperatureUnit")}, {period.get("windSpeed")} {period.get("windDirection")}') + cache_key = f'{point.get("gridId")}-{point.get("gridX")}-{point.get("gridY")}' + + try: + print(cache.read(cache_key)) + except fs.CacheMiss: + forecast_url = point.get('forecast') + forecast = requests.get(forecast_url, headers=headers).json() + # TODO retry on 500 - known issue + # TODO query param units=si + + # TODO replace the whole thing with /gridpoints/{point.gridId}/{gridX},{gridY} and create our own forecast + # there is lots more information there that would be useful + # probabilityOfPrecipitation is the neatest one that's not in .../forecast or .../forecast/hourly + # but there's also relativeHumidity, apparentTemperature (and, separately, windChill), skyCover, windGust + # downside, it appears to always convert the F/mph measurements into C/kph for no reason. e.g. all temps are measured in 9ths of C + + # TODO pick out location name from somewhere. + # city and state hide in point.relativeLocation.properties.city (and state) + # getting the county or zone's `name (id) state` is good too + + buffer = f'{term.BOLD}forecast' + buffer += '\n' + f'===================={term.RESET}' + periods = forecast.get('periods') + # print today and tonight in long format + for period in periods[:2]: + buffer += '\n' + f'{term.BOLD}{period.get("name")}{term.RESET}:' + buffer += '\n' + f' {period.get("temperature")}°{period.get("temperatureUnit")}, wind {period.get("windSpeed")} {period.get("windDirection")}' + buffer += '\n' + f' {period.get("detailedForecast")}' + # print further forecasted periods in short format + for period in periods[2:]: + # TODO parse isDaytime true/false to squish a day together + buffer += '\n' + f'{term.BOLD}{period.get("name").ljust(15)}{term.RESET}: {period.get("shortForecast")} - {period.get("temperature")}°{period.get("temperatureUnit")}, {period.get("windSpeed")} {period.get("windDirection")}' + + cache.write(cache_key, buffer) + print(buffer) def main(): args = handle_args() + global cache + cache = fs.Cache('meteor.py') point = requests.get(base_url + f'/points/{args.lat},{args.lon}', headers=headers).json() @@ -90,17 +105,9 @@ def main(): zone = point.get('forecastZone').split('/')[-1] print_alerts(county, zone) - # TODO pick out location name from somewhere. - # city and state hide in point.relativeLocation.properties.city (and state) - # getting the county or zone's `name (id) state` is good too - # TODO can hazardous weather outlook be gotten programatically? - - forecast_url = point.get('forecast') - print_forecast(forecast_url) - - # hourly_forecast_url = point.get('forecastHourly') + print_forecast(point) if __name__ == '__main__': import sys |