From cf171f6e0f831b3fa3e3cbbdfd9118dbca4d1363 Mon Sep 17 00:00:00 2001 From: Kimmo Huoman Date: Mon, 21 Dec 2020 18:33:08 +0200 Subject: [PATCH 1/4] Drop requirement for place or coordinates, allows using for example FMISID to define location. --- README.md | 17 +++++++++++++++++ fmi/fmi.py | 10 +++------- setup.py | 2 +- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e65570e..123f847 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,23 @@ f = FMI() print(f.forecast()) ``` +### New in 1.1.0 +`place` or `coordinates` are not longer required, +but they are respected if present. +This allows the usage of `fmisid` and `wmo` for definition of location, +allowing better transparency on what location is used. + +You can list view a list of locations at: +https://www.ilmatieteenlaitos.fi/havaintoasemat + +For example: +``` +from fmi import FMI +f = FMI() +# Fetch and print observations from Lappeenranta Airport +print(f.observations(fmisid=101237)) +``` + Forecast icons -------------- diff --git a/fmi/fmi.py b/fmi/fmi.py index 0a1cf0f..a62f263 100644 --- a/fmi/fmi.py +++ b/fmi/fmi.py @@ -6,18 +6,14 @@ class FMI(object): - apikey = None api_endpoint = 'https://opendata.fmi.fi/wfs' def __init__(self, apikey=None, place=None, coordinates=None): - self.apikey = os.environ.get('FMI_APIKEY', apikey) self.place = os.environ.get('FMI_PLACE', place) self.coordinates = os.environ.get('FMI_COORDINATES', coordinates) - if self.apikey is not None: + if apikey is not None: warnings.simplefilter('default') warnings.warn('The use of FMI API key is deprecated.', DeprecationWarning) - if self.place is None and self.coordinates is None: - raise AttributeError('FMI place or coordinates not set.') def _parse_identifier(self, x): identifier = x['gml:id'].split('-')[-1].lower() @@ -91,9 +87,9 @@ def get(self, storedquery_id, **params): 'request': 'getFeature', 'storedquery_id': storedquery_id, } - if self.coordinates is None: + if self.place is not None: query_params['place'] = self.place - else: + elif self.coordinates is not None: query_params['latlon'] = self.coordinates query_params.update(params) diff --git a/setup.py b/setup.py index c0b433e..ed21b1b 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='fmi_weather', - version='1.0.0', + version='1.1.0', description='FMI weather data fetcher', author='Kimmo Huoman', author_email='kipenroskaposti@gmail.com', From 4ab8a982bfa67686a11df62174ce7d7bbd23980b Mon Sep 17 00:00:00 2001 From: Kimmo Huoman Date: Mon, 21 Dec 2020 18:33:52 +0200 Subject: [PATCH 2/4] Inherit Observation as Forecast, to make detection between forecasts and observations easier. --- README.md | 2 +- fmi/fmi.py | 19 ++++++++++--------- fmi/observation.py | 11 +++++++++-- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 123f847..8f8bf3d 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ After setting the environment variables, you can use the library without "any" i ``` from fmi import FMI f = FMI() -# f.forecast() returns a list of Observation -objects (sorry, confusing, I know :P) for the next 36 hours +# f.forecast() returns a list of Forecast -objects print(f.forecast()) ``` diff --git a/fmi/fmi.py b/fmi/fmi.py index a62f263..046ec87 100644 --- a/fmi/fmi.py +++ b/fmi/fmi.py @@ -1,7 +1,7 @@ import os import requests import warnings -from .observation import Observation +from .observation import Observation, Forecast from bs4 import BeautifulSoup @@ -51,7 +51,7 @@ def _parse_identifier(self, x): return 'radiation_diffuse_accumulation', 1 return None, 1 - def _parse_response(self, r): + def _parse_response(self, r, klass=Observation): bs = BeautifulSoup(r.text, 'html.parser') d = {} @@ -79,10 +79,10 @@ def _parse_response(self, r): d[timestamp][identifier] = value return sorted( - [Observation(k, v) for k, v in d.items()], + [klass(k, v) for k, v in d.items()], key=lambda x: x.time) - def get(self, storedquery_id, **params): + def get(self, storedquery_id, klass=Observation, **params): query_params = { 'request': 'getFeature', 'storedquery_id': storedquery_id, @@ -93,10 +93,10 @@ def get(self, storedquery_id, **params): query_params['latlon'] = self.coordinates query_params.update(params) - return self._parse_response( - requests.get( - self.api_endpoint, - params=query_params)) + request = requests.get(self.api_endpoint, params=query_params) + request.raise_for_status() + + return self._parse_response(request, klass=klass) def observations(self, **params): return self.get( @@ -110,4 +110,5 @@ def forecast(self, model='hirlam', **params): return self.get( 'fmi::forecast::%s::surface::point::timevaluepair' % (model), maxlocations=1, - **params) + **params, + klass=Forecast) diff --git a/fmi/observation.py b/fmi/observation.py index d68dc08..e135969 100644 --- a/fmi/observation.py +++ b/fmi/observation.py @@ -48,8 +48,11 @@ def __init__(self, timestamp, point): 'radiation_diffuse_accumulation', None) def __repr__(self): - return '' % ( - self.time.isoformat(), self.temperature) + return '<%s: %s - %.1f C>' % ( + self.__class__.__name__, + self.time.isoformat(), + self.temperature, + ) @property def icon(self): @@ -79,3 +82,7 @@ def as_influx_measurement(self): if key not in ['time'] and value is not None } } + + +class Forecast(Observation): + pass From ba37080645153d66a8ae1c8df10312806999f8ec Mon Sep 17 00:00:00 2001 From: Kimmo Huoman Date: Mon, 21 Dec 2020 18:59:18 +0200 Subject: [PATCH 3/4] Add use of fmisid to tests. --- tests/test_observation.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_observation.py b/tests/test_observation.py index 5426648..0f797c4 100644 --- a/tests/test_observation.py +++ b/tests/test_observation.py @@ -12,3 +12,7 @@ def test_lappeenranta(self): for point in f.observations(): assert point.time < now assert isinstance(point.temperature, float) + + for point in f.observations(fmisid=101237): + assert point.time < now + assert isinstance(point.temperature, float) From c54f8e9645c20e5499a5eb5714b38c4f01437351 Mon Sep 17 00:00:00 2001 From: Kimmo Huoman Date: Mon, 21 Dec 2020 19:02:43 +0200 Subject: [PATCH 4/4] Make tests more robust by updating now constantly. The issue rises with using Lappeenranta Airport as measurement point, as it updates once per minute. --- tests/test_forecast.py | 6 ++---- tests/test_observation.py | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/test_forecast.py b/tests/test_forecast.py index 7cb2d77..5ccdc51 100644 --- a/tests/test_forecast.py +++ b/tests/test_forecast.py @@ -6,14 +6,12 @@ class TestForecast(unittest.TestCase): def test_lappeenranta(self): - now = datetime.now(tz=tzutc()) - f = FMI(place='Lappeenranta') for point in f.forecast(): - assert point.time > now + assert point.time > datetime.now(tz=tzutc()) assert isinstance(point.temperature, float) f = FMI(coordinates='%.03f,%.03f' % (61.058876, 28.186262)) for point in f.forecast(): - assert point.time > now + assert point.time > datetime.now(tz=tzutc()) assert isinstance(point.temperature, float) diff --git a/tests/test_observation.py b/tests/test_observation.py index 0f797c4..bc2746c 100644 --- a/tests/test_observation.py +++ b/tests/test_observation.py @@ -6,13 +6,11 @@ class TestObservations(unittest.TestCase): def test_lappeenranta(self): - now = datetime.now(tz=tzutc()) - f = FMI(place='Lappeenranta') for point in f.observations(): - assert point.time < now + assert point.time < datetime.now(tz=tzutc()) assert isinstance(point.temperature, float) for point in f.observations(fmisid=101237): - assert point.time < now + assert point.time < datetime.now(tz=tzutc()) assert isinstance(point.temperature, float)