meteocatpy 0.0.7 → 0.0.8

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.
Files changed (40) hide show
  1. package/.github/workflows/release.yml +33 -33
  2. package/.gitlab-ci.yml +46 -46
  3. package/.pre-commit-config.yaml +37 -37
  4. package/.releaserc +23 -23
  5. package/.releaserc.toml +14 -14
  6. package/AUTHORS.md +12 -12
  7. package/CHANGELOG.md +145 -137
  8. package/README.md +61 -61
  9. package/filetree.py +48 -48
  10. package/filetree.txt +48 -48
  11. package/meteocatpy/README.md +61 -61
  12. package/meteocatpy/__init__.py +27 -27
  13. package/meteocatpy/const.py +10 -10
  14. package/meteocatpy/data.py +93 -140
  15. package/meteocatpy/exceptions.py +35 -35
  16. package/meteocatpy/forecast.py +137 -137
  17. package/meteocatpy/helpers.py +46 -46
  18. package/meteocatpy/stations.py +71 -71
  19. package/meteocatpy/symbols.py +89 -89
  20. package/meteocatpy/town.py +61 -61
  21. package/meteocatpy/townstations.py +99 -99
  22. package/meteocatpy/variables.py +74 -74
  23. package/meteocatpy/version.py +2 -2
  24. package/package.json +23 -23
  25. package/poetry.lock +3313 -3313
  26. package/pyproject.toml +72 -72
  27. package/releaserc.json +17 -17
  28. package/requirements.test.txt +3 -3
  29. package/setup.cfg +64 -64
  30. package/setup.py +10 -10
  31. package/tests/data_test.py +122 -122
  32. package/tests/import_test.py +18 -18
  33. package/tests/integration_test_complete.py +76 -76
  34. package/tests/integration_test_forecast.py +54 -54
  35. package/tests/integration_test_station_data.py +33 -33
  36. package/tests/integration_test_stations.py +31 -31
  37. package/tests/integration_test_symbols.py +68 -68
  38. package/tests/integration_test_town.py +32 -32
  39. package/tests/integration_test_town_stations.py +36 -36
  40. package/tests/integration_test_variables.py +32 -32
@@ -1,46 +1,46 @@
1
- """Meteocat Helpers."""
2
-
3
- from datetime import datetime
4
- from typing import Any
5
- import re
6
- import unicodedata
7
- from zoneinfo import ZoneInfo
8
-
9
- TZ_UTC = ZoneInfo("UTC")
10
-
11
-
12
- def dict_nested_value(data: dict[str, Any] | None, keys: list[str] | None) -> Any:
13
- """Get value from dict with nested keys."""
14
- if keys is None or len(keys) == 0:
15
- return None
16
- for key in keys or {}:
17
- if data is not None:
18
- data = data.get(key)
19
- return data
20
-
21
-
22
- def get_current_datetime(tz: ZoneInfo = TZ_UTC, replace: bool = True) -> datetime:
23
- """Return current datetime in UTC."""
24
- cur_dt = datetime.now(tz=tz)
25
- if replace:
26
- cur_dt = cur_dt.replace(minute=0, second=0, microsecond=0)
27
- return cur_dt
28
-
29
-
30
- def parse_api_timestamp(timestamp: str, tz: ZoneInfo = TZ_UTC) -> datetime:
31
- """Parse API timestamp into datetime."""
32
- return datetime.fromisoformat(timestamp).replace(tzinfo=tz)
33
-
34
-
35
- def slugify(value: str, allow_unicode: bool = False) -> str:
36
- """Convert string to a valid file name."""
37
- if allow_unicode:
38
- value = unicodedata.normalize("NFKC", value)
39
- else:
40
- value = (
41
- unicodedata.normalize("NFKD", value)
42
- .encode("ascii", "ignore")
43
- .decode("ascii")
44
- )
45
- value = re.sub(r"[^\w\s]", "-", value.lower())
46
- return re.sub(r"[-\s]+", "-", value).strip("-_")
1
+ """Meteocat Helpers."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+ import re
6
+ import unicodedata
7
+ from zoneinfo import ZoneInfo
8
+
9
+ TZ_UTC = ZoneInfo("UTC")
10
+
11
+
12
+ def dict_nested_value(data: dict[str, Any] | None, keys: list[str] | None) -> Any:
13
+ """Get value from dict with nested keys."""
14
+ if keys is None or len(keys) == 0:
15
+ return None
16
+ for key in keys or {}:
17
+ if data is not None:
18
+ data = data.get(key)
19
+ return data
20
+
21
+
22
+ def get_current_datetime(tz: ZoneInfo = TZ_UTC, replace: bool = True) -> datetime:
23
+ """Return current datetime in UTC."""
24
+ cur_dt = datetime.now(tz=tz)
25
+ if replace:
26
+ cur_dt = cur_dt.replace(minute=0, second=0, microsecond=0)
27
+ return cur_dt
28
+
29
+
30
+ def parse_api_timestamp(timestamp: str, tz: ZoneInfo = TZ_UTC) -> datetime:
31
+ """Parse API timestamp into datetime."""
32
+ return datetime.fromisoformat(timestamp).replace(tzinfo=tz)
33
+
34
+
35
+ def slugify(value: str, allow_unicode: bool = False) -> str:
36
+ """Convert string to a valid file name."""
37
+ if allow_unicode:
38
+ value = unicodedata.normalize("NFKC", value)
39
+ else:
40
+ value = (
41
+ unicodedata.normalize("NFKD", value)
42
+ .encode("ascii", "ignore")
43
+ .decode("ascii")
44
+ )
45
+ value = re.sub(r"[^\w\s]", "-", value.lower())
46
+ return re.sub(r"[-\s]+", "-", value).strip("-_")
@@ -1,71 +1,71 @@
1
- import aiohttp
2
- from .const import BASE_URL, STATIONS_LIST_URL
3
- from .exceptions import (
4
- BadRequestError,
5
- ForbiddenError,
6
- TooManyRequestsError,
7
- InternalServerError,
8
- UnknownAPIError
9
- )
10
-
11
- class MeteocatStations:
12
- """
13
- Clase para interactuar con la API de Meteocat y obtener la lista de todas las estaciones."""
14
-
15
- def __init__(self, api_key: str):
16
- """
17
- Inicializa la clase MeteocatStations.
18
-
19
- Args:
20
- api_key (str): Clave de API para autenticar las solicitudes.
21
- """
22
- self.api_key = api_key
23
- self.headers = {
24
- "Content-Type": "application/json",
25
- "X-Api-Key": self.api_key,
26
- }
27
-
28
- async def get_stations(self):
29
- """
30
- Obtiene la lista de estaciones.
31
-
32
- Returns:
33
- dict: Lista de estaciones.
34
- """
35
- url = f"{BASE_URL}{STATIONS_LIST_URL}"
36
- async with aiohttp.ClientSession() as session:
37
- try:
38
- async with session.get(url, headers=self.headers) as response:
39
- if response.status == 200:
40
- return await response.json()
41
-
42
- # Gestionar errores según el código de estado
43
- if response.status == 400:
44
- raise BadRequestError(await response.json())
45
- elif response.status == 403:
46
- error_data = await response.json()
47
- if error_data.get("message") == "Forbidden":
48
- raise ForbiddenError(error_data)
49
- elif error_data.get("message") == "Missing Authentication Token":
50
- raise ForbiddenError(error_data)
51
- elif response.status == 429:
52
- raise TooManyRequestsError(await response.json())
53
- elif response.status == 500:
54
- raise InternalServerError(await response.json())
55
- else:
56
- raise UnknownAPIError(
57
- f"Unexpected error {response.status}: {await response.text()}"
58
- )
59
-
60
- except aiohttp.ClientError as e:
61
- raise UnknownAPIError(
62
- message=f"Error al conectar con la API de Meteocat: {str(e)}",
63
- status_code=0,
64
- )
65
-
66
- except Exception as ex:
67
- raise UnknownAPIError(
68
- message=f"Error inesperado: {str(ex)}",
69
- status_code=0,
70
- )
71
-
1
+ import aiohttp
2
+ from .const import BASE_URL, STATIONS_LIST_URL
3
+ from .exceptions import (
4
+ BadRequestError,
5
+ ForbiddenError,
6
+ TooManyRequestsError,
7
+ InternalServerError,
8
+ UnknownAPIError
9
+ )
10
+
11
+ class MeteocatStations:
12
+ """
13
+ Clase para interactuar con la API de Meteocat y obtener la lista de todas las estaciones."""
14
+
15
+ def __init__(self, api_key: str):
16
+ """
17
+ Inicializa la clase MeteocatStations.
18
+
19
+ Args:
20
+ api_key (str): Clave de API para autenticar las solicitudes.
21
+ """
22
+ self.api_key = api_key
23
+ self.headers = {
24
+ "Content-Type": "application/json",
25
+ "X-Api-Key": self.api_key,
26
+ }
27
+
28
+ async def get_stations(self):
29
+ """
30
+ Obtiene la lista de estaciones.
31
+
32
+ Returns:
33
+ dict: Lista de estaciones.
34
+ """
35
+ url = f"{BASE_URL}{STATIONS_LIST_URL}"
36
+ async with aiohttp.ClientSession() as session:
37
+ try:
38
+ async with session.get(url, headers=self.headers) as response:
39
+ if response.status == 200:
40
+ return await response.json()
41
+
42
+ # Gestionar errores según el código de estado
43
+ if response.status == 400:
44
+ raise BadRequestError(await response.json())
45
+ elif response.status == 403:
46
+ error_data = await response.json()
47
+ if error_data.get("message") == "Forbidden":
48
+ raise ForbiddenError(error_data)
49
+ elif error_data.get("message") == "Missing Authentication Token":
50
+ raise ForbiddenError(error_data)
51
+ elif response.status == 429:
52
+ raise TooManyRequestsError(await response.json())
53
+ elif response.status == 500:
54
+ raise InternalServerError(await response.json())
55
+ else:
56
+ raise UnknownAPIError(
57
+ f"Unexpected error {response.status}: {await response.text()}"
58
+ )
59
+
60
+ except aiohttp.ClientError as e:
61
+ raise UnknownAPIError(
62
+ message=f"Error al conectar con la API de Meteocat: {str(e)}",
63
+ status_code=0,
64
+ )
65
+
66
+ except Exception as ex:
67
+ raise UnknownAPIError(
68
+ message=f"Error inesperado: {str(ex)}",
69
+ status_code=0,
70
+ )
71
+
@@ -1,89 +1,89 @@
1
- import aiohttp
2
- from .const import BASE_URL, SYMBOLS_URL
3
- from .exceptions import BadRequestError, ForbiddenError, TooManyRequestsError, InternalServerError, UnknownAPIError
4
-
5
-
6
- class MeteocatSymbols:
7
- """Clase para interactuar con la API de símbolos de Meteocat."""
8
-
9
- def __init__(self, api_key: str):
10
- """
11
- Inicializa la clase MeteocatSymbols.
12
-
13
- Args:
14
- api_key (str): Clave de API para autenticar las solicitudes.
15
- """
16
- self.api_key = api_key
17
- self.headers = {
18
- "Content-Type": "application/json",
19
- "X-Api-Key": self.api_key,
20
- }
21
- self.symbols_map = {}
22
-
23
- async def fetch_symbols(self):
24
- url = f"{BASE_URL}{SYMBOLS_URL}"
25
- async with aiohttp.ClientSession() as session:
26
- try:
27
- async with session.get(url, headers=self.headers) as response:
28
- if response.status == 200:
29
- data = await response.json()
30
- print(data) # Esto te mostrará la estructura completa en la consola
31
-
32
- # Asegurarse de que `data` sea una lista de categorías
33
- if isinstance(data, list):
34
- self.symbols_map = {}
35
- for category in data:
36
- if "valors" in category:
37
- # Guardamos los valores de cada categoría
38
- self.symbols_map[category["nom"]] = category["valors"]
39
- return data # Devolvemos todo el conjunto de datos
40
-
41
- else:
42
- raise UnknownAPIError(f"Unexpected structure of data: {data}", status_code=response.status)
43
-
44
- # Gestionar errores de respuesta
45
- if response.status == 400:
46
- raise BadRequestError(await response.json())
47
- elif response.status == 403:
48
- error_data = await response.json()
49
- if error_data.get("message") == "Forbidden":
50
- raise ForbiddenError(error_data)
51
- elif error_data.get("message") == "Missing Authentication Token":
52
- raise ForbiddenError(error_data)
53
- elif response.status == 429:
54
- raise TooManyRequestsError(await response.json())
55
- elif response.status == 500:
56
- raise InternalServerError(await response.json())
57
- else:
58
- raise UnknownAPIError(f"Unexpected error {response.status}: {await response.text()}", status_code=response.status)
59
-
60
- except aiohttp.ClientError as e:
61
- raise UnknownAPIError(
62
- message=f"Error al conectar con la API de Meteocat: {str(e)}",
63
- status_code=0,
64
- )
65
-
66
- except Exception as ex:
67
- raise UnknownAPIError(
68
- message=f"Error inesperado: {str(ex)}",
69
- status_code=0,
70
- )
71
-
72
-
73
-
74
- def get_description(self, category: str, code: int) -> str:
75
- """
76
- Obtiene la descripción de un código de símbolo dentro de una categoría.
77
-
78
- Args:
79
- category (str): Nombre de la categoría (e.g., "cel").
80
- code (int): Código del símbolo.
81
-
82
- Returns:
83
- str: Descripción del símbolo. Retorna 'Desconocido' si el código no está en el mapeo.
84
- """
85
- category_symbols = self.symbols_map.get(category, [])
86
- for symbol in category_symbols:
87
- if symbol["codi"] == str(code): # El código es devuelto como string por la API
88
- return symbol["descripcio"]
89
- return "Desconocido"
1
+ import aiohttp
2
+ from .const import BASE_URL, SYMBOLS_URL
3
+ from .exceptions import BadRequestError, ForbiddenError, TooManyRequestsError, InternalServerError, UnknownAPIError
4
+
5
+
6
+ class MeteocatSymbols:
7
+ """Clase para interactuar con la API de símbolos de Meteocat."""
8
+
9
+ def __init__(self, api_key: str):
10
+ """
11
+ Inicializa la clase MeteocatSymbols.
12
+
13
+ Args:
14
+ api_key (str): Clave de API para autenticar las solicitudes.
15
+ """
16
+ self.api_key = api_key
17
+ self.headers = {
18
+ "Content-Type": "application/json",
19
+ "X-Api-Key": self.api_key,
20
+ }
21
+ self.symbols_map = {}
22
+
23
+ async def fetch_symbols(self):
24
+ url = f"{BASE_URL}{SYMBOLS_URL}"
25
+ async with aiohttp.ClientSession() as session:
26
+ try:
27
+ async with session.get(url, headers=self.headers) as response:
28
+ if response.status == 200:
29
+ data = await response.json()
30
+ print(data) # Esto te mostrará la estructura completa en la consola
31
+
32
+ # Asegurarse de que `data` sea una lista de categorías
33
+ if isinstance(data, list):
34
+ self.symbols_map = {}
35
+ for category in data:
36
+ if "valors" in category:
37
+ # Guardamos los valores de cada categoría
38
+ self.symbols_map[category["nom"]] = category["valors"]
39
+ return data # Devolvemos todo el conjunto de datos
40
+
41
+ else:
42
+ raise UnknownAPIError(f"Unexpected structure of data: {data}", status_code=response.status)
43
+
44
+ # Gestionar errores de respuesta
45
+ if response.status == 400:
46
+ raise BadRequestError(await response.json())
47
+ elif response.status == 403:
48
+ error_data = await response.json()
49
+ if error_data.get("message") == "Forbidden":
50
+ raise ForbiddenError(error_data)
51
+ elif error_data.get("message") == "Missing Authentication Token":
52
+ raise ForbiddenError(error_data)
53
+ elif response.status == 429:
54
+ raise TooManyRequestsError(await response.json())
55
+ elif response.status == 500:
56
+ raise InternalServerError(await response.json())
57
+ else:
58
+ raise UnknownAPIError(f"Unexpected error {response.status}: {await response.text()}", status_code=response.status)
59
+
60
+ except aiohttp.ClientError as e:
61
+ raise UnknownAPIError(
62
+ message=f"Error al conectar con la API de Meteocat: {str(e)}",
63
+ status_code=0,
64
+ )
65
+
66
+ except Exception as ex:
67
+ raise UnknownAPIError(
68
+ message=f"Error inesperado: {str(ex)}",
69
+ status_code=0,
70
+ )
71
+
72
+
73
+
74
+ def get_description(self, category: str, code: int) -> str:
75
+ """
76
+ Obtiene la descripción de un código de símbolo dentro de una categoría.
77
+
78
+ Args:
79
+ category (str): Nombre de la categoría (e.g., "cel").
80
+ code (int): Código del símbolo.
81
+
82
+ Returns:
83
+ str: Descripción del símbolo. Retorna 'Desconocido' si el código no está en el mapeo.
84
+ """
85
+ category_symbols = self.symbols_map.get(category, [])
86
+ for symbol in category_symbols:
87
+ if symbol["codi"] == str(code): # El código es devuelto como string por la API
88
+ return symbol["descripcio"]
89
+ return "Desconocido"
@@ -1,61 +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
- )
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
+ )