Skip to content

Commit

Permalink
New feature and tests: owm.uvindex_forecast_around_coords
Browse files Browse the repository at this point in the history
  • Loading branch information
csparpa committed Apr 18, 2018
1 parent a3afa34 commit be3e4f0
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 21 deletions.
22 changes: 20 additions & 2 deletions pyowm/commons/uv_client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pyowm.webapi25.configuration25 import UV_INDEX_URL
from pyowm.webapi25.configuration25 import UV_INDEX_URL, UV_INDEX_FORECAST_URL
from pyowm.commons import http_client


Expand Down Expand Up @@ -35,7 +35,6 @@ def _trim_to(self, date_object, interval):
raise ValueError("The interval provided for UVIndex search "
"window is invalid")


def get_uvi(self, params_dict):
"""
Invokes the UV Index endpoint
Expand All @@ -54,6 +53,25 @@ def get_uvi(self, params_dict):
_, json_data = self._client.cacheable_get_json(uri, params=params)
return json_data

def get_uvi_forecast(self, params_dict):
"""
Invokes the UV Index Forecast endpoint
:param params_dict: dict of parameters
:returns: a string containing raw JSON data
:raises: *ValueError*, *APICallError*
"""
lat = str(params_dict['lat'])
lon = str(params_dict['lon'])
params = dict(lat=lat, lon=lon)

# build request URL
uri = http_client.HttpClient.to_url(UV_INDEX_FORECAST_URL,
self._API_key,
None)
_, json_data = self._client.cacheable_get_json(uri, params=params)
return json_data

def __repr__(self):
return "<%s.%s - httpclient=%s>" % \
Expand Down
6 changes: 5 additions & 1 deletion pyowm/docs/air-pollution-api-usage-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,8 @@ oz.get_reception_time()
```

If you want to know if an Ozone measurement refers to the future - aka: is a forecast - wth respect to the
current timestamp, then use the `is_forecast()` method
current timestamp, then use the `is_forecast()` method


### Querying Nitrogen dioxide (NO2) and Sulfur Dioxide (SO2) data
This works exactly as for O2 adata - please refer to that bit of the docs
30 changes: 28 additions & 2 deletions pyowm/docs/uv-api-usage-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,44 @@ Please refer to the official API docs for [UV](http://openweathermap.org/api/uvi

Getting the data is easy:

```
```python
uvi = owm.uvindex_around_coords(lat, lon)
```

The query returns an UV Index value entity instance


### Querying UV index forecasts

As easy as:

```python
uvi_list = owm.uvindex_forecast_around_coords(lat, lon)
```

### Querying UV index history

As easy as:

```python
uvi_history_list = owm.uvindex_history_around_coords(
lat, lon,
datetime.datetime(2017, 8, 1, 0, 0),
end=datetime.datetime(2018, 2, 15, 0, 0))
```

`start` and `end` can be ISO-8601 date strings, unix timestamps or Python datetime
objects.

In case `end` is not provided, then UV historical values will be retrieved
dating back to `start` up to the current timestamp.


### `UVIndex` entity
`UVIndex` is an entity representing a UV intensity measurement on a certain geopoint.
Here are some of the methods:

```
```python
uvi.get_value()
uvi.get_reference_time()
uvi.get_reception_time()
Expand Down
7 changes: 5 additions & 2 deletions pyowm/webapi25/configuration25.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from pyowm.caches import nullcache
from pyowm.webapi25 import observationparser, observationlistparser, \
forecastparser, weatherhistoryparser, stationparser, stationlistparser, \
stationhistoryparser, uvindexparser, coindexparser, weathercoderegistry,\
cityidregistry, ozone_parser, no2indexparser, so2indexparser
stationhistoryparser, uvindexparser, uvindexlistparser, coindexparser, \
weathercoderegistry, cityidregistry, ozone_parser, no2indexparser, \
so2indexparser

"""
Configuration for the PyOWM library specific to OWM web API version 2.5
Expand Down Expand Up @@ -36,6 +37,7 @@
# OWM UV web API URLs
ROOT_UV_API_URL = 'http://api.openweathermap.org/data/2.5'
UV_INDEX_URL = ROOT_UV_API_URL + '/uvi'
UV_INDEX_FORECAST_URL = ROOT_UV_API_URL + '/uvi/forecast'

# OWM Air Pollution API URLs
ROOT_POLLUTION_API_URL = 'http://api.openweathermap.org/pollution/v1'
Expand All @@ -55,6 +57,7 @@
'station': stationparser.StationParser(),
'station_list': stationlistparser.StationListParser(),
'uvindex': uvindexparser.UVIndexParser(),
'uvindex_list': uvindexlistparser.UVIndexListParser(),
'coindex': coindexparser.COIndexParser(),
'ozone': ozone_parser.OzoneParser(),
'no2index': no2indexparser.NO2IndexParser(),
Expand Down
60 changes: 55 additions & 5 deletions pyowm/webapi25/owm25.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ def is_API_online(self):
except api_call_error.APICallTimeoutError:
return False

# --- WEATHER API ENDPOINTS ---

def weather_at_place(self, name):
"""
Queries the OWM web API for the currently observed weather at the
Expand Down Expand Up @@ -1041,18 +1043,14 @@ def _retrieve_station_history(self, station_ID, limit, interval):
station_history.set_interval(interval)
return station_history

# --- POLLUTION ENDPOINTS ---
# --- UV API ENDPOINTS ---

def uvindex_around_coords(self, lat, lon):
"""
Queries the OWM web API for Ultra Violet value sampled in the
surroundings of the provided geocoordinates and in the specified time
interval. A *UVIndex* object instance is returned, encapsulating a
*Location* object and the UV intensity value.
If `start` is not provided, the latest available UVIndex value is
retrieved.
If `start` is provided but `interval` is not, then `interval` defaults
to the maximum extent, which is: `year`
:param lat: the location's latitude, must be between -90.0 and 90.0
:type lat: int/float
Expand All @@ -1075,6 +1073,58 @@ def uvindex_around_coords(self, lat, lon):
uvindex = self._parsers['uvindex'].parse_JSON(json_data)
return uvindex

def uvindex_forecast_around_coords(self, lat, lon):
"""
Queries the OWM web API for forecast Ultra Violet values in the next 8
days in the surroundings of the provided geocoordinates.
:param lat: the location's latitude, must be between -90.0 and 90.0
:type lat: int/float
:param lon: the location's longitude, must be between -180.0 and 180.0
:type lon: int/float
:return: a list of *UVIndex* instances or empty list if data is not available
:raises: *ParseResponseException* when OWM web API responses' data
cannot be parsed, *APICallException* when OWM web API can not be
reached, *ValueError* for wrong input values
"""
assert type(lon) is float or type(lon) is int, "'lon' must be a float"
if lon < -180.0 or lon > 180.0:
raise ValueError("'lon' value must be between -180 and 180")
assert type(lat) is float or type(lat) is int, "'lat' must be a float"
if lat < -90.0 or lat > 90.0:
raise ValueError("'lat' value must be between -90 and 90")

params = {'lon': lon, 'lat': lat}
json_data = self._uvapi.get_uvi_forecast(params)
uvindex_list = self._parsers['uvindex_list'].parse_JSON(json_data)
return uvindex_list

def uvindex_history_around_coords(self, lat, lon, start, end=None):
"""
Queries the OWM web API for UV index historical values in the
surroundings of the provided geocoordinates and in the specified
time frame. If the end of the time frame is not provided, that is
intended to be the current datetime.
:param lat: the location's latitude, must be between -90.0 and 90.0
:type lat: int/float
:param lon: the location's longitude, must be between -180.0 and 180.0
:type lon: int/float
:param start: the object conveying the time value for the start query boundary
:type start: int, ``datetime.datetime`` or ISO8601-formatted string
:param end: the object conveying the time value for the end query
boundary (defaults to ``None``, in which case the current datetime
will be used)
:type end: int, ``datetime.datetime`` or ISO8601-formatted string
:return: a list of *UVIndex* instances or empty list if data is not available
:raises: *ParseResponseException* when OWM web API responses' data
cannot be parsed, *APICallException* when OWM web API can not be
reached, *ValueError* for wrong input values
"""
raise NotImplementedError()

# --- POLLUTION API ENDPOINTS ---

def coindex_around_coords(self, lat, lon, start=None, interval=None):
"""
Queries the OWM web API for Carbon Monoxide values sampled in the
Expand Down
15 changes: 13 additions & 2 deletions tests/integration/webapi25/test_integration_webapi25.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

class IntegrationTestsWebAPI25(unittest.TestCase):

__owm = OWM25(parsers, os.getenv('OWM_API_KEY', DEFAULT_API_KEY))
__owm = OWM25(parsers, os.getenv('OWM_API_KEY', '0f7d1d702ba2d055ab90a14bd7799090'))

def test_is_API_online(self):
self.assertTrue(self.__owm.is_API_online())
Expand Down Expand Up @@ -543,12 +543,23 @@ def test_uvindex_around_coords(self):
"""
Test feature: get UV index around geo-coordinates.
"""
u = self.__owm.uvindex_around_coords(45,9)
u = self.__owm.uvindex_around_coords(45, 9)
self.assertIsNotNone(u)
self.assertIsNotNone(u.get_value())
self.assertIsNotNone(u.get_reception_time())
self.assertIsNotNone(u.get_location())

def test_uvindex_forecast_around_coords(self):
"""
Test feature: get UV index forecast around geo-coordinates.
"""
uv_list = self.__owm.uvindex_forecast_around_coords(45, 9)
self.assertIsInstance(uv_list, list)
for item in uv_list:
self.assertIsNotNone(item.get_value())
self.assertIsNotNone(item.get_reception_time())
self.assertIsNotNone(item.get_location())

def test_coindex_around_coords(self):
"""
Test feature: get CO index around geo-coordinates.
Expand Down
19 changes: 16 additions & 3 deletions tests/unit/commons/test_uv_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ def test_trim_to(self):
ts, 'abcdef')

def test_get_uvi(self):

# case: current UV index
params = {'lon': '8.25', 'lat': '43.75'}

Expand All @@ -42,5 +41,19 @@ def mock_func(uri, params=None, headers=None):
self.__instance._client.cacheable_get_json = mock_func

result = self.__instance.get_uvi(params)
self.assertEqual('http://api.openweathermap.org/data/2.5/uvi?APPID=xyz', result[0])
self.assertEqual(params, result[1])
self.assertEqual('http://api.openweathermap.org/data/2.5/uvi?APPID=xyz',
result[0])
self.assertEqual(params, result[1])

def test_get_uvi_forecast(self):
params = {'lon': '8.25', 'lat': '43.75'}

def mock_func(uri, params=None, headers=None):
return 200, (uri, params)

self.__instance._client.cacheable_get_json = mock_func

result = self.__instance.get_uvi_forecast(params)
self.assertEqual('http://api.openweathermap.org/data/2.5/uvi/forecast?APPID=xyz',
result[0])
self.assertEqual(params, result[1])
33 changes: 29 additions & 4 deletions tests/unit/webapi25/test_owm25.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

import unittest
import time
import sys
from tests.unit.webapi25.json_test_responses import (OBSERVATION_JSON,
SEARCH_RESULTS_JSON, THREE_HOURS_FORECAST_JSON, DAILY_FORECAST_JSON,
THREE_HOURS_FORECAST_AT_COORDS_JSON, DAILY_FORECAST_AT_COORDS_JSON,
Expand All @@ -23,8 +22,8 @@
STATION_WEATHER_HISTORY_JSON, THREE_HOURS_FORECAST_NOT_FOUND_JSON,
DAILY_FORECAST_NOT_FOUND_JSON, STATION_HISTORY_NO_ITEMS_JSON,
STATION_OBSERVATION_JSON, STATION_AT_COORDS_JSON,
WEATHER_AT_STATION_IN_BBOX_JSON, UVINDEX_JSON, COINDEX_JSON, OZONE_JSON,
NO2INDEX_JSON, SO2INDEX_JSON)
WEATHER_AT_STATION_IN_BBOX_JSON, UVINDEX_JSON, UVINDEX_LIST_JSON,
COINDEX_JSON, OZONE_JSON, NO2INDEX_JSON, SO2INDEX_JSON)
from pyowm.webapi25.owm25 import OWM25
from pyowm.constants import PYOWM_VERSION
from pyowm.commons.http_client import HttpClient
Expand Down Expand Up @@ -52,6 +51,7 @@
from pyowm.webapi25.stationhistoryparser import StationHistoryParser
from pyowm.webapi25.weatherhistoryparser import WeatherHistoryParser
from pyowm.webapi25.uvindexparser import UVIndexParser
from pyowm.webapi25.uvindexlistparser import UVIndexListParser
from pyowm.webapi25.coindexparser import COIndexParser
from pyowm.webapi25.ozone_parser import OzoneParser
from pyowm.webapi25.no2indexparser import NO2IndexParser
Expand All @@ -69,6 +69,7 @@ class TestOWM25(unittest.TestCase):
'station': StationParser(),
'station_list': StationListParser(),
'uvindex': UVIndexParser(),
'uvindex_list': UVIndexListParser(),
'coindex': COIndexParser(),
'ozone': OzoneParser(),
'no2index': NO2IndexParser(),
Expand Down Expand Up @@ -143,6 +144,9 @@ def mock_api_call_returning_weather_history_at_coords(self, uri, params=None, he
def mock_get_uvi_returning_uvindex_around_coords(self, params_dict):
return UVINDEX_JSON

def mock_get_uvi_forecast(self, params_dict):
return UVINDEX_LIST_JSON

def mock_get_coi_returning_coindex_around_coords(self, params_dict):
return COINDEX_JSON

Expand Down Expand Up @@ -809,7 +813,7 @@ def test_station_at_coords(self):
self.assertTrue(isinstance(result.get_station_type(), int))
self.assertTrue(isinstance(result.get_status(), int))

# ---- Pollution API methods tests ---
# ---- UltraViolet API methods tests ---

def test_uvindex_around_coords(self):
ref_to_original = UltraVioletHttpClient.get_uvi
Expand All @@ -836,6 +840,27 @@ def test_uvindex_around_coords_fails_with_wrong_parameters(self):
self.assertRaises(ValueError, OWM25.uvindex_around_coords, \
self.__test_instance, 200, 2.5)

def test_uvindex_forecast_around_coords(self):
ref_to_original = UltraVioletHttpClient.get_uvi_forecast
UltraVioletHttpClient.get_uvi_forecast = \
self.mock_get_uvi_forecast
result = self.__test_instance.uvindex_forecast_around_coords(45, 9)
UltraVioletHttpClient.get_uvi_forecast = ref_to_original
self.assertTrue(isinstance(result, list))
self.assertTrue(all([isinstance(i, UVIndex) for i in result]))

def test_uvindex_forecast_around_coords_fails_with_wrong_parameters(self):
self.assertRaises(ValueError, OWM25.uvindex_forecast_around_coords, \
self.__test_instance, 43.7, -200.0)
self.assertRaises(ValueError, OWM25.uvindex_forecast_around_coords, \
self.__test_instance, 43.7, 200.0)
self.assertRaises(ValueError, OWM25.uvindex_forecast_around_coords, \
self.__test_instance, -200, 2.5)
self.assertRaises(ValueError, OWM25.uvindex_forecast_around_coords, \
self.__test_instance, 200, 2.5)

# ---- Pollution API methods tests ---

def test_coindex_around_coords(self):
ref_to_original = AirPollutionHttpClient.get_coi
AirPollutionHttpClient.get_coi = \
Expand Down

0 comments on commit be3e4f0

Please sign in to comment.