mirror of
https://github.com/jdejaegh/python-irceline.git
synced 2025-06-27 03:35:56 +02:00
Refactor API class
This commit is contained in:
parent
e146c8359e
commit
800f87cf38
3 changed files with 50 additions and 43 deletions
|
@ -2,3 +2,7 @@ from pyproj import Transformer
|
|||
|
||||
project_transform = Transformer.from_crs('EPSG:4326', 'EPSG:31370', always_xy=True)
|
||||
rio_wfs_base_url = 'https://geo.irceline.be/rio/wfs'
|
||||
# noinspection HttpUrlsUsage
|
||||
# There is not HTTPS version of this endpoint
|
||||
forecast_base_url = 'http://ftp.irceline.be/forecast'
|
||||
user_agent = 'github.com/jdejaegh/python-irceline'
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import asyncio
|
||||
import socket
|
||||
from xml.etree import ElementTree
|
||||
from datetime import datetime, timedelta, date
|
||||
from typing import Tuple, List, Dict, Set
|
||||
from xml.etree import ElementTree
|
||||
|
||||
import aiohttp
|
||||
import async_timeout
|
||||
from aiohttp import ClientResponse
|
||||
|
||||
from . import project_transform, rio_wfs_base_url
|
||||
from . import project_transform, rio_wfs_base_url, user_agent
|
||||
from .data import RioFeature, FeatureValue
|
||||
|
||||
|
||||
|
@ -16,14 +16,44 @@ class IrcelineApiError(Exception):
|
|||
"""Exception to indicate an API error."""
|
||||
|
||||
|
||||
class IrcelineClient:
|
||||
"""API client for IRCEL - CELINE open data"""
|
||||
|
||||
class IrcelineBaseClient:
|
||||
def __init__(self, session: aiohttp.ClientSession) -> None:
|
||||
self._session = session
|
||||
|
||||
async def _api_wrapper(self, url: str, querystring: dict, method: str = 'GET'):
|
||||
"""
|
||||
Call the URL with the specified query string. Raises exception for >= 400 response code
|
||||
:param url: base URL
|
||||
:param querystring: dict to build the query string
|
||||
:return: response from the client
|
||||
"""
|
||||
|
||||
headers = {'User-Agent': user_agent}
|
||||
|
||||
try:
|
||||
async with async_timeout.timeout(60):
|
||||
response = await self._session.request(
|
||||
method=method,
|
||||
url=url,
|
||||
params=querystring,
|
||||
headers=headers
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
|
||||
except asyncio.TimeoutError as exception:
|
||||
raise IrcelineApiError("Timeout error fetching information") from exception
|
||||
except (aiohttp.ClientError, socket.gaierror) as exception:
|
||||
raise IrcelineApiError("Error fetching information") from exception
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
raise IrcelineApiError(f"Something really wrong happened! {exception}") from exception
|
||||
|
||||
|
||||
class IrcelineRioClient(IrcelineBaseClient):
|
||||
"""API client for RIO interpolated IRCEL - CELINE open data"""
|
||||
|
||||
@staticmethod
|
||||
def epsg_transform(position: Tuple[float, float]) -> tuple:
|
||||
def _epsg_transform(position: Tuple[float, float]) -> tuple:
|
||||
"""
|
||||
Convert 'EPSG:4326' coordinates to 'EPSG:31370' coordinates
|
||||
:param position: (x, y) coordinates
|
||||
|
@ -58,7 +88,7 @@ class IrcelineClient:
|
|||
else:
|
||||
raise IrcelineApiError(f"Wrong parameter type for timestamp: {type(timestamp)}")
|
||||
|
||||
coord = self.epsg_transform(position)
|
||||
coord = self._epsg_transform(position)
|
||||
querystring = {"service": "WFS",
|
||||
"version": "1.3.0",
|
||||
"request": "GetFeature",
|
||||
|
@ -70,7 +100,7 @@ class IrcelineClient:
|
|||
f"INTERSECTS(the_geom, POINT ({coord[0]} {coord[1]}))"}
|
||||
|
||||
r: ClientResponse = await self._api_wrapper(rio_wfs_base_url, querystring)
|
||||
return self.format_result('rio', await r.json(), features)
|
||||
return self._format_result('rio', await r.json(), features)
|
||||
|
||||
async def get_rio_capabilities(self) -> Set[str]:
|
||||
"""
|
||||
|
@ -82,10 +112,10 @@ class IrcelineClient:
|
|||
"request": "GetCapabilities"}
|
||||
r: ClientResponse = await self._api_wrapper(rio_wfs_base_url, querystring)
|
||||
|
||||
return self.parse_capabilities(await r.text())
|
||||
return self._parse_capabilities(await r.text())
|
||||
|
||||
@staticmethod
|
||||
def parse_capabilities(xml_string: str) -> Set[str]:
|
||||
def _parse_capabilities(xml_string: str) -> Set[str]:
|
||||
"""
|
||||
From an XML string obtained with GetCapabilities, generate a set of feature names
|
||||
:param xml_string: XML string to parse
|
||||
|
@ -105,7 +135,7 @@ class IrcelineClient:
|
|||
return feature_type_names
|
||||
|
||||
@staticmethod
|
||||
def format_result(prefix: str, data: dict, features: List[RioFeature]) -> dict:
|
||||
def _format_result(prefix: str, data: dict, features: List[RioFeature]) -> dict:
|
||||
"""
|
||||
Format the JSON dict returned by the WFS service into a more practical dict to use with only the latest measure
|
||||
for each feature requested
|
||||
|
@ -145,30 +175,3 @@ class IrcelineClient:
|
|||
|
||||
return result
|
||||
|
||||
async def _api_wrapper(self, url: str, querystring: dict):
|
||||
"""
|
||||
Call the URL with the specified query string (GET). Raises exception for >= 400 response code
|
||||
:param url: base URL
|
||||
:param querystring: dict to build the query string
|
||||
:return: response from the client
|
||||
"""
|
||||
|
||||
headers = {'User-Agent': 'github.com/jdejaegh/python-irceline'}
|
||||
|
||||
try:
|
||||
async with async_timeout.timeout(60):
|
||||
response = await self._session.request(
|
||||
method='GET',
|
||||
url=url,
|
||||
params=querystring,
|
||||
headers=headers
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
|
||||
except asyncio.TimeoutError as exception:
|
||||
raise IrcelineApiError("Timeout error fetching information") from exception
|
||||
except (aiohttp.ClientError, socket.gaierror) as exception:
|
||||
raise IrcelineApiError("Error fetching information") from exception
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
raise IrcelineApiError(f"Something really wrong happened! {exception}") from exception
|
||||
|
|
|
@ -2,7 +2,7 @@ from datetime import datetime, date
|
|||
|
||||
from freezegun import freeze_time
|
||||
|
||||
from src.open_irceline.api import IrcelineClient
|
||||
from src.open_irceline.api import IrcelineRioClient
|
||||
from src.open_irceline.data import RioFeature, FeatureValue
|
||||
from tests.conftest import get_api_data
|
||||
|
||||
|
@ -10,7 +10,7 @@ from tests.conftest import get_api_data
|
|||
@freeze_time(datetime.fromisoformat("2024-06-15T16:55:03.419Z"))
|
||||
async def test_format_result_hmean():
|
||||
data = get_api_data('rio_wfs.json')
|
||||
result = IrcelineClient.format_result('rio', data, [RioFeature.NO2_HMEAN, RioFeature.O3_HMEAN])
|
||||
result = IrcelineRioClient._format_result('rio', data, [RioFeature.NO2_HMEAN, RioFeature.O3_HMEAN])
|
||||
|
||||
expected = {
|
||||
str(RioFeature.O3_HMEAN): FeatureValue(
|
||||
|
@ -29,8 +29,8 @@ async def test_format_result_hmean():
|
|||
@freeze_time(datetime.fromisoformat("2024-06-15T19:30:09.581Z"))
|
||||
async def test_format_result_dmean():
|
||||
data = get_api_data('rio_wfs_dmean.json')
|
||||
result = IrcelineClient.format_result('rio', data,
|
||||
[RioFeature.BC_DMEAN, RioFeature.PM10_DMEAN, RioFeature.PM25_DMEAN])
|
||||
result = IrcelineRioClient._format_result('rio', data,
|
||||
[RioFeature.BC_DMEAN, RioFeature.PM10_DMEAN, RioFeature.PM25_DMEAN])
|
||||
|
||||
expected = {
|
||||
str(RioFeature.BC_DMEAN): FeatureValue(timestamp=date(2024, 6, 15), value=0.1),
|
||||
|
@ -43,7 +43,7 @@ async def test_format_result_dmean():
|
|||
|
||||
def test_parse_capabilities():
|
||||
with open('tests/fixtures/capabilities.xml', 'r') as xml_file:
|
||||
result = IrcelineClient.parse_capabilities(xml_file.read())
|
||||
result = IrcelineRioClient._parse_capabilities(xml_file.read())
|
||||
|
||||
expected = {'rio:so2_anmean_be', 'rio:o3_hmean', 'rio:bc_anmean_vl', 'rio:o3_anmean_be', 'rio:pm10_hmean_vl',
|
||||
'rio:o3_aot40for_be', 'rio:no2_maxhmean', 'rio:pm10_24hmean_1x1', 'rio:o3_aot40veg_5y_be',
|
||||
|
|
Loading…
Add table
Reference in a new issue