meteocatpy 0.0.18 → 0.0.21
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/CHANGELOG.md +23 -2
- package/filetree.txt +2 -0
- package/meteocatpy/const.py +1 -0
- package/meteocatpy/lightning.py +136 -0
- package/meteocatpy/version.py +1 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/tests/integration_test_lightning.py +36 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
|
+
## [0.0.21](https://github.com/figorr/meteocatpy/compare/v0.0.20...v0.0.21) (2025-02-04)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* 1.0.0 ([c237e77](https://github.com/figorr/meteocatpy/commit/c237e7736f3504f19a1bb2e39ed7edd3264de115))
|
|
7
|
+
|
|
8
|
+
## [0.0.20](https://github.com/figorr/meteocatpy/compare/v0.0.19...v0.0.20) (2025-01-08)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* 0.0.20 ([8665c69](https://github.com/figorr/meteocatpy/commit/8665c696476d2a074673c4fc56d35aed61c68559))
|
|
14
|
+
* Update CHANGELOG.md ([495220e](https://github.com/figorr/meteocatpy/commit/495220ef4bc01393a1d1fc5506808ffceac9d80c))
|
|
15
|
+
|
|
16
|
+
## [0.0.19](https://github.com/figorr/meteocatpy/compare/v0.0.18...v0.0.19) (2025-01-08)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* 0.0.19 ([2884a6f](https://github.com/figorr/meteocatpy/commit/2884a6ff7216f412e5144ba8a89a300f6b0a42c2))
|
|
22
|
+
* fix timedelta ([dc716ad](https://github.com/figorr/meteocatpy/commit/dc716ad3843f1e138ccab6ac7e01d406e13cf4a5))
|
|
23
|
+
|
|
1
24
|
## [0.0.18](https://github.com/figorr/meteocatpy/compare/v0.0.17...v0.0.18) (2025-01-08)
|
|
2
25
|
|
|
3
26
|
|
|
4
27
|
### Bug Fixes
|
|
5
28
|
|
|
6
29
|
* 0.0.18 ([bdc81d2](https://github.com/figorr/meteocatpy/commit/bdc81d2c4ada74478e66d376962bd2e7b01fe39b))
|
|
7
|
-
* 0.0.19 ([2884a6f](https://github.com/figorr/meteocatpy/commit/2884a6ff7216f412e5144ba8a89a300f6b0a42c2))
|
|
8
30
|
* fix error variable null ([cba0f9d](https://github.com/figorr/meteocatpy/commit/cba0f9d80c1b2a6e6f3ab8660b7ae703bfe4c148))
|
|
9
|
-
* fix timedelta ([dc716ad](https://github.com/figorr/meteocatpy/commit/dc716ad3843f1e138ccab6ac7e01d406e13cf4a5))
|
|
10
31
|
|
|
11
32
|
## [0.0.17](https://github.com/figorr/meteocatpy/compare/v0.0.16...v0.0.17) (2024-12-29)
|
|
12
33
|
|
package/filetree.txt
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
├── forecast.py
|
|
23
23
|
├── helpers.py
|
|
24
24
|
├── infostation.py
|
|
25
|
+
├── lightning.py
|
|
25
26
|
├── py.typed
|
|
26
27
|
├── quotes.py
|
|
27
28
|
├── stations.py
|
|
@@ -46,6 +47,7 @@
|
|
|
46
47
|
├── integration_test_complete.py
|
|
47
48
|
├── integration_test_forecast.py
|
|
48
49
|
├── integration_test_infostation.py
|
|
50
|
+
├── integration_test_lightning.py
|
|
49
51
|
├── integration_test_quotes.py
|
|
50
52
|
├── integration_test_station_data.py
|
|
51
53
|
├── integration_test_stations.py
|
package/meteocatpy/const.py
CHANGED
|
@@ -12,3 +12,4 @@ VARIABLES_URL = "/xema/v1/variables/mesurades/metadades"
|
|
|
12
12
|
STATION_DATA_URL = "/xema/v1/estacions/mesurades/{codiEstacio}/{any}/{mes}/{dia}"
|
|
13
13
|
UVI_DATA_URL = "/pronostic/v1/uvi/{codi_municipi}"
|
|
14
14
|
ALERTS_URL = "/pronostic/v2/smp/episodis-oberts?data={any}-{mes}-{dia}Z"
|
|
15
|
+
LIGHTNING_URL = "/xdde/v1/informes/comarques/{codi_comarca}/{any}/{mes}/{dia}"
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
from .variables import MeteocatVariables
|
|
6
|
+
from .const import BASE_URL, LIGHTNING_URL
|
|
7
|
+
from .exceptions import (
|
|
8
|
+
BadRequestError,
|
|
9
|
+
ForbiddenError,
|
|
10
|
+
TooManyRequestsError,
|
|
11
|
+
InternalServerError,
|
|
12
|
+
UnknownAPIError,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
_LOGGER = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
class MeteocatLightningData:
|
|
18
|
+
"""Clase para interactuar con los datos de rayos de la API de Meteocat."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, api_key: str):
|
|
21
|
+
"""
|
|
22
|
+
Inicializa la clase MeteocatStationData.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
api_key (str): Clave de API para autenticar las solicitudes.
|
|
26
|
+
"""
|
|
27
|
+
self.api_key = api_key
|
|
28
|
+
self.headers = {
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
"X-Api-Key": self.api_key,
|
|
31
|
+
}
|
|
32
|
+
self.variables = MeteocatVariables(api_key)
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def get_current_date():
|
|
36
|
+
"""
|
|
37
|
+
Obtiene la fecha actual en formato numérico.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
tuple: Año (YYYY), mes (MM), día (DD) como enteros.
|
|
41
|
+
"""
|
|
42
|
+
now = datetime.now()
|
|
43
|
+
return now.year, now.month, now.day
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def get_previous_date():
|
|
47
|
+
"""
|
|
48
|
+
Obtiene la fecha del día anterior en formato numérico.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
tuple: Año (YYYY), mes (MM), día (DD) como enteros.
|
|
52
|
+
"""
|
|
53
|
+
yesterday = datetime.now() - timedelta(days=1)
|
|
54
|
+
return yesterday.year, yesterday.month, yesterday.day
|
|
55
|
+
|
|
56
|
+
async def get_lightning_data(self, region_id: str):
|
|
57
|
+
"""
|
|
58
|
+
Obtiene los datos meteorológicos de rayos desde la API de Meteocat.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
region_id (str): Código de la comarca.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
dict: Datos de rayos de la comarca.
|
|
65
|
+
"""
|
|
66
|
+
any, mes, dia = self.get_current_date() # Calcula la fecha actual
|
|
67
|
+
url = f"{BASE_URL}{LIGHTNING_URL}".format(
|
|
68
|
+
codi_comarca=region_id, any=any, mes=f"{mes:02d}", dia=f"{dia:02d}"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
async with aiohttp.ClientSession() as session:
|
|
72
|
+
try:
|
|
73
|
+
async with session.get(url, headers=self.headers) as response:
|
|
74
|
+
if response.status == 200:
|
|
75
|
+
return await response.json()
|
|
76
|
+
|
|
77
|
+
# Gestionar errores según el código de estado
|
|
78
|
+
if response.status == 400:
|
|
79
|
+
error_data = await response.json()
|
|
80
|
+
# Filtrar si el mensaje contiene una fecha válida
|
|
81
|
+
if "message" in error_data:
|
|
82
|
+
match = re.search(r'entre (\d{2}-\d{2}-\d{4}) i', error_data["message"])
|
|
83
|
+
if match:
|
|
84
|
+
last_valid_date = match.group(1) # Captura la última fecha válida
|
|
85
|
+
dia, mes, any = map(int, last_valid_date.split('-'))
|
|
86
|
+
# Intentar nuevamente con la última fecha válida
|
|
87
|
+
new_url = f"{BASE_URL}{LIGHTNING_URL}".format(
|
|
88
|
+
codi_comarca=region_id, any=any, mes=f"{mes:02d}", dia=f"{dia:02d}"
|
|
89
|
+
)
|
|
90
|
+
async with session.get(new_url, headers=self.headers) as new_response:
|
|
91
|
+
if new_response.status == 200:
|
|
92
|
+
return await new_response.json()
|
|
93
|
+
raise UnknownAPIError(
|
|
94
|
+
f"Failed with the valid date {last_valid_date}: {await new_response.text()}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Filtrar si el mensaje es "La comarca '<código>' no mesura la variable 'null'"
|
|
98
|
+
if re.search(r"La comarca '.*?' no mesura la variable 'null'", error_data.get("message", "")):
|
|
99
|
+
any, mes, dia = self.get_previous_date() # Obtener la fecha del día anterior
|
|
100
|
+
new_url = f"{BASE_URL}{LIGHTNING_URL}".format(
|
|
101
|
+
codi_comarca=region_id, any=any, mes=f"{mes:02d}", dia=f"{dia:02d}"
|
|
102
|
+
)
|
|
103
|
+
async with session.get(new_url, headers=self.headers) as new_response:
|
|
104
|
+
if new_response.status == 200:
|
|
105
|
+
return await new_response.json()
|
|
106
|
+
raise UnknownAPIError(
|
|
107
|
+
f"Failed with the previous date: {await new_response.text()}"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
raise BadRequestError(await response.json())
|
|
111
|
+
elif response.status == 403:
|
|
112
|
+
error_data = await response.json()
|
|
113
|
+
if error_data.get("message") == "Forbidden":
|
|
114
|
+
raise ForbiddenError(error_data)
|
|
115
|
+
elif error_data.get("message") == "Missing Authentication Token":
|
|
116
|
+
raise ForbiddenError(error_data)
|
|
117
|
+
elif response.status == 429:
|
|
118
|
+
raise TooManyRequestsError(await response.json())
|
|
119
|
+
elif response.status == 500:
|
|
120
|
+
raise InternalServerError(await response.json())
|
|
121
|
+
else:
|
|
122
|
+
raise UnknownAPIError(
|
|
123
|
+
f"Unexpected error {response.status}: {await response.text()}"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
except aiohttp.ClientError as e:
|
|
127
|
+
raise UnknownAPIError(
|
|
128
|
+
message=f"Error al conectar con la API de Meteocat: {str(e)}",
|
|
129
|
+
status_code=0,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
except Exception as ex:
|
|
133
|
+
raise UnknownAPIError(
|
|
134
|
+
message=f"Error inesperado: {str(ex)}",
|
|
135
|
+
status_code=0,
|
|
136
|
+
)
|
package/meteocatpy/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# version.py
|
|
2
|
-
__version__ = "0.0
|
|
2
|
+
__version__ = "1.0.0"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meteocatpy",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"description": "[](https://opensource.org/licenses/Apache-2.0)\r [](https://pypi.org/project/meteocatpy)\r [](https://gitlab.com/figorr/meteocatpy/commits/master)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
package/pyproject.toml
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
import json
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
from meteocatpy.lightning import MeteocatLightningData
|
|
6
|
+
|
|
7
|
+
# Cargar variables desde el archivo .env
|
|
8
|
+
load_dotenv()
|
|
9
|
+
|
|
10
|
+
# Obtener los valores del archivo .env
|
|
11
|
+
API_KEY = os.getenv("METEOCAT_API_KEY_TEST")
|
|
12
|
+
REGION_CODI_TEST = os.getenv("REGION_CODI_TEST")
|
|
13
|
+
|
|
14
|
+
# Asegúrate de que las variables estén definidas
|
|
15
|
+
assert API_KEY, "API Key is required"
|
|
16
|
+
assert REGION_CODI_TEST, "Region codi test is required"
|
|
17
|
+
|
|
18
|
+
@pytest.mark.asyncio
|
|
19
|
+
async def test_lightning():
|
|
20
|
+
# Crear una instancia de MeteocatLightningData con la API Key
|
|
21
|
+
lightning_client = MeteocatLightningData(API_KEY)
|
|
22
|
+
|
|
23
|
+
# Obtener los datos de rayos
|
|
24
|
+
lightning_data = await lightning_client.get_lightning_data(REGION_CODI_TEST)
|
|
25
|
+
|
|
26
|
+
print("Datos de la API:", lightning_data) # Para depuración
|
|
27
|
+
|
|
28
|
+
# Crear la carpeta si no existe
|
|
29
|
+
os.makedirs('tests/files', exist_ok=True)
|
|
30
|
+
|
|
31
|
+
# Guardar los datos de rayos en un archivo JSON
|
|
32
|
+
with open(f'tests/files/lightning_{REGION_CODI_TEST}_data.json', 'w', encoding='utf-8') as f:
|
|
33
|
+
json.dump(lightning_data, f, ensure_ascii=False, indent=4)
|
|
34
|
+
|
|
35
|
+
# Verificar que los datos de rayos sean una lista
|
|
36
|
+
assert isinstance(lightning_data, list), "La respuesta no es una lista"
|