diff --git a/README.rst b/README.rst index 571dee4..3fcf7b5 100644 --- a/README.rst +++ b/README.rst @@ -13,9 +13,10 @@ utility for fetching the forecast. Requires ======== -* Python 2.6+ -* argparse (if Python < 2.7) +* Python 3.5+ +* argparse * dateutils +* configparser Command-Line Usage diff --git a/bin/noaa b/bin/noaa index d381688..46b8dcd 100755 --- a/bin/noaa +++ b/bin/noaa @@ -25,7 +25,7 @@ Database (NDFD). """ import argparse -import ConfigParser +import configparser as configparser # update to match package name change import os import sys @@ -101,23 +101,23 @@ def format_conditions(conditions, padding=30, color=True): def config_get_boolean(config, section, option, default=None): try: value = config.getboolean(section, option) - except ConfigParser.NoSectionError: + except configparser.NoSectionError: value = default - except ConfigParser.NoOptionError: + except configparser.NoOptionError: value = default return value def make_parser(): - config = ConfigParser.ConfigParser() + config = configparser.ConfigParser() config.read(os.path.expanduser("~/.noaarc")) try: default_location = config.get('default', 'location').split() - except ConfigParser.NoSectionError: + except configparser.NoSectionError: default_location = None - except ConfigParser.NoOptionError: + except configparser.NoOptionError: default_location = None default_metric = config_get_boolean( @@ -220,14 +220,14 @@ def main(): pretty_location, fcast = forecast_func(args) if args.heading: - print "Forecast for %s" % pretty_location - - for datapoint in fcast: - print datapoint.date.strftime('%a'), - print format_conditions(datapoint.conditions, color=args.color), - print format_temp(datapoint.min_temp, color=args.color), - print format_temp(datapoint.max_temp, color=args.color), - print simple_temp_graph(datapoint.max_temp, color=args.color) + print("Forecast for %s" % pretty_location) + + for datapoint in fcast: # some updates for py3.8 compatibility + print(datapoint.date.strftime('%a')) + print(format_conditions(datapoint.conditions, color=args.color)) + print(format_temp(datapoint.min_temp, color=args.color)) + print(format_temp(datapoint.max_temp, color=args.color)) + print(simple_temp_graph(datapoint.max_temp, color=args.color)) if __name__ == "__main__": diff --git a/noaa/__init__.py b/noaa/__init__.py index 2761705..beb7688 100644 --- a/noaa/__init__.py +++ b/noaa/__init__.py @@ -22,4 +22,4 @@ Python bindings to the NOAA National Digital Forecast Database (NDFD) """ -__version__ = "0.2.1" +__version__ = "0.2.2" diff --git a/noaa/forecast.py b/noaa/forecast.py index ad2a9ba..2d4af4d 100644 --- a/noaa/forecast.py +++ b/noaa/forecast.py @@ -6,18 +6,19 @@ from noaa import utils -def daily_forecast_by_zip_code(zip_code, start_date=None, num_days=6, - metric=False): +def daily_forecast_by_zip_code(zip_code, start_date=None, num_days=6, metric=False): """Return a daily forecast by zip code. :param zip_code: :param start_date: :param num_days: + :param metric: :returns: [ForecastedCondition() ...] """ location_info = [("zipCodeList", zip_code)] + client = "NDFDgenByDayMultiZipCode" # pulled from the details of the API return _daily_forecast_from_location_info( - location_info, start_date=start_date, num_days=num_days, + location_info, client, start_date=start_date, num_days=num_days, metric=metric) @@ -29,21 +30,23 @@ def daily_forecast_by_lat_lon(lat, lon, start_date=None, num_days=6, :param lon: :param start_date: :param num_days: + :param metric: :returns: [ForecastedCondition() ...] """ location_info = [("lat", lat), ("lon", lon)] + client = "NDFDgenByDay" # pulled from the details of the API return _daily_forecast_from_location_info( - location_info, start_date=start_date, num_days=num_days, + location_info, client, start_date=start_date, num_days=num_days, metric=metric) -def daily_forecast_by_location(location, start_date=None, num_days=6, - metric=False): +def daily_forecast_by_location(location, start_date=None, num_days=6, metric=False): """Return a daily forecast by location. :param location: A location string that will be geocoded (ex. "Austin") :param start_date: :param num_days: + :param metric: :returns: [ForecastedCondition() ...] """ loc = geocode.geocode_location(location) @@ -110,21 +113,19 @@ def _parse_conditions(tree): return time_layout_key, values -def _daily_forecast_from_location_info(location_info, start_date=None, - num_days=6, metric=False): +def _daily_forecast_from_location_info(location_info, client, start_date=None, num_days=6, metric=False): if not start_date: start_date = datetime.date.today() # NOTE: the order of the query-string parameters seems to matter; so, # we can't use a dictionary to hold the params - params = location_info + [("format", "24 hourly"), + params = [("whichClient", client)] + location_info + [("format", "24 hourly"), # update to match newest API ("startDate", start_date.strftime("%Y-%m-%d")), ("numDays", str(num_days)), - ("Unit", "m" if metric else "e")] + ("Unit", "m" if metric else "e"), + ("Submit", "Submit")] - FORECAST_BY_DAY_URL = ("http://www.weather.gov/forecasts/xml" - "/sample_products/browser_interface" - "/ndfdBrowserClientByDay.php") + FORECAST_BY_DAY_URL="https://graphical.weather.gov/xml/sample_products/browser_interface/ndfdBrowserClientByDay.php" resp = utils.open_url(FORECAST_BY_DAY_URL, params) tree = utils.parse_xml(resp) diff --git a/noaa/geocode.py b/noaa/geocode.py index e2a0c33..c7a4327 100644 --- a/noaa/geocode.py +++ b/noaa/geocode.py @@ -10,10 +10,8 @@ def geocode_location(location, api_key=None): For high-volume traffic, you will need to specify an API-key. """ - GEOCODE_URL = "http://maps.google.com/maps/geo" - params = [('q', location), - ('sensor', 'false'), - ('output', 'json')] + GEOCODE_URL = "https://maps.googleapis.com/maps/api/geocode/json" # update to match new google API + params = ["latlng", location[0], location[1]] if api_key: params += [('key', api_key)] diff --git a/noaa/observation.py b/noaa/observation.py index 63a259d..3c28127 100644 --- a/noaa/observation.py +++ b/noaa/observation.py @@ -68,7 +68,7 @@ def station_observation_by_station_id(station_id): and return the observation (if any) that has the data we want. """ STATION_OBSERVATIONS_URL = ( - 'http://www.weather.gov/data/current_obs/%s.xml' % station_id) + 'https://w1.weather.gov/data/current_obs/%s.xml' % station_id) # updated to reflect new API resp = noaa.utils.open_url(STATION_OBSERVATIONS_URL) tree = noaa.utils.parse_xml(resp) diff --git a/noaa/stations.py b/noaa/stations.py index 20c9d2c..66b45a2 100644 --- a/noaa/stations.py +++ b/noaa/stations.py @@ -86,5 +86,4 @@ def _parse_stations(fileobj): station = noaa.models.Station(station_id, location) stations.append(station) - return stations diff --git a/noaa/utils.py b/noaa/utils.py index 62dca24..99d479d 100644 --- a/noaa/utils.py +++ b/noaa/utils.py @@ -3,8 +3,9 @@ import sys import urllib from xml.etree import ElementTree as ET - -import dateutil.parser +import urllib.request +from urllib.parse import urlencode, quote_plus +from dateutil import parser def colorize(text, color): @@ -38,11 +39,11 @@ def all_numbers(L): def print_tree(tree, indent=4): """Print an ElementTree for debugging purposes.""" - def print_elem(elem, level): - print " " * (indent * level), - print 'tag="%s"' % elem.tag, - print 'text="%s"' % elem.text.strip() if elem.text is not None else "", - print 'attrib=%s' % elem.attrib + def print_elem(elem, level): # some updates for py3.8 compatibility + print(" " * (indent * level)) + print('tag="%s"' % elem.tag) + print('text="%s"' % elem.text.strip() if elem.text is not None else "") + print('attrib=%s' % elem.attrib) for child in elem.getchildren(): print_elem(child, level + 1) print_elem(tree.getroot(), 0) @@ -50,10 +51,10 @@ def print_elem(elem, level): def open_url(url, params=None): if params: - query_string = urllib.urlencode(params) + query_string = urlencode(params, quote_via=quote_plus) # some updates for py3.8 compatibility url = "?".join([url, query_string]) - resp = urllib.urlopen(url) + resp = urllib.request.urlopen(url) # some updates for py3.8 compatibility return resp @@ -72,7 +73,7 @@ def die_on(*exception_classes, **kwargs): try: yield except exception_classes as e: - print >> sys.stderr, msg_func(e) + print(msg_func(e)) # some updates for py3.8 compatibility sys.exit(exit_code) @@ -82,7 +83,7 @@ def parse_xml(fileobj): def parse_dt(dt): - return dateutil.parser.parse(dt) + return parser.parse(dt) def great_circle_distance(lat1, lon1, lat2, lon2, radius, angle_units="deg"): diff --git a/tools/pip-requires b/tools/pip-requires index 3ad0e81..066a1f7 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,3 +1,4 @@ argparse dateutils pep8 +configparser