Beispiel #1
0
    def cacheable_get_json(self):

        cached_data = '{"name": "james bond", "designation": "007"}'
        other_data = '{"name": "doctor no"}'

        def monkey_patched_get(uri, params=None, headers=None, timeout=None,
                               verify=False):
            return MockResponse(200, other_data)
        requests.get = monkey_patched_get

        # cache hit
        cache = MockCache(cached_data)
        instance = HttpClient(cache=cache)
        status, data = instance.cacheable_get_json('http://anyurl.com')
        self.assertEqual(200, status)
        self.assertEqual(cached_data, data)

        # cache miss
        cache = MockCache(None)
        instance = HttpClient(cache=cache)
        status, data = instance.cacheable_get_json('http://anyurl.com')
        self.assertEqual(200, status)
        self.assertEqual(other_data, data)

        requests.get = self.requests_original_get
 def __init__(self, API_key, map_layer):
     assert API_key is not None, 'You must provide a valid API Key'
     self.API_key = API_key
     assert map_layer is not None, 'You must provide a valid map layer name'
     assert isinstance(map_layer, str), 'Map layer name must be a string'
     self.map_layer = map_layer
     self.http_client = HttpClient()
Beispiel #3
0
    def test_delete(self):
        # in case an empty payload is returned
        def monkey_patched_delete(uri,
                                  params=None,
                                  headers=None,
                                  proxies=None,
                                  json=None,
                                  timeout=None,
                                  verify=False):
            return MockResponse(204, None)

        requests.delete = monkey_patched_delete
        status, data = HttpClient('apikey', DEFAULT_CONFIG,
                                  'anyurl.com').delete('/resource')
        self.assertIsNone(data)

        # in case a non-empty payload is returned
        expected_data = '{"message": "deleted"}'

        def monkey_patched_delete_returning_payload(uri,
                                                    params=None,
                                                    headers=None,
                                                    proxies=None,
                                                    json=None,
                                                    timeout=None,
                                                    verify=False):
            return MockResponse(204, expected_data)

        requests.delete = monkey_patched_delete_returning_payload
        status, data = HttpClient('apikey', DEFAULT_CONFIG,
                                  'anyurl.com').delete('/resource')
        self.assertEqual(json.loads(expected_data), data)

        requests.delete = self.requests_original_delete
Beispiel #4
0
    def test_escape_subdomain(self):
        # correct subscription type
        API_endpoint = 'http://%s.openweathermap.org/data/2.5/ep'
        api_subscription = 'free'
        expected = 'http://api.openweathermap.org/data/2.5/ep'
        result = HttpClient._escape_subdomain(API_endpoint, api_subscription)
        self.assertEqual(expected, result)

        # non-existing subscription type
        API_endpoint = 'http://%s.openweathermap.org/data/2.5/ep'
        try:
            HttpClient._escape_subdomain(API_endpoint, 'unexistent')
            self.fail()
        except ValueError:
            pass

        # no subdomains needed
        API_endpoint = 'http://test.com'
        result = HttpClient._escape_subdomain(API_endpoint, api_subscription)
        self.assertEqual(API_endpoint, result)

        # subscription type is None
        API_endpoint = 'http://test.com'
        result = HttpClient._escape_subdomain(API_endpoint, None)
        self.assertEqual(API_endpoint, result)
Beispiel #5
0
    def test_escape_subdomain(self):
        # correct subscription type
        API_endpoint = 'http://%s.openweathermap.org/data/2.5/ep'
        api_subscription = 'free'
        expected = 'http://api.openweathermap.org/data/2.5/ep'
        result = HttpClient._escape_subdomain(API_endpoint, api_subscription)
        self.assertEqual(expected, result)

        # non-existing subscription type
        API_endpoint = 'http://%s.openweathermap.org/data/2.5/ep'
        try:
            HttpClient._escape_subdomain(API_endpoint, 'unexistent')
            self.fail()
        except ValueError:
            pass

        # no subdomains needed
        API_endpoint = 'http://test.com'
        result = HttpClient._escape_subdomain(API_endpoint, api_subscription)
        self.assertEqual(API_endpoint, result)

        # subscription type is None
        API_endpoint = 'http://test.com'
        result = HttpClient._escape_subdomain(API_endpoint, None)
        self.assertEqual(API_endpoint, result)
 def __init__(self, API_key, config):
     assert API_key is not None, 'You must provide a valid API Key'
     self.API_key = API_key
     assert isinstance(config, dict)
     self.ap_client = airpollution_client.AirPollutionHttpClient(
         API_key, HttpClient(API_key, config, ROOT_POLLUTION_API_URL))
     self.new_ap_client = airpollution_client.AirPollutionHttpClient(
         API_key, HttpClient(API_key, config, NEW_ROOT_POLLUTION_API_URL))
 def __init__(self, API_key, map_layer, config):
     assert API_key is not None, 'You must provide a valid API Key'
     self.API_key = API_key
     assert map_layer is not None, 'You must provide a valid map layer name'
     assert isinstance(map_layer, str), 'Map layer name must be a string'
     self.map_layer = map_layer
     assert isinstance(config, dict)
     self.http_client = HttpClient(API_key,
                                   config,
                                   ROOT_TILE_URL,
                                   admits_subdomains=False)
 def test_get_geotiff(self):
     # https://download.osgeo.org/geotiff/samples/made_up/bogota.tif
     config = DEFAULT_CONFIG.copy()
     config['connection']['use_ssl'] = True
     instance = HttpClient('fakeapikey',
                           config,
                           'download.osgeo.org',
                           admits_subdomains=False)
     status, data = instance.get_geotiff(
         'geotiff/samples/made_up/bogota.tif')
     self.assertIsNotNone(data)
     self.assertIsInstance(data, bytes)
Beispiel #9
0
 def test_to_url_with_unicode_chars_in_API_key(self):
     API_key = '£°test££'
     API_endpoint = 'http://%s.openweathermap.org/data/2.5/ep'
     api_subscription = 'free'
     result = HttpClient.to_url(API_endpoint, API_key, api_subscription)
     expected = 'http://api.openweathermap.org/data/2.5/ep?APPID=%C2%A3%C2%B0test%C2%A3%C2%A3'
     self.assertEqual(expected, result)
Beispiel #10
0
 def test_to_url_with_no_API_key(self):
     API_endpoint = 'http://%s.openweathermap.org/data/2.5/ep'
     params = {'a': 1, 'b': 2}
     api_subscription = 'pro'
     result = HttpClient.to_url(API_endpoint, None, api_subscription)
     expected = 'http://pro.openweathermap.org/data/2.5/ep'
     self.assertEqual(expected, result)
Beispiel #11
0
 def test_to_url(self):
     API_endpoint = 'http://%s.openweathermap.org/data/2.5/ep'
     API_key = 'test_API_key'
     api_subscription = 'free'
     result = HttpClient.to_url(API_endpoint, API_key, api_subscription)
     expected = 'http://api.openweathermap.org/data/2.5/ep?APPID=test_API_key'
     self.assertEqual(expected, expected)
Beispiel #12
0
 def __init__(self, API_key, map_layer):
     assert API_key is not None, 'You must provide a valid API Key'
     self.API_key = API_key
     assert map_layer is not None, 'You must provide a valid map layer name'
     assert isinstance(map_layer, str), 'Map layer name must be a string'
     self.map_layer = map_layer
     self.http_client = HttpClient()
Beispiel #13
0
 def test_to_url(self):
     API_endpoint = 'http://%s.openweathermap.org/data/2.5/ep'
     API_key = 'test_API_key'
     api_subscription = 'free'
     result = HttpClient.to_url(API_endpoint, API_key, api_subscription)
     expected = 'http://api.openweathermap.org/data/2.5/ep?APPID=test_API_key'
     self.assertEqual(expected, expected)
Beispiel #14
0
 def test_to_url_with_no_API_key(self):
     API_endpoint = 'http://%s.openweathermap.org/data/2.5/ep'
     params = {'a': 1, 'b': 2}
     api_subscription = 'pro'
     result = HttpClient.to_url(API_endpoint, None, api_subscription)
     expected = 'http://pro.openweathermap.org/data/2.5/ep'
     self.assertEqual(expected, result)
Beispiel #15
0
 def test_to_url_with_unicode_chars_in_API_key(self):
     API_key = '£°test££'
     API_endpoint = 'http://%s.openweathermap.org/data/2.5/ep'
     api_subscription = 'free'
     result = HttpClient.to_url(API_endpoint, API_key, api_subscription)
     expected = 'http://api.openweathermap.org/data/2.5/ep?APPID=%C2%A3%C2%B0test%C2%A3%C2%A3'
     self.assertEqual(expected, result)
    def test_get_json_parse_error(self):

        def monkey_patched_get(uri, params=None, headers=None, proxies=None, timeout=None, verify=False):
            return MockResponse(200, 123846237647236)

        requests.get = monkey_patched_get
        self.assertRaises(pyowm.commons.exceptions.ParseAPIResponseError,
                          HttpClient('apikey', DEFAULT_CONFIG, 'anyurl.com').get_json, '/resource', params=dict(a=1, b=2))
        requests.get = self.requests_original_get
    def test_put(self):
        expected_data = '{"key": "value"}'

        def monkey_patched_put(uri, params=None, headers=None, proxies=None, json=None, timeout=None, verify=False):
            return MockResponse(200, expected_data)

        requests.put = monkey_patched_put
        status, data = HttpClient('apikey', DEFAULT_CONFIG, 'anyurl.com').put('/resource', data=dict(key=7))
        self.assertEqual(json.loads(expected_data), data)
        requests.put = self.requests_original_put
Beispiel #18
0
    def test_get_json_parse_error(self):
        def monkey_patched_get(uri, params=None, headers=None):
            return MockResponse(200, 123846237647236)

        requests.get = monkey_patched_get
        self.assertRaises(parse_response_error.ParseResponseError,
                          HttpClient().get_json,
                          'http://anyurl.com',
                          params=dict(a=1, b=2))
        requests.get = self.requests_original_get
Beispiel #19
0
    def test_get_json(self):

        expected_data = '{"name": "james bond", "designation": "007"}'

        def monkey_patched_get(uri, params=None, headers=None):
            return MockResponse(200, expected_data)

        requests.get = monkey_patched_get
        status, data = HttpClient().get_json('http://anyurl.com')
        self.assertEquals(json.loads(expected_data), data)
        requests.get = self.requests_original_get
    def test_get_json(self):

        expected_data = '{"name": "james bond", "designation": "007"}'

        def monkey_patched_get(uri, params=None, headers=None, proxies=None, timeout=None, verify=False):
            return MockResponse(200, expected_data)

        requests.get = monkey_patched_get
        status, data = HttpClient('apikey', DEFAULT_CONFIG, 'anyurl.com').get_json('/resource')
        self.assertEqual(json.loads(expected_data), data)
        requests.get = self.requests_original_get
    def test_get_geotiff(self):
        expected_data = b'II*\x00\x08\x00\x04\x00k{\x84s\x84\x84\x8c\x84\x84\x84k\x84k\x84\x84k{s\x9c\x94k\x84'

        def monkey_patched_get(uri, stream=True, params=None, headers=None, proxies=None, timeout=None, verify=False):
            return MockResponse(200, expected_data)

        requests.get = monkey_patched_get
        status, data = HttpClient('apikey', DEFAULT_CONFIG, 'anyurl.com').get_geotiff('/resource')
        self.assertIsInstance(data, bytes)
        self.assertEqual(expected_data, data)
        requests.get = self.requests_original_get
    def test_get_png(self):
        expected_data = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x01\x03\x00\x00\x00%\xdbV\xca\x00\x00\x00\x03PLTE\x00p\xff\xa5G\xab\xa1\x00\x00\x00\x01tRNS\xcc\xd24V\xfd\x00\x00\x00\nIDATx\x9ccb\x00\x00\x00\x06\x00\x0367|\xa8\x00\x00\x00\x00IEND\xaeB`\x82'

        def monkey_patched_get(uri, stream=True, params=None, headers=None, proxies=None, timeout=None, verify=False):
            return MockResponse(200, expected_data)

        requests.get = monkey_patched_get
        status, data = HttpClient('apikey', DEFAULT_CONFIG, 'anyurl.com').get_png('/resource')
        self.assertIsInstance(data, bytes)
        self.assertEqual(expected_data, data)
        requests.get = self.requests_original_get
Beispiel #23
0
    def test_put(self):
        expected_data = '{"key": "value"}'

        def monkey_patched_put(uri, params=None, headers=None, json=None):
            return MockResponse(200, expected_data)

        requests.put = monkey_patched_put
        status, data = HttpClient().put('http://anyurl.com', data=dict(key=7))
        self.assertEquals(json.loads(expected_data), data)

        requests.put = self.requests_original_put
 def test_ssl_certs_verification_failure(self):
     # https://wrong.host.badssl.com does not have a valid SSL cert
     config = DEFAULT_CONFIG.copy()
     config['connection']['use_ssl'] = True
     config['connection']['verify_ssl_certs'] = True
     instance = HttpClient('fakeapikey',
                           config,
                           'wrong.host.badssl.com',
                           admits_subdomains=False)
     self.assertRaises(pyowm.commons.exceptions.InvalidSSLCertificateError,
                       HttpClient.get_json, instance, '')
Beispiel #25
0
 def test_is_success(self):
     self.assertTrue(HttpClient.is_success(200))
     self.assertTrue(HttpClient.is_success(201))
     self.assertTrue(HttpClient.is_success(299))
     self.assertFalse(HttpClient.is_success(300))
     self.assertFalse(HttpClient.is_success(400))
     self.assertFalse(HttpClient.is_success(500))
Beispiel #26
0
 def test_is_success(self):
     self.assertTrue(HttpClient.is_success(200))
     self.assertTrue(HttpClient.is_success(201))
     self.assertTrue(HttpClient.is_success(299))
     self.assertFalse(HttpClient.is_success(300))
     self.assertFalse(HttpClient.is_success(400))
     self.assertFalse(HttpClient.is_success(500))
Beispiel #27
0
    def cacheable_get_json(self):

        cached_data = '{"name": "james bond", "designation": "007"}'
        other_data = '{"name": "doctor no"}'

        def monkey_patched_get(uri,
                               params=None,
                               headers=None,
                               timeout=None,
                               verify=False):
            return MockResponse(200, other_data)

        requests.get = monkey_patched_get

        # cache hit
        cache = MockCache(cached_data)
        instance = HttpClient(cache=cache)
        status, data = instance.cacheable_get_json('http://anyurl.com')
        self.assertEqual(200, status)
        self.assertEqual(cached_data, data)

        # cache miss
        cache = MockCache(None)
        instance = HttpClient(cache=cache)
        status, data = instance.cacheable_get_json('http://anyurl.com')
        self.assertEqual(200, status)
        self.assertEqual(other_data, data)

        requests.get = self.requests_original_get
Beispiel #28
0
    def test_delete(self):
        # in case an empty payload is returned
        def monkey_patched_delete(uri, params=None, headers=None, json=None):
            return MockResponse(204, None)

        requests.delete = monkey_patched_delete
        status, data = HttpClient().delete('http://anyurl.com')
        self.assertIsNone(data)

        # in case a non-empty payload is returned
        expected_data = '{"message": "deleted"}'

        def monkey_patched_delete_returning_payload(uri,
                                                    params=None,
                                                    headers=None,
                                                    json=None):
            return MockResponse(204, expected_data)

        requests.delete = monkey_patched_delete_returning_payload
        status, data = HttpClient().delete('http://anyurl.com')
        self.assertEquals(json.loads(expected_data), data)

        requests.delete = self.requests_original_delete
class TileManager:
    """
    A manager objects that reads OWM map layers tile images .

    :param API_key: the OWM Weather API key
    :type API_key: str
    :param map_layer: the layer for which you want tiles fetched. Allowed map layers are specified by
        the `pyowm.tiles.enum.MapLayerEnum` enumerator class.
    :type map_layer: str
    :param config: the configuration dictionary
    :type config: dict
    :returns: a *TileManager* instance
    :raises: *AssertionError* when no API Key or no map layer is provided, or map layer name is not a string

    """
    def __init__(self, API_key, map_layer, config):
        assert API_key is not None, 'You must provide a valid API Key'
        self.API_key = API_key
        assert map_layer is not None, 'You must provide a valid map layer name'
        assert isinstance(map_layer, str), 'Map layer name must be a string'
        self.map_layer = map_layer
        assert isinstance(config, dict)
        self.http_client = HttpClient(API_key,
                                      config,
                                      ROOT_TILE_URL,
                                      admits_subdomains=False)

    def get_tile(self, x, y, zoom):
        """
        Retrieves the tile having the specified coordinates and zoom level

        :param x: horizontal tile number in OWM tile reference system
        :type x: int
        :param y: vertical tile number in OWM tile reference system
        :type y: int
        :param zoom: zoom level for the tile
        :type zoom: int
        :returns: a `pyowm.tiles.Tile` instance

        """
        status, data = self.http_client.get_png(
            NAMED_MAP_LAYER_URL % self.map_layer + '/%s/%s/%s.png' %
            (zoom, x, y),
            params={'appid': self.API_key})
        img = Image(data, ImageTypeEnum.PNG)
        return Tile(x, y, zoom, self.map_layer, img)

    def __repr__(self):
        return "<%s.%s - layer_name=%s>" % (__name__, self.__class__.__name__,
                                            self.map_layer)
    def test_timeouts(self):
        timeout = 0.5

        def monkey_patched_get_timeouting(uri, params=None, headers=None, proxies=None, timeout=timeout, verify=False):
            raise requests.exceptions.Timeout()

        requests.get = monkey_patched_get_timeouting
        config = DEFAULT_CONFIG.copy()
        config['connection']['timeout_secs'] = timeout
        try:
            status, data = HttpClient('apikey', config, 'anyurl.com').get_json('/resource')
            self.fail()
        except pyowm.commons.exceptions.TimeoutError:
            requests.get = self.requests_original_get
Beispiel #31
0
class TestHTTPClient(unittest.TestCase):

    instance = HttpClient()

    def test_get_json_against_httpbin_ok(self):
        # http://httpbin.org/ip
        status, data = self.instance.get_json('http://httpbin.org/ip')
        self.assertEqual(200, status)
        self.assertIsInstance(data, dict)

    def test_get_json_against_httpbin_status_code_ko(self):
        # http://httpbin.org/status/400
        expected_status = 400

        self.assertRaises(api_call_error.APICallError, HttpClient.get_json,
                          self.instance,
                          'http://httpbin.org/status/' + str(expected_status))

    def test_get_json_against_httpbin_parse_error(self):
        # http://httpbin.org/xml
        try:
            status, data = self.instance.get_json('http://httpbin.org/xml')
            self.fail()
        except parse_response_error.ParseResponseError:
            pass

    def test_put_against_httpbin(self):
        # http://httpbin.org/put
        formdata = dict(a=1, b=2, c=3)
        status, data = self.instance.put('http://httpbin.org/put',
                                         data=formdata)
        self.assertEqual(200, status)
        self.assertIsInstance(data, dict)
        self.assertEqual(formdata, data['json'])

    def test_delete_against_httpbin(self):
        # http://httpbin.org/delete
        formdata = dict(a=1, b=2, c=3)
        status, data = self.instance.delete('http://httpbin.org/delete',
                                            data=formdata)
        self.assertEqual(200, status)
        self.assertIsInstance(data, dict)
        self.assertEqual(formdata, data['json'])

    def test_ssl_certs_verification_failure(self):
        # https://wrong.host.badssl.com does not have a valid SSL cert
        client = HttpClient(timeout=4, use_ssl=True, verify_ssl_certs=True)
        self.assertRaises(api_call_error.APIInvalidSSLCertificateError,
                          HttpClient.get_json, client,
                          'https://wrong.host.badssl.com')
Beispiel #32
0
 def __init__(self, API_key, config):
     assert isinstance(API_key, str), 'You must provide a valid API Key'
     self.API_key = API_key
     assert isinstance(config, dict)
     self.http_client = HttpClient(API_key, config, ROOT_AGRO_API)
     self.geotiff_downloader_http_client = HttpClient(
         self.API_key, config, ROOT_DOWNLOAD_GEOTIFF_API)
     self.png_downloader_http_client = HttpClient(self.API_key, config,
                                                  ROOT_DOWNLOAD_PNG_API)
Beispiel #33
0
    def test_timeouts(self):
        timeout = 0.5

        def monkey_patched_get_timeouting(uri,
                                          params=None,
                                          headers=None,
                                          timeout=timeout,
                                          verify=False):
            raise requests.exceptions.Timeout()

        requests.get = monkey_patched_get_timeouting
        try:
            status, data = HttpClient(
                timeout=timeout).get_json('http://anyurl.com')
            self.fail()
        except api_call_error.APICallTimeoutError:
            requests.get = self.requests_original_get
Beispiel #34
0
class TileManager(object):

    """
    A manager objects that reads OWM map layers tile images .

    :param API_key: the OWM Weather API key
    :type API_key: str
    :param map_layer: the layer for which you want tiles fetched. Allowed map layers are specified by the `pyowm.tiles.enum.MapLayerEnum` enumerator class.
    :type map_layer: str
    :returns: a *TileManager* instance
    :raises: *AssertionError* when no API Key or no map layer is provided, or map layer name is not a string

    """

    def __init__(self, API_key, map_layer):
        assert API_key is not None, 'You must provide a valid API Key'
        self.API_key = API_key
        assert map_layer is not None, 'You must provide a valid map layer name'
        assert isinstance(map_layer, str), 'Map layer name must be a string'
        self.map_layer = map_layer
        self.http_client = HttpClient()

    def get_tile(self, x, y, zoom):
        """
        Retrieves the tile having the specified coordinates and zoom level

        :param x: horizontal tile number in OWM tile reference system
        :type x: int
        :param y: vertical tile number in OWM tile reference system
        :type y: int
        :param zoom: zoom level for the tile
        :type zoom: int
        :returns: a `pyowm.tiles.Tile` instance

        """
        status, data = self.http_client.get_png(
            ROOT_TILE_URL % self.map_layer + '/%s/%s/%s.png' % (zoom, x, y),
            params={'appid': self.API_key})
        img = Image(data, ImageTypeEnum.PNG)
        return Tile(x, y, zoom, self.map_layer, img)

    def __repr__(self):
        return "<%s.%s - layer name=%s>" % (__name__, self.__class__.__name__, self.map_layer)
Beispiel #35
0
class TestHTTPClient(unittest.TestCase):

    instance = HttpClient()

    def test_get_json_against_httpbin_ok(self):
        # https://httpbin.org/ip
        status, data = self.instance.get_json('http://httpbin.org/ip')
        self.assertEqual(200, status)
        self.assertIsInstance(data, dict)

    def test_get_json_against_httpbin_status_code_ko(self):
        # https://httpbin.org/status/400
        expected_status = 400

        self.assertRaises(api_call_error.APICallError, HttpClient.get_json,
                          self.instance, 'https://httpbin.org/status/' +
                                              str(expected_status))

    def test_get_json_against_httpbin_parse_error(self):
        # https://httpbin.org/xml
        try:
            status, data = self.instance.get_json('http://httpbin.org/xml')
            self.fail()
        except parse_response_error.ParseResponseError:
            pass

    def test_put_against_httpbin(self):
        # https://httpbin.org/put
        formdata = dict(a=1, b=2, c=3)
        status, data = self.instance.put('http://httpbin.org/put', data=formdata)
        self.assertEqual(200, status)
        self.assertIsInstance(data, dict)
        self.assertEquals(formdata, data['json'])

    def test_delete_against_httpbin(self):
        # https://httpbin.org/delete
        formdata = dict(a=1, b=2, c=3)
        status, data = self.instance.delete('http://httpbin.org/delete', data=formdata)
        self.assertEqual(200, status)
        self.assertIsInstance(data, dict)
        self.assertEquals(formdata, data['json'])
Beispiel #36
0
class TestOWMHttpUVClient(unittest.TestCase):

    __test_cache = NullCache()
    __instance = UltraVioletHttpClient('xyz', HttpClient(cache=__test_cache))

    def test_trim_to(self):
        ts = timeformatutils.to_date(1463041620)  # 2016-05-12T08:27:00Z
        self.assertEqual(self.__instance._trim_to(ts, 'minute'),
                          '2016-05-12T08:27Z')
        self.assertEqual(self.__instance._trim_to(ts, 'hour'),
                          '2016-05-12T08Z')
        self.assertEqual(self.__instance._trim_to(ts, 'day'),
                          '2016-05-12Z')
        self.assertEqual(self.__instance._trim_to(ts, 'month'),
                          '2016-05Z')
        self.assertEqual(self.__instance._trim_to(ts, 'year'),
                          '2016Z')
        self.assertRaises(ValueError, self.__instance._trim_to,
                          ts, 'abcdef')

    def test_get_uvi(self):
        # case: current UV index
        params = {'lon': 8.25, 'lat': 43.75}
        expected = {k: str(v) for k, v in params.items()}

        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(params)
        self.assertEqual('http://api.openweathermap.org/data/2.5/uvi?APPID=xyz',
                         result[0])
        self.assertEqual(expected, result[1])

    def test_get_uvi_forecast(self):
        params = {'lon': 8.25, 'lat': 43.75}
        expected = {k: str(v) for k, v in params.items()}

        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(expected, result[1])

    def test_get_uvi_history(self):
        params = {'lon': 8.25, 'lat': 43.75, 'start': 1498049953,
                  'end': 1498481991}
        expected = {k: str(v) for k, v in params.items()}

        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_history(params)
        self.assertEqual('http://api.openweathermap.org/data/2.5/uvi/history?APPID=xyz',
                         result[0])
        self.assertEqual(expected, result[1])
Beispiel #37
0
 def test_fix_schema_to_https(self):
     API_endpoint = 'http://api.openweathermap.org/data/2.5/ep?APPID=%C2%A3%C2%B0test%C2%A3%C2%A3'
     expected = 'https://api.openweathermap.org/data/2.5/ep?APPID=%C2%A3%C2%B0test%C2%A3%C2%A3'
     result = HttpClient._fix_schema(API_endpoint, True)
     self.assertEqual(expected, result)
Beispiel #38
0
 def test_check_status_code(self):
     msg = 'Generic error'
     HttpClient.check_status_code(200, msg)
     with self.assertRaises(api_call_error.APICallError):
         HttpClient.check_status_code(400, msg)
     with self.assertRaises(api_response_error.UnauthorizedError):
         HttpClient.check_status_code(401, msg)
     with self.assertRaises(api_response_error.NotFoundError):
         HttpClient.check_status_code(404, msg)
     with self.assertRaises(api_call_error.BadGatewayError):
         HttpClient.check_status_code(502, msg)
     with self.assertRaises(api_call_error.APICallError):
         HttpClient.check_status_code(555, msg)
Beispiel #39
0
class AgroManager(object):

    """
    A manager objects that provides a full interface to OWM Agro API.

    :param API_key: the OWM Weather API key
    :type API_key: str
    :returns: an `AgroManager` instance
    :raises: `AssertionError` when no API Key is provided

    """

    def __init__(self, API_key):
        assert API_key is not None, 'You must provide a valid API Key'
        self.API_key = API_key
        self.http_client = HttpClient()

    def agro_api_version(self):
        return AGRO_API_VERSION

    # POLYGON API subset methods

    def create_polygon(self, geopolygon, name=None):
        """
        Create a new polygon on the Agro API with the given parameters

        :param geopolygon: the geopolygon representing the new polygon
        :type geopolygon: `pyowm.utils.geo.Polygon` instance
        :param name: optional mnemonic name for the new polygon
        :type name: str
        :return: a `pyowm.agro10.polygon.Polygon` instance
        """
        assert geopolygon is not None
        assert isinstance(geopolygon, GeoPolygon)
        data = dict()
        data['geo_json'] = {
            "type": "Feature",
            "properties": {},
            "geometry": geopolygon.as_dict()
        }
        if name is not None:
            data['name'] = name
        status, payload = self.http_client.post(
            POLYGONS_URI,
            params={'appid': self.API_key},
            data=data,
            headers={'Content-Type': 'application/json'})
        return Polygon.from_dict(payload)

    def get_polygons(self):
        """
        Retrieves all of the user's polygons registered on the Agro API.

        :returns: list of `pyowm.agro10.polygon.Polygon` objects

        """

        status, data = self.http_client.get_json(
            POLYGONS_URI,
            params={'appid': self.API_key},
            headers={'Content-Type': 'application/json'})
        return [Polygon.from_dict(item) for item in data]

    def get_polygon(self, polygon_id):
        """
        Retrieves a named polygon registered on the Agro API.

        :param id: the ID of the polygon
        :type id: str
        :returns: a `pyowm.agro10.polygon.Polygon` object

        """
        status, data = self.http_client.get_json(
            NAMED_POLYGON_URI % str(polygon_id),
            params={'appid': self.API_key},
            headers={'Content-Type': 'application/json'})
        return Polygon.from_dict(data)

    def update_polygon(self, polygon):
        """
        Updates on the Agro API the Polygon identified by the ID of the provided polygon object.
        Currently this only changes the mnemonic name of the remote polygon

        :param polygon: the `pyowm.agro10.polygon.Polygon` object to be updated
        :type polygon: `pyowm.agro10.polygon.Polygon` instance
        :returns: `None` if update is successful, an exception otherwise
        """
        assert polygon.id is not None
        status, _ = self.http_client.put(
            NAMED_POLYGON_URI % str(polygon.id),
            params={'appid': self.API_key},
            data=dict(name=polygon.name),
            headers={'Content-Type': 'application/json'})

    def delete_polygon(self, polygon):
        """
        Deletes on the Agro API the Polygon identified by the ID of the provided polygon object.

        :param polygon: the `pyowm.agro10.polygon.Polygon` object to be deleted
        :type polygon: `pyowm.agro10.polygon.Polygon` instance
        :returns: `None` if deletion is successful, an exception otherwise
        """
        assert polygon.id is not None
        status, _ = self.http_client.delete(
            NAMED_POLYGON_URI % str(polygon.id),
            params={'appid': self.API_key},
            headers={'Content-Type': 'application/json'})

    # SOIL API subset methods

    def soil_data(self, polygon):
        """
        Retrieves the latest soil data on the specified polygon

        :param polygon: the reference polygon you want soil data for
        :type polygon: `pyowm.agro10.polygon.Polygon` instance
        :returns: a `pyowm.agro10.soil.Soil` instance

        """
        assert polygon is not None
        assert isinstance(polygon, Polygon)
        polyd = polygon.id
        status, data = self.http_client.get_json(
            SOIL_URI,
            params={'appid': self.API_key,
                    'polyid': polyd},
            headers={'Content-Type': 'application/json'})
        the_dict = dict()
        the_dict['reference_time'] = data['dt']
        the_dict['surface_temp'] = data['t0']
        the_dict['ten_cm_temp'] = data['t10']
        the_dict['moisture'] = data['moisture']
        the_dict['polygon_id'] = polyd
        return Soil.from_dict(the_dict)

    # Satellite Imagery subset methods

    def search_satellite_imagery(self, polygon_id, acquired_from, acquired_to, img_type=None, preset=None,
                                 min_resolution=None, max_resolution=None, acquired_by=None, min_cloud_coverage=None,
                                 max_cloud_coverage=None, min_valid_data_coverage=None, max_valid_data_coverage=None):
        """
        Searches on the Agro API the metadata for all available satellite images that contain the specified polygon and
        acquired during the specified time interval; and optionally matching the specified set of filters:
        - image type (eg. GeoTIF)
        - image preset (eg. false color, NDVI, ...)
        - min/max acquisition resolution
        - acquiring satellite
        - min/max cloud coverage on acquired scene
        - min/max valid data coverage on acquired scene

        :param polygon_id: the ID of the reference polygon
        :type polygon_id: str
        :param acquired_from: lower edge of acquisition interval, UNIX timestamp
        :type acquired_from: int
        :param acquired_to: upper edge of acquisition interval, UNIX timestamp
        :type acquired_to: int
        :param img_type: the desired file format type of the images. Allowed values are given by `pyowm.commons.enums.ImageTypeEnum`
        :type img_type: `pyowm.commons.databoxes.ImageType`
        :param preset: the desired preset of the images. Allowed values are given by `pyowm.agroapi10.enums.PresetEnum`
        :type preset: str
        :param min_resolution: minimum resolution for images, px/meters
        :type min_resolution: int
        :param max_resolution: maximum resolution for images, px/meters
        :type max_resolution: int
        :param acquired_by: short symbol of the satellite that acquired the image (eg. "l8")
        :type acquired_by: str
        :param min_cloud_coverage: minimum cloud coverage percentage on acquired images
        :type min_cloud_coverage: int
        :param max_cloud_coverage: maximum cloud coverage percentage on acquired images
        :type max_cloud_coverage: int
        :param min_valid_data_coverage: minimum valid data coverage percentage on acquired images
        :type min_valid_data_coverage: int
        :param max_valid_data_coverage: maximum valid data coverage percentage on acquired images
        :type max_valid_data_coverage: int
        :return: a list of `pyowm.agro10.imagery.MetaImage` subtypes instances
        """
        assert polygon_id is not None
        assert acquired_from is not None
        assert acquired_to is not None
        assert acquired_from <= acquired_to, 'Start timestamp of acquisition window must come before its end'
        if min_resolution is not None:
            assert min_resolution > 0, 'Minimum resolution must be positive'
        if max_resolution is not None:
            assert max_resolution > 0, 'Maximum resolution must be positive'
        if min_resolution is not None and max_resolution is not None:
            assert min_resolution <= max_resolution, 'Mininum resolution must be lower than maximum resolution'
        if min_cloud_coverage is not None:
            assert min_cloud_coverage >= 0, 'Minimum cloud coverage must be non negative'
        if max_cloud_coverage is not None:
            assert max_cloud_coverage >= 0, 'Maximum cloud coverage must be non negative'
        if min_cloud_coverage is not None and max_cloud_coverage is not None:
            assert min_cloud_coverage <= max_cloud_coverage, 'Minimum cloud coverage must be lower than maximum cloud coverage'
        if min_valid_data_coverage is not None:
            assert min_valid_data_coverage >= 0, 'Minimum valid data coverage must be non negative'
        if max_valid_data_coverage is not None:
            assert max_valid_data_coverage >= 0, 'Maximum valid data coverage must be non negative'
        if min_valid_data_coverage is not None and max_valid_data_coverage is not None:
            assert min_valid_data_coverage <= max_valid_data_coverage, 'Minimum valid data coverage must be lower than maximum valid data coverage'

        # prepare params
        params = dict(appid=self.API_key, polyid=polygon_id, start=acquired_from, end=acquired_to)
        if min_resolution is not None:
            params['resolution_min'] = min_resolution
        if max_resolution is not None:
            params['resolution_max'] = max_resolution
        if acquired_by is not None:
            params['type'] = acquired_by
        if min_cloud_coverage is not None:
            params['clouds_min'] = min_cloud_coverage
        if max_cloud_coverage is not None:
            params['clouds_max'] = max_cloud_coverage
        if min_valid_data_coverage is not None:
            params['coverage_min'] = min_valid_data_coverage
        if max_valid_data_coverage is not None:
            params['coverage_max'] = max_valid_data_coverage

        # call API
        status, data = self.http_client.get_json(SATELLITE_IMAGERY_SEARCH_URI, params=params)

        result_set = SatelliteImagerySearchResultSet(polygon_id, data, timeutils.now(timeformat='unix'))

        # further filter by img_type and/or preset (if specified)
        if img_type is not None and preset is not None:
            return result_set.with_img_type_and_preset(img_type, preset)
        elif img_type is not None:
            return result_set.with_img_type(img_type)
        elif preset is not None:
            return result_set.with_preset(preset)
        else:
            return result_set.all()

    def download_satellite_image(self, metaimage, x=None, y=None, zoom=None, palette=None):
        """
        Downloads the satellite image described by the provided metadata. In case the satellite image is a tile, then
        tile coordinates and zoom must be provided. An optional palette ID can be provided, if supported by the
        downloaded preset (currently only NDVI is supported)

        :param metaimage: the satellite image's metadata, in the form of a `MetaImage` subtype instance
        :type metaimage: a `pyowm.agroapi10.imagery.MetaImage` subtype
        :param x: x tile coordinate (only needed in case you are downloading a tile image)
        :type x: int or `None`
        :param y: y tile coordinate (only needed in case you are downloading a tile image)
        :type y: int or `None`
        :param zoom: zoom level (only needed in case you are downloading a tile image)
        :type zoom: int or `None`
        :param palette: ID of the color palette of the downloaded images. Values are provided by `pyowm.agroapi10.enums.PaletteEnum`
        :type palette: str or `None`
        :return: a `pyowm.agroapi10.imagery.SatelliteImage` instance containing both image's metadata and data
        """
        if palette is not None:
            assert isinstance(palette, str)
            params = dict(paletteid=palette)
        else:
            palette = PaletteEnum.GREEN
            params = dict()
        # polygon PNG
        if isinstance(metaimage, MetaPNGImage):
            prepared_url = metaimage.url
            status, data = self.http_client.get_png(
                prepared_url, params=params)
            img = Image(data, metaimage.image_type)
            return SatelliteImage(metaimage, img, downloaded_on=timeutils.now(timeformat='unix'), palette=palette)
        # GeoTIF
        elif isinstance(metaimage, MetaGeoTiffImage):
            prepared_url = metaimage.url
            status, data = self.http_client.get_geotiff(
                prepared_url, params=params)
            img = Image(data, metaimage.image_type)
            return SatelliteImage(metaimage, img, downloaded_on=timeutils.now(timeformat='unix'), palette=palette)
        # tile PNG
        elif isinstance(metaimage, MetaTile):
            assert x is not None
            assert y is not None
            assert zoom is not None
            prepared_url = self._fill_url(metaimage.url, x, y, zoom)
            status, data = self.http_client.get_png(
                prepared_url, params=params)
            img = Image(data, metaimage.image_type)
            tile = Tile(x, y, zoom, None, img)
            return SatelliteImage(metaimage, tile, downloaded_on=timeutils.now(timeformat='unix'), palette=palette)
        else:
            raise ValueError("Cannot download: unsupported MetaImage subtype")

    def stats_for_satellite_image(self, metaimage):
        """
        Retrieves statistics for the satellite image described by the provided metadata.
        This is currently only supported 'EVI' and 'NDVI' presets

        :param metaimage: the satellite image's metadata, in the form of a `MetaImage` subtype instance
        :type metaimage: a `pyowm.agroapi10.imagery.MetaImage` subtype
        :return: dict
        """
        if metaimage.preset != PresetEnum.EVI and metaimage.preset != PresetEnum.NDVI:
            raise ValueError("Unsupported image preset: should be EVI or NDVI")
        if metaimage.stats_url is None:
            raise ValueError("URL for image statistics is not defined")
        status, data = self.http_client.get_json(metaimage.stats_url, params={})
        return data

    # Utilities
    def _fill_url(self, url_template, x, y, zoom):
        return url_template.replace('{x}', str(x)).replace('{y}', str(y)).replace('{z}', str(zoom))

    def __repr__(self):
        return '<%s.%s>' % (__name__, self.__class__.__name__)
Beispiel #40
0
 def __init__(self, API_key):
     assert API_key is not None, 'You must provide a valid API Key'
     self.API_key = API_key
     self.http_client = HttpClient()
Beispiel #41
0
class WeatherManager:
    """
    A manager objects that provides a full interface to OWM Weather API.

    :param API_key: the OWM AirPollution API key
    :type API_key: str
    :param config: the configuration dictionary
    :type config: dict
    :returns: a *WeatherManager* instance
    :raises: *AssertionError* when no API Key is provided

    """
    def __init__(self, API_key, config):
        assert isinstance(API_key, str), 'You must provide a valid API Key'
        self.API_key = API_key
        assert isinstance(config, dict)
        self.http_client = HttpClient(API_key, config, ROOT_WEATHER_API)

    def weather_api_version(self):
        return WEATHER_API_VERSION

    def weather_at_place(self, name):
        """
        Queries the OWM Weather API for the currently observed weather at the
        specified toponym (eg: "London,uk")

        :param name: the location's toponym
        :type name: str
        :returns: an *Observation* instance or ``None`` if no weather data is
            available
        :raises: *ParseResponseException* when OWM Weather API responses' data
            cannot be parsed or *APICallException* when OWM Weather API can not be
            reached
        """

        assert isinstance(name, str), "Value must be a string"
        params = {'q': name}
        _, json_data = self.http_client.get_json(OBSERVATION_URI,
                                                 params=params)
        return observation.Observation.from_dict(json_data)

    def weather_at_coords(self, lat, lon):
        """
        Queries the OWM Weather API for the currently observed weather at the
        specified geographic (eg: 51.503614, -0.107331).

        :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
        :returns: an *Observation* instance or ``None`` if no weather data is
            available
        :raises: *ParseResponseException* when OWM Weather API responses' data
            cannot be parsed or *APICallException* when OWM Weather API can not be
            reached
        """
        geo.assert_is_lon(lon)
        geo.assert_is_lat(lat)
        params = {'lon': lon, 'lat': lat}
        _, json_data = self.http_client.get_json(OBSERVATION_URI,
                                                 params=params)
        return observation.Observation.from_dict(json_data)

    def weather_at_zip_code(self, zipcode, country):
        """
        Queries the OWM Weather API for the currently observed weather at the
        specified zip code and country code (eg: 2037, au).

        :param zip: the location's zip or postcode
        :type zip: string
        :param country: the location's country code
        :type country: string
        :returns: an *Observation* instance or ``None`` if no weather data is
            available
        :raises: *ParseResponseException* when OWM Weather API responses' data
            cannot be parsed or *APICallException* when OWM Weather API can not be
            reached
        """
        assert isinstance(zipcode, str), "Value must be a string"
        assert isinstance(country, str), "Value must be a string"
        zip_param = zipcode + ',' + country
        params = {'zip': zip_param}
        _, json_data = self.http_client.get_json(OBSERVATION_URI,
                                                 params=params)
        return observation.Observation.from_dict(json_data)

    def weather_at_id(self, id):
        """
        Queries the OWM Weather API for the currently observed weather at the
        specified city ID (eg: 5128581)

        :param id: the location's city ID
        :type id: int
        :returns: an *Observation* instance or ``None`` if no weather data is
            available
        :raises: *ParseResponseException* when OWM Weather API responses' data
            cannot be parsed or *APICallException* when OWM Weather API can not be
            reached
        """
        assert type(id) is int, "'id' must be an int"
        if id < 0:
            raise ValueError("'id' value must be greater than 0")
        params = {'id': id}
        _, json_data = self.http_client.get_json(OBSERVATION_URI,
                                                 params=params)
        return observation.Observation.from_dict(json_data)

    def weather_at_ids(self, ids_list):
        """
        Queries the OWM Weather API for the currently observed weathers at the
        specified city IDs (eg: [5128581,87182])

        :param ids_list: the list of city IDs
        :type ids_list: list of int
        :returns: a list of *Observation* instances or an empty list if no
            weather data is available
        :raises: *ParseResponseException* when OWM Weather API responses' data
            cannot be parsed or *APICallException* when OWM Weather API can not be
            reached
        """
        assert type(ids_list) is list, "'ids_list' must be a list of integers"
        for id in ids_list:
            assert type(id) is int, "'ids_list' must be a list of integers"
            if id < 0:
                raise ValueError("id values in 'ids_list' must be greater "
                                 "than 0")
        params = {'id': ','.join(list(map(str, ids_list)))}
        _, json_data = self.http_client.get_json(GROUP_OBSERVATIONS_URI,
                                                 params=params)
        return observation.Observation.from_dict_of_lists(json_data)

    def weather_at_places(self, pattern, searchtype, limit=None):
        """
        Queries the OWM Weather API for the currently observed weather in all the
        locations whose name is matching the specified text search parameters.
        A twofold search can be issued: *'accurate'* (exact matching) and
        *'like'* (matches names that are similar to the supplied pattern).

        :param pattern: the string pattern (not a regex) to be searched for the
            toponym
        :type pattern: str
        :param searchtype: the search mode to be used, must be *'accurate'* for
          an exact matching or *'like'* for a likelihood matching
        :type: searchtype: str
        :param limit: the maximum number of *Observation* items in the returned
            list (default is ``None``, which stands for any number of items)
        :param limit: int or ``None``
        :returns: a list of *Observation* objects or ``None`` if no weather
            data is available
        :raises: *ParseResponseException* when OWM Weather API responses' data
            cannot be parsed, *APICallException* when OWM Weather API can not be
            reached, *ValueError* when bad value is supplied for the search
            type or the maximum number of items retrieved
        """
        assert isinstance(pattern, str), "'pattern' must be a str"
        assert isinstance(searchtype, str), "'searchtype' must be a str"
        if searchtype != "accurate" and searchtype != "like":
            raise ValueError("'searchtype' value must be 'accurate' or 'like'")
        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 = {'q': pattern, 'type': searchtype}
        if limit is not None:
            # fix for OWM 2.5 API bug!
            params['cnt'] = limit - 1
        _, json_data = self.http_client.get_json(FIND_OBSERVATIONS_URI,
                                                 params=params)
        return observation.Observation.from_dict_of_lists(json_data)

    def weather_at_places_in_bbox(self,
                                  lon_left,
                                  lat_bottom,
                                  lon_right,
                                  lat_top,
                                  zoom=10,
                                  cluster=False):
        """
        Queries the OWM Weather API for the weather currently observed by
        meteostations inside the bounding box of latitude/longitude coords.

        :param lat_top: latitude for top margin of bounding box, must be
            between -90.0 and 90.0
        :type lat_top: int/float
        :param lon_left: longitude for left margin of bounding box
            must be between -180.0 and 180.0
        :type lon_left: int/float
        :param lat_bottom: latitude for the bottom margin of bounding box, must
            be between -90.0 and 90.0
        :type lat_bottom: int/float
        :param lon_right: longitude for the right margin of bounding box,
            must be between -180.0 and 180.0
        :type lon_right: int/float
        :param zoom: zoom level (defaults to: 10)
        :type zoom: int
        :param cluster: use server clustering of points
        :type cluster: bool
        :returns: a list of *Observation* objects or ``None`` if no weather
            data is available
        :raises: *ParseResponseException* when OWM Weather API responses' data
            cannot be parsed, *APICallException* when OWM Weather API can not be
            reached, *ValueError* when coordinates values are out of bounds or
            negative values are provided for limit
        """
        geo.assert_is_lon(lon_left)
        geo.assert_is_lon(lon_right)
        geo.assert_is_lat(lat_bottom)
        geo.assert_is_lat(lat_top)
        assert type(zoom) is int, "'zoom' must be an int"
        if zoom <= 0:
            raise ValueError("'zoom' must greater than zero")
        assert type(cluster) is bool, "'cluster' must be a bool"
        params = {
            'bbox':
            ','.join([
                str(lon_left),
                str(lat_bottom),
                str(lon_right),
                str(lat_top),
                str(zoom)
            ]),
            'cluster':
            'yes' if cluster else 'no'
        }
        _, json_data = self.http_client.get_json(BBOX_CITY_URI, params=params)
        return observation.Observation.from_dict_of_lists(json_data)

    def weather_around_coords(self, lat, lon, limit=None):
        """
        Queries the OWM Weather API for the currently observed weather in all the
        locations in the proximity of the specified coordinates.

        :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 *Observation* items in the returned
            list (default is ``None``, which stands for any number of items)
        :param limit: int or ``None``
        :returns: a list of *Observation* objects or ``None`` if no weather
            data is available
        :raises: *ParseResponseException* when OWM Weather API responses' data
            cannot be parsed, *APICallException* when OWM Weather API can not be
            reached, *ValueError* when coordinates values are out of bounds or
            negative values are provided for limit
        """
        geo.assert_is_lon(lon)
        geo.assert_is_lat(lat)
        params = {'lon': lon, '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['cnt'] = limit
        _, json_data = self.http_client.get_json(FIND_OBSERVATIONS_URI,
                                                 params=params)
        return observation.Observation.from_dict_of_lists(json_data)

    def forecast_at_place(self, name, interval, limit=None):
        """
        Queries the OWM Weather API for weather forecast for the
        specified location (eg: "London,uk") with the given time granularity.
        A *Forecaster* object is returned, containing a *Forecast*: this instance
        encapsulates *Weather* objects corresponding to the provided granularity.

        :param name: the location's toponym
        :type name: str
        :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
            cannot be parsed, *APICallException* when OWM Weather API can not be
            reached
        """
        assert isinstance(name, str), "Value must be a string"
        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 = {'q': name}
        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 = interval
            return forecaster.Forecaster(fc)
        else:
            return None

    def forecast_at_coords(self, lat, lon, interval, limit=None):
        """
        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
            cannot be parsed, *APICallException* when OWM Weather API can not be
            reached
        """
        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}
        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 = interval
            return forecaster.Forecaster(fc)
        else:
            return None

    def forecast_at_id(self, id, interval, limit=None):
        """
        Queries the OWM Weather API for weather forecast for the
        specified city ID (eg: 5128581) with the given time granularity.
        A *Forecaster* object is returned, containing a *Forecast*: this instance
        encapsulates *Weather* objects corresponding to the provided granularity.

        :param id: the location's city ID
        :type id: int
        :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
            cannot be parsed, *APICallException* when OWM Weather API can not be
            reached
        """
        assert type(id) is int, "'id' must be an int"
        if id < 0:
            raise ValueError("'id' value must be greater than 0")
        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 = {'id': id}
        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 = interval
            return forecaster.Forecaster(fc)
        else:
            return None

    def station_tick_history(self, station_ID, limit=None):
        """
        Queries the OWM Weather API for historic weather data measurements for the
        specified meteostation (eg: 2865), sampled once a minute (tick).
        A *StationHistory* object instance is returned, encapsulating the
        measurements: the total number of data points can be limited using the
        appropriate parameter

        :param station_ID: the numeric ID of the meteostation
        :type station_ID: int
        :param limit: the maximum number of data points the result shall
            contain (default is ``None``, which stands for any number of data
            points)
        :type limit: int or ``None``
        :returns: a *StationHistory* instance or ``None`` if data is not
            available for the specified meteostation
        :raises: *ParseResponseException* when OWM Weather API responses' data
            cannot be parsed, *APICallException* when OWM Weather API can not be
            reached, *ValueError* if the limit value is negative

        """
        assert isinstance(station_ID, int), "'station_ID' must be int"
        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")
        station_history = self._retrieve_station_history(
            station_ID, limit, "tick")
        if station_history is not None:
            return historian.Historian(station_history)
        else:
            return None

    def station_hour_history(self, station_ID, limit=None):
        """
        Queries the OWM Weather API for historic weather data measurements for the
        specified meteostation (eg: 2865), sampled once a hour.
        A *Historian* object instance is returned, encapsulating a
        *StationHistory* objects which contains the measurements. The total
        number of retrieved data points can be limited using the appropriate
        parameter

        :param station_ID: the numeric ID of the meteostation
        :type station_ID: int
        :param limit: the maximum number of data points the result shall
            contain (default is ``None``, which stands for any number of data
            points)
        :type limit: int or ``None``
        :returns: a *Historian* instance or ``None`` if data is not
            available for the specified meteostation
        :raises: *ParseResponseException* when OWM Weather API responses' data
            cannot be parsed, *APICallException* when OWM Weather API can not be
            reached, *ValueError* if the limit value is negative

        """
        assert isinstance(station_ID, int), "'station_ID' must be int"
        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")
        station_history = self._retrieve_station_history(
            station_ID, limit, "hour")
        if station_history is not None:
            return historian.Historian(station_history)
        else:
            return None

    def station_day_history(self, station_ID, limit=None):
        """
        Queries the OWM Weather API for historic weather data measurements for the
        specified meteostation (eg: 2865), sampled once a day.
        A *Historian* object instance is returned, encapsulating a
        *StationHistory* objects which contains the measurements. The total
        number of retrieved data points can be limited using the appropriate
        parameter

        :param station_ID: the numeric ID of the meteostation
        :type station_ID: int
        :param limit: the maximum number of data points the result shall
            contain (default is ``None``, which stands for any number of data
            points)
        :type limit: int or ``None``
        :returns: a *Historian* instance or ``None`` if data is not
            available for the specified meteostation
        :raises: *ParseResponseException* when OWM Weather API responses' data
            cannot be parsed, *APICallException* when OWM Weather API can not be
            reached, *ValueError* if the limit value is negative

        """
        assert isinstance(station_ID, int), "'station_ID' must be int"
        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")
        station_history = self._retrieve_station_history(
            station_ID, limit, "day")
        if station_history is not None:
            return historian.Historian(station_history)
        else:
            return None

    def _retrieve_station_history(self, station_ID, limit, interval):
        """
        Helper method for station_X_history functions.
        """
        params = {'id': station_ID, 'type': interval}
        if limit is not None:
            params['cnt'] = limit
        _, json_data = self.http_client.get_json(STATION_WEATHER_HISTORY_URI,
                                                 params=params)
        sh = stationhistory.StationHistory.from_dict(json_data)
        if sh is not None:
            sh.station_id = station_ID
            sh.interval = interval
        return sh

    def one_call(self, lat: Union[int, float],
                 lon: Union[int, float]) -> one_call.OneCall:
        """
        Queries the OWM Weather API with one call for current weather information and forecast for the
        specified geographic coordinates.
        One Call API provides the following weather data for any geographical coordinate:
        - Current weather
        - Hourly forecast for 48 hours
        - Daily forecast for 7 days

        A *OneCall* object is returned with the current data and the two forecasts.

        :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
        :returns: a *OneCall* instance or ``None`` if the 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
        """
        geo.assert_is_lon(lon)
        geo.assert_is_lat(lat)
        params = {'lon': lon, 'lat': lat}

        _, json_data = self.http_client.get_json(ONE_CALL_URI, params=params)
        return one_call.OneCall.from_dict(json_data)

    def one_call_history(self,
                         lat: Union[int, float],
                         lon: Union[int, float],
                         dt: int = None):
        """
        Queries the OWM Weather API with one call for historical weather information for the
        specified geographic coordinates.

        A *OneCall* object is returned with the current data and the two forecasts.

        :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 dt: timestamp from when the historical data starts. Cannot be less then now - 5 days.
                    Default = None means now - 5 days
        :type dt: int
        :returns: a *OneCall* instance or ``None`` if the 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
        """
        geo.assert_is_lon(lon)
        geo.assert_is_lat(lat)
        if dt is None:
            dt = int(
                (datetime.now() -
                 timedelta(days=5)).replace(tzinfo=timezone.utc).timestamp())
        else:
            if not isinstance(dt, int):
                raise ValueError("dt must be of type int")
            if dt < 0:
                raise ValueError("dt must be positive")

        params = {'lon': lon, 'lat': lat, 'dt': dt}

        _, json_data = self.http_client.get_json(ONE_CALL_HISTORICAL_URI,
                                                 params=params)
        return one_call.OneCall.from_dict(json_data)

    def __repr__(self):
        return '<%s.%s>' % (__name__, self.__class__.__name__)
Beispiel #42
0
class AlertManager:

    """
    A manager objects that provides a full interface to OWM Alert API. It implements CRUD methods on Trigger entities
    and read/deletion of related Alert objects

    :param API_key: the OWM Weather API key
    :type API_key: str
    :returns: an *AlertManager* instance
    :raises: *AssertionError* when no API Key is provided

    """

    def __init__(self, API_key):
        assert API_key is not None, 'You must provide a valid API Key'
        self.API_key = API_key
        self.trigger_parser = TriggerParser()
        self.alert_parser = AlertParser()
        self.http_client = HttpClient()

    def alert_api_version(self):
        return ALERT_API_VERSION

    # TRIGGER methods

    def create_trigger(self,  start, end, conditions, area, alert_channels=None):
        """
        Create a new trigger on the Alert API with the given parameters
        :param start: time object representing the time when the trigger begins to be checked
        :type start: int, ``datetime.datetime`` or ISO8601-formatted string
        :param end: time object representing the time when the trigger ends to be checked
        :type end: int, ``datetime.datetime`` or ISO8601-formatted string
        :param conditions: the `Condition` objects representing the set of checks to be done on weather variables
        :type conditions: list of `pyowm.utils.alertapi30.Condition` instances
        :param area: the geographic are over which conditions are checked: it can be composed by multiple geoJSON types
        :type area: list of geoJSON types
        :param alert_channels: the alert channels through which alerts originating from this `Trigger` can be consumed.
        Defaults to OWM API polling
        :type alert_channels: list of `pyowm.utils.alertapi30.AlertChannel` instances
        :returns:  a *Trigger* instance
        :raises: *ValueError* when start or end epochs are `None` or when end precedes start or when conditions or area
        are empty collections
        """
        assert start is not None
        assert end is not None

        # prepare time period
        unix_start = timeformatutils.to_UNIXtime(start)
        unix_end = timeformatutils.to_UNIXtime(end)
        unix_current = timeutils.now(timeformat='unix')
        if unix_start >= unix_end:
            raise ValueError("The start timestamp must precede the end timestamp")
        delta_millis_start = timeutils.millis_offset_between_epochs(unix_current, unix_start)
        delta_millis_end = timeutils.millis_offset_between_epochs(unix_current, unix_end)
        the_time_period = {
            "start": {
                "expression": "after",
                "amount": delta_millis_start
            },
            "end": {
                "expression": "after",
                "amount": delta_millis_end
            }
        }

        assert conditions is not None
        if len(conditions) == 0:
            raise ValueError('A trigger must contain at least one condition: you provided none')
        the_conditions = [dict(name=c.weather_param, expression=c.operator, amount=c.amount) for c in conditions]

        assert area is not None
        if len(area) == 0:
            raise ValueError('The area for a trigger must contain at least one geoJSON type: you provided none')
        the_area = [a.as_dict() for a in area]

        # >>> for the moment, no specific handling for alert channels

        status, payload = self.http_client.post(
            TRIGGERS_URI,
            params={'appid': self.API_key},
            data=dict(time_period=the_time_period, conditions=the_conditions, area=the_area),
            headers={'Content-Type': 'application/json'})
        return self.trigger_parser.parse_dict(payload)

    def get_triggers(self):
        """
        Retrieves all of the user's triggers that are set on the Weather Alert API.

        :returns: list of `pyowm.alertapi30.trigger.Trigger` objects

        """
        status, data = self.http_client.get_json(
            TRIGGERS_URI,
            params={'appid': self.API_key},
            headers={'Content-Type': 'application/json'})
        return [self.trigger_parser.parse_dict(item) for item in data]

    def get_trigger(self, trigger_id):
        """
        Retrieves the named trigger from the Weather Alert API.

        :param trigger_id: the ID of the trigger
        :type trigger_id: str
        :return: a `pyowm.alertapi30.trigger.Trigger` instance
        """
        assert isinstance(trigger_id, str), "Value must be a string"
        status, data = self.http_client.get_json(
            NAMED_TRIGGER_URI % trigger_id,
            params={'appid': self.API_key},
            headers={'Content-Type': 'application/json'})
        return self.trigger_parser.parse_dict(data)

    def update_trigger(self, trigger):
        """
        Updates on the Alert API the trigger record having the ID of the specified Trigger object: the remote record is
        updated with data from the local Trigger object.

        :param trigger: the Trigger with updated data
        :type trigger: `pyowm.alertapi30.trigger.Trigger`
        :return: ``None`` if update is successful, an error otherwise
        """
        assert trigger is not None
        assert isinstance(trigger.id, str), "Value must be a string"
        the_time_period = {
            "start": {
                "expression": "after",
                "amount": trigger.start_after_millis
            },
            "end": {
                "expression": "after",
                "amount": trigger.end_after_millis
            }
        }
        the_conditions = [dict(name=c.weather_param, expression=c.operator, amount=c.amount) for c in trigger.conditions]
        the_area = [a.as_dict() for a in trigger.area]

        status, _ = self.http_client.put(
            NAMED_TRIGGER_URI % trigger.id,
            params={'appid': self.API_key},
            data=dict(time_period=the_time_period, conditions=the_conditions, area=the_area),
            headers={'Content-Type': 'application/json'})

    def delete_trigger(self, trigger):
        """
        Deletes from the Alert API the trigger record identified by the ID of the provided
        `pyowm.alertapi30.trigger.Trigger`, along with all related alerts

        :param trigger: the `pyowm.alertapi30.trigger.Trigger` object to be deleted
        :type trigger: `pyowm.alertapi30.trigger.Trigger`
        :returns: `None` if deletion is successful, an exception otherwise
        """
        assert trigger is not None
        assert isinstance(trigger.id, str), "Value must be a string"
        status, _ = self.http_client.delete(
            NAMED_TRIGGER_URI % trigger.id,
            params={'appid': self.API_key},
            headers={'Content-Type': 'application/json'})

    # ALERTS methods

    def get_alerts_for(self, trigger):
        """
        Retrieves all of the alerts that were fired for the specified Trigger
        :param trigger: the trigger
        :type trigger: `pyowm.alertapi30.trigger.Trigger`
        :return: list of `pyowm.alertapi30.alert.Alert` objects
        """
        assert trigger is not None
        assert isinstance(trigger.id, str), "Value must be a string"
        status, data = self.http_client.get_json(
            ALERTS_URI % trigger.id,
            params={'appid': self.API_key},
            headers={'Content-Type': 'application/json'})
        return [self.alert_parser.parse_dict(item) for item in data]

    def get_alert(self, alert_id, trigger):
        """
        Retrieves info about the alert record on the Alert API that has the specified ID and belongs to the specified
        parent Trigger object
        :param trigger: the parent trigger
        :type trigger: `pyowm.alertapi30.trigger.Trigger`
        :param alert_id: the ID of the alert
        :type alert_id `pyowm.alertapi30.alert.Alert`
        :return: an `pyowm.alertapi30.alert.Alert` instance
        """
        assert trigger is not None
        assert alert_id is not None
        assert isinstance(alert_id, str), "Value must be a string"
        assert isinstance(trigger.id, str), "Value must be a string"
        status, data = self.http_client.get_json(
            NAMED_ALERT_URI % (trigger.id, alert_id),
            params={'appid': self.API_key},
            headers={'Content-Type': 'application/json'})
        return self.alert_parser.parse_dict(data)

    def delete_all_alerts_for(self, trigger):
        """
        Deletes all of the alert that were fired for the specified Trigger
        :param trigger: the trigger whose alerts are to be cleared
        :type trigger: `pyowm.alertapi30.trigger.Trigger`
        :return: `None` if deletion is successful, an exception otherwise
        """
        assert trigger is not None
        assert isinstance(trigger.id, str), "Value must be a string"
        status, _ = self.http_client.delete(
            ALERTS_URI % trigger.id,
            params={'appid': self.API_key},
            headers={'Content-Type': 'application/json'})

    def delete_alert(self, alert):
        """
        Deletes the specified alert from the Alert API
        :param alert: the alert to be deleted
        :type alert: pyowm.alertapi30.alert.Alert`
        :return: ``None`` if the deletion was successful, an error otherwise
        """
        assert alert is not None
        assert isinstance(alert.id, str), "Value must be a string"
        assert isinstance(alert.trigger_id, str), "Value must be a string"
        status, _ = self.http_client.delete(
            NAMED_ALERT_URI % (alert.trigger_id, alert.id),
            params={'appid': self.API_key},
            headers={'Content-Type': 'application/json'})
Beispiel #43
0
 def __init__(self, API_key):
     assert API_key is not None, 'You must provide a valid API Key'
     self.API_key = API_key
     self.trigger_parser = TriggerParser()
     self.alert_parser = AlertParser()
     self.http_client = HttpClient()
 def __init__(self, API_key):
     assert API_key is not None, 'You must provide a valid API Key'
     self.API_key = API_key
     self.stations_parser = StationParser()
     self.aggregated_measurements_parser = AggregatedMeasurementParser()
     self.http_client = HttpClient()
Beispiel #45
0
 def __init__(self, API_key, config):
     assert isinstance(API_key, str), 'You must provide a valid API Key'
     self.API_key = API_key
     assert isinstance(config, dict)
     self.http_client = HttpClient(API_key, config, ROOT_WEATHER_API)
class StationsManager(object):

    """
    A manager objects that provides a full interface to OWM Stations API. Mainly
    it implements CRUD methods on Station entities and the corresponding
    measured datapoints.

    :param API_key: the OWM web API key (defaults to ``None``)
    :type API_key: str
    :returns: a *StationsManager* instance
    :raises: *AssertionError* when no API Key is provided

    """

    def __init__(self, API_key):
        assert API_key is not None, 'You must provide a valid API Key'
        self.API_key = API_key
        self.stations_parser = StationParser()
        self.aggregated_measurements_parser = AggregatedMeasurementParser()
        self.http_client = HttpClient()

    def stations_api_version(self):
        return STATIONS_API_VERSION

    # STATIONS Methods

    def get_stations(self):
        """
        Retrieves all of the user's stations registered on the Stations API.

        :returns: list of *pyowm.stationsapi30.station.Station* objects

        """

        status, data = self.http_client.get_json(
            'http://api.openweathermap.org/data/3.0/stations',
            params={'appid': self.API_key},
            headers={'Content-Type': 'application/json'})
        return [self.stations_parser.parse_dict(item) for item in data]

    def get_station(self, id):
        """
        Retrieves a named station registered on the Stations API.

        :param id: the ID of the station
        :type id: str
        :returns: a *pyowm.stationsapi30.station.Station* object

        """
        status, data = self.http_client.get_json(
            'http://api.openweathermap.org/data/3.0/stations/%s' % str(id),
            params={'appid': self.API_key},
            headers={'Content-Type': 'application/json'})
        return self.stations_parser.parse_dict(data)

    def create_station(self, external_id, name, lat, lon, alt=None):
        """
        Create a new station on the Station API with the given parameters

        :param external_id: the user-given ID of the station
        :type external_id: str
        :param name: the name of the station
        :type name: str
        :param lat: latitude of the station
        :type lat: float
        :param lon: longitude of the station
        :type lon: float
        :param alt: altitude of the station
        :type alt: float
        :returns: the new *pyowm.stationsapi30.station.Station* object
        """
        assert external_id is not None
        assert name is not None
        assert lon is not None
        assert lat is not None
        if lon < -180.0 or lon > 180.0:
            raise ValueError("'lon' value must be between -180 and 180")
        if lat < -90.0 or lat > 90.0:
            raise ValueError("'lat' value must be between -90 and 90")
        if alt is not None:
            if alt < 0.0:
                raise ValueError("'alt' value must not be negative")
        status, payload = self.http_client.post(
            'http://api.openweathermap.org/data/3.0/stations',
            params={'appid': self.API_key},
            data=dict(external_id=external_id, name=name, lat=lat,
                      lon=lon, alt=alt),
            headers={'Content-Type': 'application/json'})
        return self.stations_parser.parse_dict(payload)

    def update_station(self, station):
        """
        Updates the Station API record identified by the ID of the provided
        *pyowm.stationsapi30.station.Station* object with all of its fields

        :param station: the *pyowm.stationsapi30.station.Station* object to be updated
        :type station: *pyowm.stationsapi30.station.Station*
        :returns: `None` if update is successful, an exception otherwise
        """
        assert station.id is not None
        status, _ = self.http_client.put(
            'http://api.openweathermap.org/data/3.0/stations/%s' % str(station.id),
            params={'appid': self.API_key},
            data=dict(external_id=station.external_id, name=station.name,
                      lat=station.lat, lon=station.lon, alt=station.alt),
            headers={'Content-Type': 'application/json'})

    def delete_station(self, station):
        """
        Deletes the Station API record identified by the ID of the provided
        *pyowm.stationsapi30.station.Station*, along with all its related
        measurements

        :param station: the *pyowm.stationsapi30.station.Station* object to be deleted
        :type station: *pyowm.stationsapi30.station.Station*
        :returns: `None` if deletion is successful, an exception otherwise
        """
        assert station.id is not None
        status, _ = self.http_client.delete(
            'http://api.openweathermap.org/data/3.0/stations/%s' % str(station.id),
            params={'appid': self.API_key},
            headers={'Content-Type': 'application/json'})

    # Measurements-related methods

    def send_measurement(self, measurement):
        """
        Posts the provided Measurement object's data to the Station API.

        :param measurement: the *pyowm.stationsapi30.measurement.Measurement*
          object to be posted
        :type measurement: *pyowm.stationsapi30.measurement.Measurement* instance
        :returns: `None` if creation is successful, an exception otherwise
        """
        assert measurement is not None
        assert measurement.station_id is not None
        status, _ = self.http_client.post(
            'http://api.openweathermap.org/data/3.0/measurements',
            params={'appid': self.API_key},
            data=[measurement.to_dict()],
            headers={'Content-Type': 'application/json'})

    def send_measurements(self, list_of_measurements):
        """
        Posts data about the provided list of Measurement objects to the
        Station API. The objects may be related to different station IDs.

        :param list_of_measurements: list of *pyowm.stationsapi30.measurement.Measurement*
          objects to be posted
        :type list_of_measurements: list of *pyowm.stationsapi30.measurement.Measurement*
          instances
        :returns: `None` if creation is successful, an exception otherwise
        """
        assert list_of_measurements is not None
        assert all([m.station_id is not None for m in list_of_measurements])
        msmts = [m.to_dict() for m in list_of_measurements]
        status, _ = self.http_client.post(
            'http://api.openweathermap.org/data/3.0/measurements',
            params={'appid': self.API_key},
            data=msmts,
            headers={'Content-Type': 'application/json'})

    def get_measurements(self, station_id, aggregated_on, from_timestamp,
                         to_timestamp, limit=100):
        """
        Reads measurements of a specified station recorded in the specified time
        window and aggregated on minute, hour or day. Optionally, the number of
        resulting measurements can be limited.

        :param station_id: unique station identifier
        :type station_id: str
        :param aggregated_on: aggregation time-frame for this measurement
        :type aggregated_on: string between 'm','h' and 'd'
        :param from_timestamp: Unix timestamp corresponding to the beginning of
          the time window
        :type from_timestamp: int
        :param to_timestamp: Unix timestamp corresponding to the end of the
          time window
        :type to_timestamp: int
        :param limit: max number of items to be returned. Defaults to 100
        :type limit: int
        :returns: list of *pyowm.stationsapi30.measurement.AggregatedMeasurement*
          objects
        """
        assert station_id is not None
        assert aggregated_on is not None
        assert from_timestamp is not None
        assert from_timestamp > 0
        assert to_timestamp is not None
        assert to_timestamp > 0
        if to_timestamp < from_timestamp:
            raise ValueError("End timestamp can't be earlier than begin timestamp")
        assert isinstance(limit, int)
        assert limit >= 0
        query = {'appid': self.API_key,
                 'station_id': station_id,
                 'type': aggregated_on,
                 'from': from_timestamp,
                 'to': to_timestamp,
                 'limit': limit}
        status, data = self.http_client.get_json(
            'http://api.openweathermap.org/data/3.0/measurements',
            params=query,
            headers={'Content-Type': 'application/json'})
        return [self.aggregated_measurements_parser.parse_dict(item) for item in data]

    def send_buffer(self, buffer):
        """
        Posts to the Stations API data about the Measurement objects contained
        into the provided Buffer instance.

        :param buffer: the *pyowm.stationsapi30.buffer.Buffer* instance whose
          measurements are to be posted
        :type buffer: *pyowm.stationsapi30.buffer.Buffer* instance
        :returns: `None` if creation is successful, an exception otherwise
        """
        assert buffer is not None
        msmts = []
        for x in buffer.measurements:
            m = x.to_dict()
            item = dict()
            item['station_id'] = m['station_id']
            item['dt'] = m['timestamp']
            item['temperature'] = m['temperature']
            item['wind_speed'] = m['wind_speed']
            item['wind_gust'] = m['wind_gust']
            item['wind_deg'] = m['wind_deg']
            item['pressure'] = m['pressure']
            item['humidity'] = m['humidity']
            item['rain_1h'] = m['rain_1h']
            item['rain_6h'] = m['rain_6h']
            item['rain_24h'] = m['rain_24h']
            item['snow_1h'] = m['snow_1h']
            item['snow_6h'] = m['snow_6h']
            item['snow_24h'] = m['snow_24h']
            item['dew_point'] = m['dew_point']
            item['humidex'] = m['humidex']
            item['heat_index'] = m['heat_index']
            item['visibility_distance'] = m['visibility_distance']
            item['visibility_prefix'] = m['visibility_prefix']
            item['clouds'] = [dict(distance=m['clouds_distance']),
                              dict(condition=m['clouds_condition']),
                              dict(cumulus=m['clouds_cumulus'])]
            item['weather'] = [
                dict(precipitation=m['weather_precipitation']),
                dict(descriptor=m['weather_descriptor']),
                dict(intensity=m['weather_intensity']),
                dict(proximity=m['weather_proximity']),
                dict(obscuration=m['weather_obscuration']),
                dict(other=m['weather_other'])]
            msmts.append(item)
        status, _ = self.http_client.post(
            'http://api.openweathermap.org/data/3.0/measurements',
            params={'appid': self.API_key},
            data=msmts,
            headers={'Content-Type': 'application/json'})