meteocatpy 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +33 -0
- package/.gitlab-ci.yml +46 -0
- package/.pre-commit-config.yaml +37 -0
- package/.releaserc +23 -0
- package/.releaserc.toml +14 -0
- package/AUTHORS.md +12 -0
- package/CHANGELOG.md +137 -0
- package/LICENSE +194 -0
- package/README.md +62 -0
- package/filetree.py +48 -0
- package/filetree.txt +48 -0
- package/meteocatpy/README.md +62 -0
- package/meteocatpy/__init__.py +27 -0
- package/meteocatpy/const.py +10 -0
- package/meteocatpy/data.py +140 -0
- package/meteocatpy/exceptions.py +35 -0
- package/meteocatpy/forecast.py +137 -0
- package/meteocatpy/helpers.py +46 -0
- package/meteocatpy/py.typed +0 -0
- package/meteocatpy/stations.py +71 -0
- package/meteocatpy/symbols.py +89 -0
- package/meteocatpy/town.py +61 -0
- package/meteocatpy/townstations.py +99 -0
- package/meteocatpy/variables.py +74 -0
- package/meteocatpy/version.py +2 -0
- package/package.json +23 -0
- package/poetry.lock +3313 -0
- package/pyproject.toml +72 -0
- package/releaserc.json +18 -0
- package/requirements.test.txt +3 -0
- package/setup.cfg +64 -0
- package/setup.py +10 -0
- package/tests/data_test.py +122 -0
- package/tests/import_test.py +18 -0
- package/tests/integration_test_complete.py +77 -0
- package/tests/integration_test_forecast.py +54 -0
- package/tests/integration_test_station_data.py +34 -0
- package/tests/integration_test_stations.py +32 -0
- package/tests/integration_test_symbols.py +68 -0
- package/tests/integration_test_town.py +32 -0
- package/tests/integration_test_town_stations.py +36 -0
- package/tests/integration_test_variables.py +32 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
from .const import BASE_URL, MUNICIPIS_LIST_URL
|
|
3
|
+
from .exceptions import BadRequestError, ForbiddenError, TooManyRequestsError, InternalServerError, UnknownAPIError
|
|
4
|
+
|
|
5
|
+
class MeteocatTown:
|
|
6
|
+
"""Clase para interactuar con la lista de municipios de la API de Meteocat."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, api_key: str):
|
|
9
|
+
"""
|
|
10
|
+
Inicializa la clase MeteocatTown.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
api_key (str): Clave de API para autenticar las solicitudes.
|
|
14
|
+
"""
|
|
15
|
+
self.api_key = api_key
|
|
16
|
+
self.headers = {
|
|
17
|
+
"Content-Type": "application/json",
|
|
18
|
+
"X-Api-Key": self.api_key,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async def get_municipis(self):
|
|
22
|
+
"""
|
|
23
|
+
Obtiene la lista de municipios desde la API de Meteocat.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
dict: Datos de los municipios.
|
|
27
|
+
"""
|
|
28
|
+
url = f"{BASE_URL}{MUNICIPIS_LIST_URL}"
|
|
29
|
+
async with aiohttp.ClientSession() as session:
|
|
30
|
+
try:
|
|
31
|
+
async with session.get(url, headers=self.headers) as response:
|
|
32
|
+
if response.status == 200:
|
|
33
|
+
return await response.json()
|
|
34
|
+
|
|
35
|
+
# Gestionar errores según el código de estado
|
|
36
|
+
if response.status == 400:
|
|
37
|
+
raise BadRequestError(await response.json())
|
|
38
|
+
elif response.status == 403:
|
|
39
|
+
error_data = await response.json()
|
|
40
|
+
if error_data.get("message") == "Forbidden":
|
|
41
|
+
raise ForbiddenError(error_data)
|
|
42
|
+
elif error_data.get("message") == "Missing Authentication Token":
|
|
43
|
+
raise ForbiddenError(error_data)
|
|
44
|
+
elif response.status == 429:
|
|
45
|
+
raise TooManyRequestsError(await response.json())
|
|
46
|
+
elif response.status == 500:
|
|
47
|
+
raise InternalServerError(await response.json())
|
|
48
|
+
else:
|
|
49
|
+
raise UnknownAPIError(f"Unexpected error {response.status}: {await response.text()}")
|
|
50
|
+
|
|
51
|
+
except aiohttp.ClientError as e:
|
|
52
|
+
raise UnknownAPIError(
|
|
53
|
+
message=f"Error al conectar con la API de Meteocat: {str(e)}",
|
|
54
|
+
status_code=0,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
except Exception as ex:
|
|
58
|
+
raise UnknownAPIError(
|
|
59
|
+
message=f"Error inesperado: {str(ex)}",
|
|
60
|
+
status_code=0,
|
|
61
|
+
)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
from .stations import MeteocatStations # Importamos la clase MeteocatStations
|
|
3
|
+
from .const import BASE_URL, STATIONS_MUNICIPI_URL
|
|
4
|
+
from .exceptions import (
|
|
5
|
+
BadRequestError,
|
|
6
|
+
ForbiddenError,
|
|
7
|
+
TooManyRequestsError,
|
|
8
|
+
InternalServerError,
|
|
9
|
+
UnknownAPIError,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
class MeteocatTownStations:
|
|
13
|
+
"""
|
|
14
|
+
Clase para interactuar con la API de Meteocat y obtener
|
|
15
|
+
las estaciones representativas de un municipio para una variable específica.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, api_key: str):
|
|
19
|
+
"""
|
|
20
|
+
Inicializa la clase MeteocatTownStations.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
api_key (str): Clave de API para autenticar las solicitudes.
|
|
24
|
+
"""
|
|
25
|
+
self.api_key = api_key
|
|
26
|
+
self.headers = {
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
"X-Api-Key": self.api_key,
|
|
29
|
+
}
|
|
30
|
+
self.stations_service = MeteocatStations(api_key) # Instancia de MeteocatStations
|
|
31
|
+
|
|
32
|
+
async def get_town_stations(self, town_id: str, variable_id: str):
|
|
33
|
+
"""
|
|
34
|
+
Obtiene la lista de estaciones representativas para un municipio y una variable específica,
|
|
35
|
+
enriqueciendo los datos con el nombre de las estaciones.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
codi_municipi (str): Código del municipio.
|
|
39
|
+
codi_variable (str): Código de la variable.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
list: Datos de las estaciones representativas con nombres añadidos.
|
|
43
|
+
"""
|
|
44
|
+
# Obtener la lista completa de estaciones
|
|
45
|
+
all_stations = await self.stations_service.get_stations()
|
|
46
|
+
|
|
47
|
+
# Crear un diccionario para acceder rápidamente a los nombres por código
|
|
48
|
+
station_names = {station["codi"]: station["nom"] for station in all_stations}
|
|
49
|
+
|
|
50
|
+
# URL para obtener las estaciones del municipio y la variable
|
|
51
|
+
url = f"{BASE_URL}{STATIONS_MUNICIPI_URL}".format(
|
|
52
|
+
codi_municipi=town_id, codi_variable=variable_id
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
async with aiohttp.ClientSession() as session:
|
|
56
|
+
try:
|
|
57
|
+
async with session.get(url, headers=self.headers) as response:
|
|
58
|
+
if response.status == 200:
|
|
59
|
+
data = await response.json()
|
|
60
|
+
|
|
61
|
+
# Enriquecer el JSON con los nombres de las estaciones
|
|
62
|
+
for town in data:
|
|
63
|
+
for variable in town.get("variables", []):
|
|
64
|
+
for station in variable.get("estacions", []):
|
|
65
|
+
codi = station["codi"]
|
|
66
|
+
station["nom"] = station_names.get(codi, "Nombre desconocido")
|
|
67
|
+
|
|
68
|
+
return data
|
|
69
|
+
|
|
70
|
+
# Gestionar errores según el código de estado
|
|
71
|
+
if response.status == 400:
|
|
72
|
+
raise BadRequestError(await response.json())
|
|
73
|
+
elif response.status == 403:
|
|
74
|
+
error_data = await response.json()
|
|
75
|
+
if error_data.get("message") == "Forbidden":
|
|
76
|
+
raise ForbiddenError(error_data)
|
|
77
|
+
elif error_data.get("message") == "Missing Authentication Token":
|
|
78
|
+
raise ForbiddenError(error_data)
|
|
79
|
+
elif response.status == 429:
|
|
80
|
+
raise TooManyRequestsError(await response.json())
|
|
81
|
+
elif response.status == 500:
|
|
82
|
+
raise InternalServerError(await response.json())
|
|
83
|
+
else:
|
|
84
|
+
raise UnknownAPIError(
|
|
85
|
+
f"Unexpected error {response.status}: {await response.text()}"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
except aiohttp.ClientError as e:
|
|
89
|
+
raise UnknownAPIError(
|
|
90
|
+
message=f"Error al conectar con la API de Meteocat: {str(e)}",
|
|
91
|
+
status_code=0,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
except Exception as ex:
|
|
95
|
+
raise UnknownAPIError(
|
|
96
|
+
message=f"Error inesperado: {str(ex)}",
|
|
97
|
+
status_code=0,
|
|
98
|
+
)
|
|
99
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
from diskcache import Cache
|
|
3
|
+
from .const import BASE_URL, VARIABLES_URL
|
|
4
|
+
from .exceptions import BadRequestError, ForbiddenError, TooManyRequestsError, InternalServerError, UnknownAPIError
|
|
5
|
+
|
|
6
|
+
class MeteocatVariables:
|
|
7
|
+
"""Clase para interactuar con la lista de variables de la API de Meteocat."""
|
|
8
|
+
|
|
9
|
+
_cache = Cache(".meteocat_cache") # Directorio donde se guardará la caché
|
|
10
|
+
|
|
11
|
+
def __init__(self, api_key: str):
|
|
12
|
+
"""
|
|
13
|
+
Inicializa la clase MeteocatVariables.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
api_key (str): Clave de API para autenticar las solicitudes.
|
|
17
|
+
"""
|
|
18
|
+
self.api_key = api_key
|
|
19
|
+
self.headers = {
|
|
20
|
+
"Content-Type": "application/json",
|
|
21
|
+
"X-Api-Key": self.api_key,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async def get_variables(self, force_update=False):
|
|
25
|
+
"""
|
|
26
|
+
Obtiene la lista de variables desde la API de Meteocat. Usa la caché si está disponible.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
force_update (bool): Si es True, fuerza la actualización desde la API.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
list: Datos de las variables.
|
|
33
|
+
"""
|
|
34
|
+
# Verificar si las variables están en caché y no se solicita actualización forzada
|
|
35
|
+
if not force_update and "variables" in self._cache:
|
|
36
|
+
return self._cache["variables"]
|
|
37
|
+
|
|
38
|
+
# Hacer la solicitud a la API para obtener las variables
|
|
39
|
+
url = f"{BASE_URL}{VARIABLES_URL}"
|
|
40
|
+
async with aiohttp.ClientSession() as session:
|
|
41
|
+
try:
|
|
42
|
+
async with session.get(url, headers=self.headers) as response:
|
|
43
|
+
if response.status == 200:
|
|
44
|
+
variables = await response.json()
|
|
45
|
+
self._cache["variables"] = variables # Guardar en caché
|
|
46
|
+
return variables
|
|
47
|
+
|
|
48
|
+
# Gestionar errores según el código de estado
|
|
49
|
+
if response.status == 400:
|
|
50
|
+
raise BadRequestError(await response.json())
|
|
51
|
+
elif response.status == 403:
|
|
52
|
+
error_data = await response.json()
|
|
53
|
+
if error_data.get("message") == "Forbidden":
|
|
54
|
+
raise ForbiddenError(error_data)
|
|
55
|
+
elif error_data.get("message") == "Missing Authentication Token":
|
|
56
|
+
raise ForbiddenError(error_data)
|
|
57
|
+
elif response.status == 429:
|
|
58
|
+
raise TooManyRequestsError(await response.json())
|
|
59
|
+
elif response.status == 500:
|
|
60
|
+
raise InternalServerError(await response.json())
|
|
61
|
+
else:
|
|
62
|
+
raise UnknownAPIError(f"Unexpected error {response.status}: {await response.text()}")
|
|
63
|
+
|
|
64
|
+
except aiohttp.ClientError as e:
|
|
65
|
+
raise UnknownAPIError(
|
|
66
|
+
message=f"Error al conectar con la API de Meteocat: {str(e)}",
|
|
67
|
+
status_code=0,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
except Exception as ex:
|
|
71
|
+
raise UnknownAPIError(
|
|
72
|
+
message=f"Error inesperado: {str(ex)}",
|
|
73
|
+
status_code=0,
|
|
74
|
+
)
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "meteocatpy",
|
|
3
|
+
"version": "0.0.7",
|
|
4
|
+
"description": "[](https://opensource.org/licenses/Apache-2.0)\r [](https://pypi.org/project/meteocatpy)\r [](https://gitlab.com/figorr/meteocatpy/commits/master)",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"directories": {
|
|
7
|
+
"example": "examples",
|
|
8
|
+
"test": "tests"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
18
|
+
"@semantic-release/exec": "^6.0.3",
|
|
19
|
+
"@semantic-release/git": "^10.0.1",
|
|
20
|
+
"@semantic-release/github": "^11.0.1",
|
|
21
|
+
"semantic-release": "^24.2.0"
|
|
22
|
+
}
|
|
23
|
+
}
|