From 324eac9320c4b2114acf937f3704ba9f30f4f65a Mon Sep 17 00:00:00 2001 From: Alex Hilson Date: Thu, 21 Jul 2016 15:34:27 +0100 Subject: [PATCH 1/6] Add capability to retrieve region details for #5 --- datapoint/Manager.py | 4 ++ datapoint/regions/RegionManager.py | 60 ++++++++++++++++++++++++++++++ datapoint/regions/__init__.py | 0 datapoint/regions/region_names.py | 19 ++++++++++ tests/integration/regions_test.py | 40 ++++++++++++++++++++ 5 files changed, 123 insertions(+) create mode 100644 datapoint/regions/RegionManager.py create mode 100644 datapoint/regions/__init__.py create mode 100644 datapoint/regions/region_names.py create mode 100644 tests/integration/regions_test.py diff --git a/datapoint/Manager.py b/datapoint/Manager.py index 1ce9aa7..8c203ec 100644 --- a/datapoint/Manager.py +++ b/datapoint/Manager.py @@ -15,6 +15,8 @@ from .Day import Day from .Timestep import Timestep from .Element import Element +from .regions.RegionManager import RegionManager + if (sys.version_info > (3, 0)): long = int @@ -84,6 +86,8 @@ def __init__(self, api_key=""): self.sites_last_request = None self.sites_update_time = 3600 + self.regions = RegionManager(self.api_key) + def __call_api(self, path, params=None): """ Call the datapoint api using the requests module diff --git a/datapoint/regions/RegionManager.py b/datapoint/regions/RegionManager.py new file mode 100644 index 0000000..33be9ff --- /dev/null +++ b/datapoint/regions/RegionManager.py @@ -0,0 +1,60 @@ +import json +from time import time + +import requests + +from datapoint.Site import Site +from datapoint.regions.region_names import REGION_NAMES + +REGIONS_BASE_URL = 'http://datapoint.metoffice.gov.uk/public/data/txt/wxfcs/regionalforecast/json' + +class RegionManager(object): + ''' + Datapoint Manager for national and regional text forecasts + ''' + def __init__(self, api_key, base_url=None): + self.api_key = api_key + self.all_regions_path = '/sitelist' + if not base_url: + self.base_url = REGIONS_BASE_URL + + # The list of regions changes infrequently so limit to requesting it + # every hour. + self.regions_last_update = 0 + self.regions_last_request = None + self.regions_update_time = 3600 + + def call_api(self, path, **kwargs): + ''' + Call datapoint api + ''' + if 'key' not in kwargs: + kwargs['key'] = self.api_key + req = requests.get('{}{}'.format(self.base_url, path), params=kwargs) + + if req.status_code != requests.codes.ok: + req.raise_for_status() + + return req.json() + + def get_all_regions(self): + ''' + Request a list of regions from Datapoint. Returns each Region + as a Site object. Regions rarely change, so we cache the response + for one hour to minimise requests to API. + ''' + if (time() - self.regions_last_update) < self.regions_update_time: + return self.regions_last_request + + response = self.call_api(self.all_regions_path) + regions = [] + for location in response['Locations']['Location']: + region = Site() + region.id = location['@id'] + region.region = location['@name'] + region.name = REGION_NAMES[location['@name']] + regions.append(region) + + self.regions_last_update = time() + self.regions_last_request = regions + return regions diff --git a/datapoint/regions/__init__.py b/datapoint/regions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/datapoint/regions/region_names.py b/datapoint/regions/region_names.py new file mode 100644 index 0000000..8dd5d45 --- /dev/null +++ b/datapoint/regions/region_names.py @@ -0,0 +1,19 @@ +REGION_NAMES = { + 'os': 'Orkney & Shetland', + 'he': 'Highland & Eilean Siar', + 'gr': 'Grampian', + 'ta': 'Tayside', + 'st': 'Strathclyde', + 'dg': 'Dumfries, Galloway, Lothian', + 'ni': 'Northern Ireland', + 'yh': 'Yorkshire & the Humber', + 'ne': 'Northeast England', + 'em': 'East Midlands', + 'ee': 'East of England', + 'se': 'London & Southeast England', + 'nw': 'Northwest England', + 'wm': 'West Midlands', + 'sw': 'Southwest England', + 'wl': 'Wales', + 'uk': 'UK', +} \ No newline at end of file diff --git a/tests/integration/regions_test.py b/tests/integration/regions_test.py new file mode 100644 index 0000000..b5fcd9a --- /dev/null +++ b/tests/integration/regions_test.py @@ -0,0 +1,40 @@ +import os + +from nose.tools import * +from requests import HTTPError + +import datapoint + + +class TestRegions(object): + def __init__(self): + self.manager = datapoint.Manager(api_key=os.environ['API_KEY']) + self.regions = self.manager.regions + + def test_key(self): + assert self.regions.api_key == os.environ['API_KEY'] + + def test_call_api(self): + assert ( + u'RegionalFcst' in self.regions.call_api('/500')) + assert_raises( + HTTPError, self.regions.call_api, '/fake_path') + assert_raises( + HTTPError, self.regions.call_api, '/500', key='fake_key') + + def test_get_all_regions(self): + all_regions = self.regions.get_all_regions() + sample_region = filter(lambda x: x.id == '515', all_regions)[0] + assert (sample_region.name == 'UK') + assert (sample_region.region == 'uk') + + def test_get_forecast(self): + sample_region = self.regions.get_all_regions()[0] + response = self.regions.get_forecast(sample_region.id) + assert (response['regionId'] == sample_region.id) + + # Datapoint currently serves + forecast_periods = response['FcstPeriods']['Periods'] + forecast_ids = [period.id for period in forecast_periods] + expected_ids = ['day1to2', 'day3to5', 'day6to15', 'day16to30'] + assert (forecast_ids == expected_ids) \ No newline at end of file From df7d3c31592fb7229228eba60228aecfb6e17bd5 Mon Sep 17 00:00:00 2001 From: Alex Hilson Date: Thu, 21 Jul 2016 16:47:07 +0100 Subject: [PATCH 2/6] Added way to request the text forecast for #5 --- datapoint/regions/RegionManager.py | 7 +++++++ tests/integration/regions_test.py | 8 ++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/datapoint/regions/RegionManager.py b/datapoint/regions/RegionManager.py index 33be9ff..19c2516 100644 --- a/datapoint/regions/RegionManager.py +++ b/datapoint/regions/RegionManager.py @@ -58,3 +58,10 @@ def get_all_regions(self): self.regions_last_update = time() self.regions_last_request = regions return regions + + def get_forecast(self, region_id): + ''' + Request forecast for a specific region_id. + ''' + result = self.call_api('/{}'.format(region_id)) + return result['RegionalFcst'] diff --git a/tests/integration/regions_test.py b/tests/integration/regions_test.py index b5fcd9a..29797b6 100644 --- a/tests/integration/regions_test.py +++ b/tests/integration/regions_test.py @@ -31,10 +31,10 @@ def test_get_all_regions(self): def test_get_forecast(self): sample_region = self.regions.get_all_regions()[0] response = self.regions.get_forecast(sample_region.id) - assert (response['regionId'] == sample_region.id) + assert (response['regionId'] == sample_region.region) - # Datapoint currently serves - forecast_periods = response['FcstPeriods']['Periods'] - forecast_ids = [period.id for period in forecast_periods] + # Based on what Datapoint serves at time of writing... + forecast_periods = response['FcstPeriods']['Period'] + forecast_ids = [period['id'] for period in forecast_periods] expected_ids = ['day1to2', 'day3to5', 'day6to15', 'day16to30'] assert (forecast_ids == expected_ids) \ No newline at end of file From d65309bb37cc7199aa1f7e332bba4a4dcbdfbabd Mon Sep 17 00:00:00 2001 From: Alex Hilson Date: Fri, 22 Jul 2016 11:28:08 +0100 Subject: [PATCH 3/6] Clarified that current functionality is for getting raw response --- datapoint/regions/RegionManager.py | 7 +++---- tests/integration/regions_test.py | 5 +++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/datapoint/regions/RegionManager.py b/datapoint/regions/RegionManager.py index 19c2516..446fff8 100644 --- a/datapoint/regions/RegionManager.py +++ b/datapoint/regions/RegionManager.py @@ -59,9 +59,8 @@ def get_all_regions(self): self.regions_last_request = regions return regions - def get_forecast(self, region_id): + def get_raw_forecast(self, region_id): ''' - Request forecast for a specific region_id. + Request unformatted forecast for a specific region_id. ''' - result = self.call_api('/{}'.format(region_id)) - return result['RegionalFcst'] + return self.call_api('/{}'.format(region_id)) diff --git a/tests/integration/regions_test.py b/tests/integration/regions_test.py index 29797b6..246c15a 100644 --- a/tests/integration/regions_test.py +++ b/tests/integration/regions_test.py @@ -28,9 +28,10 @@ def test_get_all_regions(self): assert (sample_region.name == 'UK') assert (sample_region.region == 'uk') - def test_get_forecast(self): + def test_get_raw_forecast(self): sample_region = self.regions.get_all_regions()[0] - response = self.regions.get_forecast(sample_region.id) + response = self.regions.get_raw_forecast( + sample_region.id)['RegionalFcst'] assert (response['regionId'] == sample_region.region) # Based on what Datapoint serves at time of writing... From 2c5ee099d0bbd366ca87be0ff3e88e29e1bcda14 Mon Sep 17 00:00:00 2001 From: Alex Hilson Date: Fri, 22 Jul 2016 11:30:18 +0100 Subject: [PATCH 4/6] Added example showing retrieval of a regional text forecast --- examples/text_forecast/text_forecast.py | 36 +++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 examples/text_forecast/text_forecast.py diff --git a/examples/text_forecast/text_forecast.py b/examples/text_forecast/text_forecast.py new file mode 100644 index 0000000..73f1ab4 --- /dev/null +++ b/examples/text_forecast/text_forecast.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +""" +This example will print out the 30 day text forecast for a region of the UK. +""" + +import datapoint + +# Create datapoint connection +#aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee +conn = datapoint.Manager(api_key="c4260d02-3133-488d-91bd-abf66150dbe9") + +# Get all regions and print out their details +regions = conn.regions.get_all_regions() +for region in regions: + print (region.name, region.id, region.region) + +# Get all forecasts for a specific region +my_region = regions[0] +forecast = conn.regions.get_raw_forecast(my_region.id)['RegionalFcst'] + +# Print the forecast details +print 'Forecast for {} (issued at {}):'.format(my_region.name, forecast['issuedAt']) + +sections = forecast['FcstPeriods']['Period'] +for section in forecast['FcstPeriods']['Period']: + paragraph = [] + content = section['Paragraph'] + + # Some paragraphs have multiple sections + if isinstance(content, dict): + paragraph.append(content) + else: + paragraph = content + + for line in paragraph: + print '{}\n{}\n'.format(line['title'], line['$']) From 3f14210d2adc5f7473a2c7940d9384439d57a0c1 Mon Sep 17 00:00:00 2001 From: Alex Hilson Date: Fri, 22 Jul 2016 14:58:45 +0100 Subject: [PATCH 5/6] Added regions as package to setup script --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7a99af6..81c8dbf 100644 --- a/setup.py +++ b/setup.py @@ -60,7 +60,7 @@ maintainer_email='jacob@jacobtomlinson.co.uk', url='https://github.com/jacobtomlinson/datapoint-python', license='GPLv3', - packages=['datapoint'], + packages=['datapoint', 'datapoint.regions'], classifiers=[ 'Development Status :: 3 - Alpha', 'Programming Language :: Python :: 2.6', From a865d8861a8a5b03e6fe535e13a147ab05504611 Mon Sep 17 00:00:00 2001 From: Alex Hilson Date: Mon, 25 Jul 2016 15:05:54 +0100 Subject: [PATCH 6/6] Cleaned text_forecast example --- examples/text_forecast/text_forecast.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/text_forecast/text_forecast.py b/examples/text_forecast/text_forecast.py index 73f1ab4..61ae08e 100644 --- a/examples/text_forecast/text_forecast.py +++ b/examples/text_forecast/text_forecast.py @@ -6,8 +6,7 @@ import datapoint # Create datapoint connection -#aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee -conn = datapoint.Manager(api_key="c4260d02-3133-488d-91bd-abf66150dbe9") +conn = datapoint.Manager(api_key="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee") # Get all regions and print out their details regions = conn.regions.get_all_regions()