def __init__(self, api_key=''): self._api_key = api_key self._request_handler = FMIRequestHandler(self._api_key) self._PATH_TO_STATIONS_CSV = "data/stations.csv" self._PATH_TO_QUERY_METADATA = "data/supported_queries.json" self._stations = self._load_station_metadata() self._supported_queries = self._load_supported_queries_metadata() self._parser = FMIxmlParser()
def should_return_empty_list_if_only_400_errors(mock_fmirequest): query = create_daily_query(datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), datetime(2011, 1, 23, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone)) mock_fmirequest.return_value.get.side_effect = [RequestException('error', 400), RequestException('error', 400)] mock_instance = mock_fmirequest.return_value mock_instance.get.return_value = 'data' handler = FMIRequestHandler('apikey') result = handler.request(query, max_timespan=_DAILY_REQUEST_MAX_RANGE_HOURS, progress_callback=None) assert_equal(2, mock_instance.get.call_count) assert_equal(0, len(result))
def should_raise_error_normally_if_error_code_is_something_else_than_400(mock_fmirequest): query = create_daily_query(datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), datetime(2011, 1, 23, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone)) mock_fmirequest.return_value.get.side_effect = [RequestException('error', 'othererror'), RequestException('error', 400)] mock_instance = mock_fmirequest.return_value mock_instance.get.return_value = 'data' handler = FMIRequestHandler('apikey') with pytest.raises(RequestException) as e: handler.request(query, max_timespan=_DAILY_REQUEST_MAX_RANGE_HOURS, progress_callback=None) assert_equal('othererror', e.value.error_code) assert_equal(1, mock_instance.get.call_count)
def should_call_fmirequest_in_two_parts_for_372_day_time_span(mock_fmirequest): query = create_daily_query(datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), datetime(2011, 1, 23, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone)) expected = [create_daily_query(datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), datetime(2011, 1, 8, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone)), create_daily_query(datetime(2011, 1, 8, hour=0, minute=2, second=0, microsecond=0, tzinfo=timezone), datetime(2011, 1, 23, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone))] expected_calls = [call(expected[0]), call(expected[1])] mock_instance = mock_fmirequest.return_value mock_instance.get.return_value = 'data' handler = FMIRequestHandler('apikey') result = handler.request(query, max_timespan=_DAILY_REQUEST_MAX_RANGE_HOURS, progress_callback=None) mock_instance.get.assert_has_calls(expected_calls) assert_equal(2, mock_instance.get.call_count) assert_equal(2, len(result))
def should_return_available_data_if_first_part_of_request_does_not_exist(mock_fmirequest): query = create_daily_query(datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), datetime(2011, 1, 23, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone)) expected = [create_daily_query(datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), datetime(2011, 1, 8, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone))] expected_calls = [call(expected[0])] # On first request throw exception with code 400, then return return value normally # Simulates query where first part is not available, but second is mock_fmirequest.return_value.get.side_effect = [RequestException('error', 400), mock.DEFAULT] mock_instance = mock_fmirequest.return_value mock_instance.get.return_value = 'data' handler = FMIRequestHandler('apikey') result = handler.request(query, max_timespan=_DAILY_REQUEST_MAX_RANGE_HOURS, progress_callback=None) mock_instance.get.assert_has_calls(expected_calls) assert_equal(2, mock_instance.get.call_count) assert_equal(1, len(result))
def should_get_year_in_one_request(mock_fmirequest): query = {'request': 'getFeature', 'storedquery_id': 'fmi::observations::weather::daily::multipointcoverage', 'fmisid': '1234', 'starttime': datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), 'endtime': datetime(2011, 1, 5, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone) } expected = {'starttime': datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), 'endtime': datetime(2011, 1, 5, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), 'fmisid': '1234', 'request': 'getFeature', 'storedquery_id': 'fmi::observations::weather::daily::multipointcoverage' } mock_instance = mock_fmirequest.return_value mock_instance.get.return_value = 'data' handler = FMIRequestHandler('apikey') result = handler.request(query, max_timespan=_DAILY_REQUEST_MAX_RANGE_HOURS, progress_callback=None) mock_instance.get.assert_has_calls([call(expected)]) assert_equal(1, mock_instance.get.call_count) assert_equal(1, len(result))
def describe_fmi_request_handler(): fmi_handler = FMIRequestHandler('apikey') _DAILY_REQUEST_MAX_RANGE_HOURS = 8928 _REALTIME_REQUEST_MAX_RANGE_HOURS = 168 @mock.patch('fmiapi.fmirequesthandler.FMIRequest', spec=True) def should_get_year_in_one_request(mock_fmirequest): query = {'request': 'getFeature', 'storedquery_id': 'fmi::observations::weather::daily::multipointcoverage', 'fmisid': '1234', 'starttime': datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), 'endtime': datetime(2011, 1, 5, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone) } expected = {'starttime': datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), 'endtime': datetime(2011, 1, 5, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), 'fmisid': '1234', 'request': 'getFeature', 'storedquery_id': 'fmi::observations::weather::daily::multipointcoverage' } mock_instance = mock_fmirequest.return_value mock_instance.get.return_value = 'data' handler = FMIRequestHandler('apikey') result = handler.request(query, max_timespan=_DAILY_REQUEST_MAX_RANGE_HOURS, progress_callback=None) mock_instance.get.assert_has_calls([call(expected)]) assert_equal(1, mock_instance.get.call_count) assert_equal(1, len(result)) @mock.patch('fmiapi.fmirequesthandler.FMIRequest', spec=True) def should_call_fmirequest_in_two_parts_for_372_day_time_span(mock_fmirequest): query = create_daily_query(datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), datetime(2011, 1, 23, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone)) expected = [create_daily_query(datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), datetime(2011, 1, 8, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone)), create_daily_query(datetime(2011, 1, 8, hour=0, minute=2, second=0, microsecond=0, tzinfo=timezone), datetime(2011, 1, 23, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone))] expected_calls = [call(expected[0]), call(expected[1])] mock_instance = mock_fmirequest.return_value mock_instance.get.return_value = 'data' handler = FMIRequestHandler('apikey') result = handler.request(query, max_timespan=_DAILY_REQUEST_MAX_RANGE_HOURS, progress_callback=None) mock_instance.get.assert_has_calls(expected_calls) assert_equal(2, mock_instance.get.call_count) assert_equal(2, len(result)) @mock.patch('fmiapi.fmirequesthandler.FMIRequest', spec=True) def should_return_available_data_if_first_part_of_request_does_not_exist(mock_fmirequest): query = create_daily_query(datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), datetime(2011, 1, 23, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone)) expected = [create_daily_query(datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), datetime(2011, 1, 8, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone))] expected_calls = [call(expected[0])] # On first request throw exception with code 400, then return return value normally # Simulates query where first part is not available, but second is mock_fmirequest.return_value.get.side_effect = [RequestException('error', 400), mock.DEFAULT] mock_instance = mock_fmirequest.return_value mock_instance.get.return_value = 'data' handler = FMIRequestHandler('apikey') result = handler.request(query, max_timespan=_DAILY_REQUEST_MAX_RANGE_HOURS, progress_callback=None) mock_instance.get.assert_has_calls(expected_calls) assert_equal(2, mock_instance.get.call_count) assert_equal(1, len(result)) @mock.patch('fmiapi.fmirequesthandler.FMIRequest', spec=True) def should_return_empty_list_if_only_400_errors(mock_fmirequest): query = create_daily_query(datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), datetime(2011, 1, 23, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone)) mock_fmirequest.return_value.get.side_effect = [RequestException('error', 400), RequestException('error', 400)] mock_instance = mock_fmirequest.return_value mock_instance.get.return_value = 'data' handler = FMIRequestHandler('apikey') result = handler.request(query, max_timespan=_DAILY_REQUEST_MAX_RANGE_HOURS, progress_callback=None) assert_equal(2, mock_instance.get.call_count) assert_equal(0, len(result)) @mock.patch('fmiapi.fmirequesthandler.FMIRequest', spec=True) def should_raise_error_normally_if_error_code_is_something_else_than_400(mock_fmirequest): query = create_daily_query(datetime(2010, 1, 1, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone), datetime(2011, 1, 23, hour=0, minute=1, second=0, microsecond=0, tzinfo=timezone)) mock_fmirequest.return_value.get.side_effect = [RequestException('error', 'othererror'), RequestException('error', 400)] mock_instance = mock_fmirequest.return_value mock_instance.get.return_value = 'data' handler = FMIRequestHandler('apikey') with pytest.raises(RequestException) as e: handler.request(query, max_timespan=_DAILY_REQUEST_MAX_RANGE_HOURS, progress_callback=None) assert_equal('othererror', e.value.error_code) assert_equal(1, mock_instance.get.call_count)
def set_apikey(self, api_key): self._api_key = api_key self._request_handler = FMIRequestHandler(self._api_key)
class FMIApi: """ Provides a simple interface to interact with FMI API by providing basic functions to get data from FMI's open data service. """ def __init__(self, api_key=''): self._api_key = api_key self._request_handler = FMIRequestHandler(self._api_key) self._PATH_TO_STATIONS_CSV = "data/stations.csv" self._PATH_TO_QUERY_METADATA = "data/supported_queries.json" self._stations = self._load_station_metadata() self._supported_queries = self._load_supported_queries_metadata() self._parser = FMIxmlParser() def set_apikey(self, api_key): self._api_key = api_key self._request_handler = FMIRequestHandler(self._api_key) def get_apikey(self): return self._api_key def get_data(self, params, callback_function=None, change_to_parsing=None): if params[ "storedquery_id"] == "fmi::observations::weather::daily::multipointcoverage": # Special logic for daily observations params['endtime'] += datetime.timedelta( days=1 ) # add one day to end time to get final day into result too data = self._request_handler.request( params, max_timespan=params['max_hours_range'], progress_callback=callback_function) # Notify ui that moving to parsing phase if change_to_parsing is not None: change_to_parsing() try: return self._parser.parse(data, progress_callback=callback_function) except NoDataException: # Augment date data to exception and raise it again raise NoDataException(starttime=params['starttime'], endtime=params['endtime']) def _load_station_metadata(self): """ FMI apparently didn't provide an API-endpoint to get list of all the stations. For now, we load the required station information from CSV-file. Change to a api-endpoint if one becomes (or is already?) available. """ stations = [] with open(self._PATH_TO_STATIONS_CSV, "r", encoding="utf8") as file: reader = csv.DictReader(file, [ "Name", "FMISID", "LPNN", "WMO", "lat", "lon", "Altitude", "Group", "Since" ], delimiter=";") for row in reader: stations.append(row) return stations def _load_supported_queries_metadata(self): with open(self._PATH_TO_QUERY_METADATA, "r", encoding="utf8") as file: queries = json.load(file) return queries def get_stations(self): return self._stations def get_supported_queries(self): return self._supported_queries def get_catalogue_of_station(self, fmisid): # Add extra metadata for each dataset which are required for queries and translations # in short data which is not provided by catalogue service. See supported_queries.json datasets = fmicatalogservice.get_station_metadata(fmisid) augmented = [] for ds in datasets: for sq in self._supported_queries: if re.search(sq['id'], ds['identifier']): augmented.append({**ds, **sq}) break return augmented def get_index_of_station(self, place_name): for i in range(0, len(self._stations)): if self._stations[i]["Name"] == place_name: return i return -1