From 3331dec3d70169813c30cd42bf19d9413ed9562c Mon Sep 17 00:00:00 2001 From: csparpa Date: Sun, 24 Nov 2019 22:11:25 +0100 Subject: [PATCH] Started #42: three_hours_forecast + daily_forecast = forecast_at_coords --- pyowm/weatherapi25/weather_manager.py | 75 +++++++------------ .../weatherapi25/test_integration.py | 14 ++-- .../unit/weatherapi25/test_weather_manager.py | 70 +++++++++-------- 3 files changed, 73 insertions(+), 86 deletions(-) diff --git a/pyowm/weatherapi25/weather_manager.py b/pyowm/weatherapi25/weather_manager.py index ce7b6405..dc0b5f53 100644 --- a/pyowm/weatherapi25/weather_manager.py +++ b/pyowm/weatherapi25/weather_manager.py @@ -294,19 +294,22 @@ def forecast_at_place(self, name, interval, limit=None): else: return None - def three_hours_forecast_at_coords(self, lat, lon): + def forecast_at_coords(self, lat, lon, interval, limit=None): """ - Queries the OWM Weather API for three hours weather forecast for the - specified geographic coordinate (eg: latitude: 51.5073509, - longitude: -0.1277583). A *Forecaster* object is returned, - containing a *Forecast* instance covering a global streak of - five days: this instance encapsulates *Weather* objects, with a time - interval of three hours one from each other + Queries the OWM Weather API for weather forecast for the + specified geographic coordinates with the given time granularity. + A *Forecaster* object is returned, containing a *Forecast*: this instance + encapsulates *Weather* objects corresponding to the provided granularity. :param lat: location's latitude, must be between -90.0 and 90.0 :type lat: int/float :param lon: location's longitude, must be between -180.0 and 180.0 :type lon: int/float + :param interval: the granularity of the forecast, among `3h` and 'daily' + :type interval: str among `3h` and 'daily' + :param limit: the maximum number of *Weather* items to be retrieved + (default is ``None``, which stands for any number of items) + :type limit: int or ``None`` :returns: a *Forecaster* instance or ``None`` if forecast data is not available for the specified location :raises: *ParseResponseException* when OWM Weather API responses' data @@ -315,15 +318,29 @@ def three_hours_forecast_at_coords(self, lat, lon): """ geo.assert_is_lon(lon) geo.assert_is_lat(lat) + assert isinstance(interval, str), "Interval must be a string" + if limit is not None: + assert isinstance(limit, int), "'limit' must be an int or None" + if limit < 1: + raise ValueError("'limit' must be None or greater than zero") params = {'lon': lon, 'lat': lat} - _, json_data = self.http_client.get_json(THREE_HOURS_FORECAST_URI, params=params) + if limit is not None: + params['cnt'] = limit + if interval == '3h': + uri = THREE_HOURS_FORECAST_URI + elif interval == 'daily': + uri = DAILY_FORECAST_URI + else: + raise ValueError("Unsupported time interval for forecast") + _, json_data = self.http_client.get_json(uri, params=params) fc = forecast.Forecast.from_dict(json_data) if fc is not None: - fc.interval = "3h" + fc.interval = interval return forecaster.Forecaster(fc) else: return None + def three_hours_forecast_at_id(self, id): """ Queries the OWM Weather API for three hours weather forecast for the @@ -352,46 +369,6 @@ def three_hours_forecast_at_id(self, id): else: return None - def daily_forecast_at_coords(self, lat, lon, limit=None): - """ - Queries the OWM Weather API for daily weather forecast for the specified - geographic coordinate (eg: latitude: 51.5073509, longitude: -0.1277583). - A *Forecaster* object is returned, containing a *Forecast* instance - covering a global streak of fourteen days by default: this instance - encapsulates *Weather* objects, with a time interval of one day one - from each other - - :param lat: location's latitude, must be between -90.0 and 90.0 - :type lat: int/float - :param lon: location's longitude, must be between -180.0 and 180.0 - :type lon: int/float - :param limit: the maximum number of daily *Weather* items to be - retrieved (default is ``None``, which stands for any number of - items) - :type limit: int or ``None`` - :returns: a *Forecaster* instance or ``None`` if forecast data is not - available for the specified location - :raises: *ParseResponseException* when OWM Weather API responses' data - cannot be parsed, *APICallException* when OWM Weather API can not be - reached, *ValueError* if negative values are supplied for limit - """ - geo.assert_is_lon(lon) - geo.assert_is_lat(lat) - if limit is not None: - assert isinstance(limit, int), "'limit' must be an int or None" - if limit < 1: - raise ValueError("'limit' must be None or greater than zero") - params = {'lon': lon, 'lat': lat} - if limit is not None: - params['cnt'] = limit - _, json_data = self.http_client.get_json(DAILY_FORECAST_URI, params=params) - fc = forecast.Forecast.from_dict(json_data) - if fc is not None: - fc.interval = "daily" - return forecaster.Forecaster(fc) - else: - return None - def daily_forecast_at_id(self, id, limit=None): """ Queries the OWM Weather API for daily weather forecast for the specified diff --git a/tests/integration/weatherapi25/test_integration.py b/tests/integration/weatherapi25/test_integration.py index e3c25653..4f3b58e8 100644 --- a/tests/integration/weatherapi25/test_integration.py +++ b/tests/integration/weatherapi25/test_integration.py @@ -207,14 +207,14 @@ def test_forecast_at_place_on_3h(self): for weather in f2: self.assertTrue(weather is not None) - def test_three_hours_forecast_at_coords(self): + def test_forecast_at_coords_on_3h(self): """ Test feature: get 3 hours forecast at a specific geographic coordinate """ # London,uk - fc1 = self.__owm.three_hours_forecast_at_coords(51.5073509, -0.1277583) + fc1 = self.__owm.forecast_at_coords(51.5073509, -0.1277583, "3h") # Kiev - fc2 = self.__owm.three_hours_forecast_at_coords(50.4501, 30.5234) + fc2 = self.__owm.forecast_at_coords(50.4501, 30.5234, "3h") self.assertTrue(fc1) f1 = fc1.forecast self.assertTrue(f1 is not None) @@ -234,7 +234,7 @@ def test_three_hours_forecast_at_coords(self): for weather in f2: self.assertTrue(weather is not None) with self.assertRaises(ValueError): - self.__owm.three_hours_forecast_at_coords(199, 199) + self.__owm.forecast_at_coords(199, 199, '3h') def test_three_hours_forecast_at_id(self): """ @@ -295,11 +295,11 @@ def forecast_at_place_daily(self): for weather in f2: self.assertTrue(weather is not None) - def test_daily_forecast_at_coords(self): + def test_forecast_at_coords_daily(self): """ Test feature: get daily forecast at a specific geographic coordinate """ - fc1 = self.__owm.daily_forecast_at_coords(51.5073509, -0.1277583) # London,uk + fc1 = self.__owm.forecast_at_coords(51.5073509, -0.1277583, 'daily') # London,uk self.assertTrue(fc1) f1 = fc1.forecast self.assertTrue(f1 is not None) @@ -310,7 +310,7 @@ def test_daily_forecast_at_coords(self): for weather in f1: self.assertTrue(weather is not None) with self.assertRaises(ValueError): - self.__owm.daily_forecast_at_coords(199, 199) + self.__owm.forecast_at_coords(199, 199, 'daily') def test_daily_forecast_at_id(self): """ diff --git a/tests/unit/weatherapi25/test_weather_manager.py b/tests/unit/weatherapi25/test_weather_manager.py index e1b2a0c0..c7f1bc77 100644 --- a/tests/unit/weatherapi25/test_weather_manager.py +++ b/tests/unit/weatherapi25/test_weather_manager.py @@ -272,13 +272,29 @@ def test_forecast_at_place_on_3h_when_forecast_not_found(self): HttpClient.get_json = original_func self.assertIsNone(result) - def test_three_hours_forecast_at_coords(self): + def test_forecast_at_coords_failing(self): + self.assertRaises(ValueError, WeatherManager.forecast_at_coords, + self.__test_instance, -100.0, 0.0, '3h', None) + self.assertRaises(ValueError, WeatherManager.forecast_at_coords, + self.__test_instance, 100.0, 0.0, '3h', None) + self.assertRaises(ValueError, WeatherManager.forecast_at_coords, + self.__test_instance, 0.0, -200.0, '3h', None) + self.assertRaises(ValueError, WeatherManager.forecast_at_coords, + self.__test_instance, 0.0, 200.0, '3h', None) + self.assertRaises(AssertionError, WeatherManager.forecast_at_coords, + self.__test_instance, 0.0, 60.0, None, None) + self.assertRaises(ValueError, WeatherManager.forecast_at_coords, + self.__test_instance, 0.0, 60.0, 'unsupported', None) + self.assertRaises(ValueError, WeatherManager.forecast_at_coords, + self.__test_instance, 0.0, 60.0, '3h', -4) + + def test_forecast_at_coords_on_3h(self): original_func = HttpClient.get_json HttpClient.get_json = \ self.mock_api_call_returning_3h_forecast_at_coords result = \ self.__test_instance\ - .three_hours_forecast_at_coords(51.50853, -0.12574) + .forecast_at_coords(51.50853, -0.12574, "3h") HttpClient.get_json = original_func self.assertTrue(isinstance(result, Forecaster)) forecast = result.forecast @@ -290,24 +306,14 @@ def test_three_hours_forecast_at_coords(self): for weather in forecast: self.assertTrue(isinstance(weather, Weather)) - def test_three_hours_forecast_at_coords_when_forecast_not_found(self): + def test_forecast_at_coords_on_3h_when_forecast_not_found(self): original_func = HttpClient.get_json HttpClient.get_json = \ self.mock_api_call_returning_empty_3h_forecast - result = self.__test_instance.three_hours_forecast_at_coords(51.50853, -0.12574) + result = self.__test_instance.forecast_at_coords(51.50853, -0.12574, '3h') HttpClient.get_json = original_func self.assertIsNone(result) - def test_three_hours_forecast_at_coords_fails_with_wrong_params(self): - self.assertRaises(ValueError, WeatherManager.three_hours_forecast_at_coords, - self.__test_instance, -100.0, 0.0) - self.assertRaises(ValueError, WeatherManager.three_hours_forecast_at_coords, - self.__test_instance, 100.0, 0.0) - self.assertRaises(ValueError, WeatherManager.three_hours_forecast_at_coords, - self.__test_instance, 0.0, -200.0) - self.assertRaises(ValueError, WeatherManager.three_hours_forecast_at_coords, - self.__test_instance, 0.0, 200.0) - def test_three_hours_forecast_at_id(self): original_func = HttpClient.get_json HttpClient.get_json = \ @@ -360,12 +366,12 @@ def test_forecast_at_place_daily_when_forecast_not_found(self): HttpClient.get_json = original_func self.assertIsNone(result) - def test_daily_forecast_at_coords(self): + def test_forecast_at_coords_daily(self): original_func = HttpClient.get_json HttpClient.get_json = \ self.mock_api_call_returning_daily_forecast_at_coords result = \ - self.__test_instance.daily_forecast_at_coords(51.50853, -0.12574, 2) + self.__test_instance.forecast_at_coords(51.50853, -0.12574, 'daily', 2) HttpClient.get_json = original_func self.assertTrue(isinstance(result, Forecaster)) forecast = result.forecast @@ -377,23 +383,27 @@ def test_daily_forecast_at_coords(self): for weather in forecast: self.assertTrue(isinstance(weather, Weather)) - def test_daily_forecast_at_coords_fails_with_wrong_parameters(self): - self.assertRaises(ValueError, WeatherManager.daily_forecast_at_coords, - self.__test_instance, 51.50853, -0.12574, -3) - self.assertRaises(ValueError, WeatherManager.daily_forecast_at_coords, - self.__test_instance, -100.0, 0.0) - self.assertRaises(ValueError, WeatherManager.daily_forecast_at_coords, - self.__test_instance, 100.0, 0.0) - self.assertRaises(ValueError, WeatherManager.daily_forecast_at_coords, - self.__test_instance, 0.0, -200.0) - self.assertRaises(ValueError, WeatherManager.daily_forecast_at_coords, - self.__test_instance, 0.0, 200.0) - - def test_daily_forecast_at_coords_when_forecast_not_found(self): + def test_forecast_at_coords_daily_fails_with_wrong_parameters(self): + self.assertRaises(ValueError, WeatherManager.forecast_at_coords, + self.__test_instance, -100.0, 0.0, 'daily') + self.assertRaises(ValueError, WeatherManager.forecast_at_coords, + self.__test_instance, 100.0, 0.0, 'daily') + self.assertRaises(ValueError, WeatherManager.forecast_at_coords, + self.__test_instance, 0.0, -200.0, 'daily') + self.assertRaises(ValueError, WeatherManager.forecast_at_coords, + self.__test_instance, 0.0, 200.0, 'daily') + self.assertRaises(AssertionError, WeatherManager.forecast_at_coords, + self.__test_instance, 0.0, 60.0, None, 2) + self.assertRaises(ValueError, WeatherManager.forecast_at_coords, + self.__test_instance, 0.0, 60.0, 'unsupported') + self.assertRaises(ValueError, WeatherManager.forecast_at_coords, + self.__test_instance, 0.0, 60.0, 'daily', -5) + + def test_forecast_at_coords_dailty_when_forecast_not_found(self): original_func = HttpClient.get_json HttpClient.get_json = \ self.mock_api_call_returning_empty_daily_forecast - result = self.__test_instance.daily_forecast_at_coords(51.50853, -0.12574) + result = self.__test_instance.forecast_at_coords(51.50853, -0.12574, 'daily') HttpClient.get_json = original_func self.assertIsNone(result)