meteocat 0.1.25 → 0.1.27

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 (35) hide show
  1. package/.github/workflows/release.yml +33 -33
  2. package/.pre-commit-config.yaml +37 -37
  3. package/.releaserc +23 -23
  4. package/.releaserc.toml +14 -14
  5. package/AUTHORS.md +12 -12
  6. package/CHANGELOG.md +430 -401
  7. package/README.md +40 -40
  8. package/custom_components/meteocat/__init__.py +136 -107
  9. package/custom_components/meteocat/condition.py +28 -28
  10. package/custom_components/meteocat/config_flow.py +199 -192
  11. package/custom_components/meteocat/const.py +55 -55
  12. package/custom_components/meteocat/coordinator.py +194 -195
  13. package/custom_components/meteocat/entity.py +98 -98
  14. package/custom_components/meteocat/helpers.py +42 -42
  15. package/custom_components/meteocat/manifest.json +12 -12
  16. package/custom_components/meteocat/options_flow.py +71 -71
  17. package/custom_components/meteocat/sensor.py +325 -303
  18. package/custom_components/meteocat/strings.json +25 -25
  19. package/custom_components/meteocat/translations/ca.json +25 -25
  20. package/custom_components/meteocat/translations/en.json +25 -25
  21. package/custom_components/meteocat/translations/es.json +25 -25
  22. package/custom_components/meteocat/version.py +2 -2
  23. package/filetree.py +48 -48
  24. package/filetree.txt +46 -46
  25. package/hacs.json +5 -5
  26. package/package.json +22 -22
  27. package/poetry.lock +3210 -3216
  28. package/pyproject.toml +64 -64
  29. package/releaserc.json +17 -17
  30. package/requirements.test.txt +3 -3
  31. package/setup.cfg +64 -64
  32. package/setup.py +10 -10
  33. package/tests/bandit.yaml +17 -17
  34. package/tests/conftest.py +19 -19
  35. package/tests/test_init.py +9 -9
package/README.md CHANGED
@@ -1,41 +1,41 @@
1
- # Meteocat for Home Assistant
2
-
3
- [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
- [![Python version compatibility](https://img.shields.io/pypi/pyversions/meteocat)](https://pypi.org/project/meteocat)
5
- [![pipeline status](https://gitlab.com/figorr/meteocat/badges/master/pipeline.svg)](https://gitlab.com/figorr/meteocat/commits/master)
6
-
7
-
8
- This is a project to obtain meteorological data from the Meteocat API.
9
-
10
- **NOTE:** Meteocat API requires to use an API_KEY, you should ask to (https://apidocs.meteocat.gencat.cat/documentacio/acces-ciutada-i-administracio/)
11
-
12
- # Credits
13
-
14
- This is a personal project.
15
-
16
- Authors:
17
- - Figorr
18
-
19
- # Contributing
20
-
21
- 1. [Check for open features/bugs](https://gitlab.com/figorr/meteocat/issues)
22
- or [initiate a discussion on one](https://gitlab.com/figorr/meteocat/issues/new).
23
- 2. [Fork the repository](https://gitlab.com/figorr/meteocat/forks/new).
24
- 3. Install the dev environment: `make init`.
25
- 4. Enter the virtual environment: `pipenv shell`
26
- 5. Code your new feature or bug fix.
27
- 6. Write a test that covers your new functionality.
28
- 7. Update `README.md` with any new documentation.
29
- 8. Run tests and ensure 100% code coverage for your contribution: `make coverage`
30
- 9. Ensure you have no linting errors: `make lint`
31
- 10. Ensure you have typed your code correctly: `make typing`
32
- 11. Add yourself to `AUTHORS.md`.
33
- 12. Submit a pull request!
34
-
35
- # License
36
-
37
- [Apache-2.0](LICENSE). By providing a contribution, you agree the contribution is licensed under Apache-2.0.
38
-
39
- # API Reference
40
-
1
+ # Meteocat for Home Assistant
2
+
3
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
+ [![Python version compatibility](https://img.shields.io/pypi/pyversions/meteocat)](https://pypi.org/project/meteocat)
5
+ [![pipeline status](https://gitlab.com/figorr/meteocat/badges/master/pipeline.svg)](https://gitlab.com/figorr/meteocat/commits/master)
6
+
7
+
8
+ This is a project to obtain meteorological data from the Meteocat API.
9
+
10
+ **NOTE:** Meteocat API requires to use an API_KEY, you should ask to (https://apidocs.meteocat.gencat.cat/documentacio/acces-ciutada-i-administracio/)
11
+
12
+ # Credits
13
+
14
+ This is a personal project.
15
+
16
+ Authors:
17
+ - Figorr
18
+
19
+ # Contributing
20
+
21
+ 1. [Check for open features/bugs](https://gitlab.com/figorr/meteocat/issues)
22
+ or [initiate a discussion on one](https://gitlab.com/figorr/meteocat/issues/new).
23
+ 2. [Fork the repository](https://gitlab.com/figorr/meteocat/forks/new).
24
+ 3. Install the dev environment: `make init`.
25
+ 4. Enter the virtual environment: `pipenv shell`
26
+ 5. Code your new feature or bug fix.
27
+ 6. Write a test that covers your new functionality.
28
+ 7. Update `README.md` with any new documentation.
29
+ 8. Run tests and ensure 100% code coverage for your contribution: `make coverage`
30
+ 9. Ensure you have no linting errors: `make lint`
31
+ 10. Ensure you have typed your code correctly: `make typing`
32
+ 11. Add yourself to `AUTHORS.md`.
33
+ 12. Submit a pull request!
34
+
35
+ # License
36
+
37
+ [Apache-2.0](LICENSE). By providing a contribution, you agree the contribution is licensed under Apache-2.0.
38
+
39
+ # API Reference
40
+
41
41
  [See the docs 📚](https://apidocs.meteocat.gencat.cat/section/informacio-general/).
@@ -1,107 +1,136 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- import logging
5
- from homeassistant import core
6
- from homeassistant.config_entries import ConfigEntry
7
- from homeassistant.core import HomeAssistant
8
- from homeassistant.exceptions import HomeAssistantError
9
- from .coordinator import MeteocatSensorCoordinator, MeteocatEntityCoordinator
10
- from .const import DOMAIN, PLATFORMS
11
-
12
- _LOGGER = logging.getLogger(__name__)
13
-
14
- # Versión
15
- __version__ = "0.1.25"
16
-
17
-
18
- async def async_setup(hass: core.HomeAssistant, config: dict) -> bool:
19
- """Configuración inicial del componente Meteocat."""
20
- return True
21
-
22
-
23
- async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
24
- """Configura una entrada de configuración para Meteocat."""
25
- _LOGGER.info("Configurando la integración de Meteocat...")
26
-
27
- # Extraer los datos necesarios de la entrada de configuración
28
- entry_data = entry.data
29
-
30
- # Validar que todos los campos requeridos estén presentes
31
- required_fields = [
32
- "api_key", "town_name", "town_id", "variable_name",
33
- "variable_id", "station_name", "station_id"
34
- ]
35
- if not all(field in entry_data for field in required_fields):
36
- _LOGGER.error("Faltan datos en la configuración. Por favor, reconfigura la integración.")
37
- return False
38
-
39
- _LOGGER.info(
40
- f"Integración configurada para el municipio '{entry_data['town_name']}' "
41
- f"(ID: {entry_data['town_id']}), variable '{entry_data['variable_name']}' "
42
- f"(ID: {entry_data['variable_id']}), estación '{entry_data['station_name']}' "
43
- f"(ID: {entry_data['station_id']})."
44
- )
45
-
46
- # Inicializa y refresca los coordinadores
47
- try:
48
- # Crear el coordinador para sensores
49
- sensor_coordinator = MeteocatSensorCoordinator(
50
- hass=hass,
51
- entry_data=entry_data, # Pasa los datos como un diccionario
52
- )
53
-
54
- # Crear el coordinador para entidades de predicción
55
- entity_coordinator = MeteocatEntityCoordinator(
56
- hass=hass,
57
- entry_data=entry_data, # Pasa los mismos datos
58
- )
59
-
60
- # Ejecutar la primera actualización
61
- await sensor_coordinator.async_config_entry_first_refresh()
62
- await entity_coordinator.async_config_entry_first_refresh()
63
- except HomeAssistantError as err:
64
- _LOGGER.error(f"Error al inicializar los coordinadores: {err}")
65
- return False
66
-
67
- # Guardar los coordinadores en hass.data
68
- hass.data.setdefault(DOMAIN, {})
69
- hass.data[DOMAIN][entry.entry_id] = {
70
- "sensor_coordinator": sensor_coordinator,
71
- "entity_coordinator": entity_coordinator,
72
- **entry_data,
73
- }
74
-
75
- # Configurar las plataformas
76
- await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
77
-
78
- return True
79
-
80
-
81
- async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
82
- """Desactiva una entrada de configuración para Meteocat."""
83
- if entry.entry_id in hass.data.get(DOMAIN, {}):
84
- hass.data[DOMAIN].pop(entry.entry_id, None)
85
- if not hass.data[DOMAIN]: # Si no quedan entradas, elimina el dominio
86
- hass.data.pop(DOMAIN)
87
-
88
- # Desinstalar las plataformas
89
- return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
90
-
91
-
92
- async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
93
- """Limpia cualquier dato adicional al desinstalar la integración."""
94
- _LOGGER.info(f"Eliminando datos residuales de la integración: {entry.entry_id}")
95
-
96
- # Definir la ruta del archivo de símbolos
97
- assets_folder = hass.config.path("custom_components", DOMAIN, "assets")
98
- symbols_file = os.path.join(assets_folder, "symbols.json")
99
-
100
- try:
101
- if os.path.exists(symbols_file):
102
- os.remove(symbols_file)
103
- _LOGGER.info("Archivo symbols.json eliminado correctamente.")
104
- else:
105
- _LOGGER.info("El archivo symbols.json no se encontró.")
106
- except OSError as e:
107
- _LOGGER.error(f"Error al intentar eliminar el archivo symbols.json: {e}")
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import logging
5
+ from homeassistant import core
6
+ from homeassistant.config_entries import ConfigEntry
7
+ from homeassistant.core import HomeAssistant
8
+ from homeassistant.exceptions import HomeAssistantError
9
+ from homeassistant.helpers.entity_platform import async_get_platforms
10
+
11
+ from .coordinator import MeteocatSensorCoordinator #, MeteocatEntityCoordinator
12
+ from .const import DOMAIN, PLATFORMS
13
+
14
+ _LOGGER = logging.getLogger(__name__)
15
+
16
+ # Versión
17
+ __version__ = "0.1.27"
18
+
19
+
20
+ async def async_setup(hass: core.HomeAssistant, config: dict) -> bool:
21
+ """Configuración inicial del componente Meteocat."""
22
+ return True
23
+
24
+
25
+ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
26
+ """Configura una entrada de configuración para Meteocat."""
27
+ _LOGGER.info("Configurando la integración de Meteocat...")
28
+
29
+ # Extraer los datos necesarios de la entrada de configuración
30
+ entry_data = entry.data
31
+
32
+ # Validar campos requeridos
33
+ required_fields = [
34
+ "api_key", "town_name", "town_id", "variable_name",
35
+ "variable_id", "station_name", "station_id"
36
+ ]
37
+ missing_fields = [field for field in required_fields if field not in entry_data]
38
+ if missing_fields:
39
+ _LOGGER.error(f"Faltan los siguientes campos en la configuración: {missing_fields}")
40
+ return False
41
+
42
+ _LOGGER.debug(
43
+ f"Datos de configuración: Municipio '{entry_data['town_name']}' (ID: {entry_data['town_id']}), "
44
+ f"Variable '{entry_data['variable_name']}' (ID: {entry_data['variable_id']}), "
45
+ f"Estación '{entry_data['station_name']}' (ID: {entry_data['station_id']})."
46
+ )
47
+
48
+ # Inicializar coordinadores
49
+ try:
50
+ sensor_coordinator = MeteocatSensorCoordinator(hass=hass, entry_data=entry_data)
51
+ # entity_coordinator = MeteocatEntityCoordinator(hass=hass, entry_data=entry_data)
52
+
53
+ await sensor_coordinator.async_config_entry_first_refresh()
54
+ # await entity_coordinator.async_config_entry_first_refresh()
55
+ except Exception as err: # Capturar todos los errores
56
+ _LOGGER.exception(f"Error al inicializar los coordinadores: {err}")
57
+ return False
58
+
59
+ # Guardar coordinadores y datos en hass.data
60
+ hass.data.setdefault(DOMAIN, {})
61
+ hass.data[DOMAIN][entry.entry_id] = {
62
+ "sensor_coordinator": sensor_coordinator,
63
+ # "entity_coordinator": entity_coordinator,
64
+ **entry_data,
65
+ }
66
+
67
+ # Configurar plataformas
68
+ _LOGGER.debug(f"Cargando plataformas: {PLATFORMS}")
69
+ await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
70
+
71
+ return True
72
+
73
+
74
+ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
75
+ """Desactiva una entrada de configuración para Meteocat."""
76
+ platforms = async_get_platforms(hass, DOMAIN)
77
+ _LOGGER.info(f"Descargando plataformas: {[p.domain for p in platforms]}")
78
+
79
+ if entry.entry_id in hass.data.get(DOMAIN, {}):
80
+ hass.data[DOMAIN].pop(entry.entry_id, None)
81
+ if not hass.data[DOMAIN]:
82
+ hass.data.pop(DOMAIN)
83
+
84
+ return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
85
+
86
+
87
+ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
88
+ """Limpia cualquier dato adicional al desinstalar la integración."""
89
+ _LOGGER.info(f"Eliminando datos residuales de la integración: {entry.entry_id}")
90
+
91
+ # Definir las rutas de los archivos y carpetas a eliminar
92
+ custom_components_path = hass.config.path("custom_components", DOMAIN)
93
+ assets_folder = os.path.join(custom_components_path, "assets")
94
+ files_folder = os.path.join(custom_components_path, "files")
95
+ symbols_file = os.path.join(assets_folder, "symbols.json")
96
+ station_data_file = os.path.join(files_folder, "station_data.json")
97
+
98
+ # Eliminar el archivo symbols.json si existe
99
+ try:
100
+ if os.path.exists(symbols_file):
101
+ os.remove(symbols_file)
102
+ _LOGGER.info("Archivo symbols.json eliminado correctamente.")
103
+ else:
104
+ _LOGGER.info("El archivo symbols.json no se encontró.")
105
+ except OSError as e:
106
+ _LOGGER.error(f"Error al intentar eliminar el archivo symbols.json: {e}")
107
+
108
+ # Eliminar el archivo station_data.json si existe
109
+ try:
110
+ if os.path.exists(station_data_file):
111
+ os.remove(station_data_file)
112
+ _LOGGER.info("Archivo station_data.json eliminado correctamente.")
113
+ else:
114
+ _LOGGER.info("El archivo station_data.json no se encontró.")
115
+ except OSError as e:
116
+ _LOGGER.error(f"Error al intentar eliminar el archivo station_data.json: {e}")
117
+
118
+ # Eliminar la carpeta assets si está vacía
119
+ try:
120
+ if os.path.exists(assets_folder) and not os.listdir(assets_folder):
121
+ os.rmdir(assets_folder)
122
+ _LOGGER.info("Carpeta assets eliminada correctamente.")
123
+ elif os.path.exists(assets_folder):
124
+ _LOGGER.warning("La carpeta assets no está vacía y no se pudo eliminar.")
125
+ except OSError as e:
126
+ _LOGGER.error(f"Error al intentar eliminar la carpeta assets: {e}")
127
+
128
+ # Eliminar la carpeta files si está vacía
129
+ try:
130
+ if os.path.exists(files_folder) and not os.listdir(files_folder):
131
+ os.rmdir(files_folder)
132
+ _LOGGER.info("Carpeta files eliminada correctamente.")
133
+ elif os.path.exists(files_folder):
134
+ _LOGGER.warning("La carpeta files no está vacía y no se pudo eliminar.")
135
+ except OSError as e:
136
+ _LOGGER.error(f"Error al intentar eliminar la carpeta files: {e}")
@@ -1,28 +1,28 @@
1
- from __future__ import annotations
2
-
3
- from datetime import datetime
4
- from .const import CONDITION_MAPPING
5
- from .helpers import is_night # Importar la función is_night de helpers.py
6
-
7
- def get_condition_from_statcel(codi_estatcel, current_time: datetime, hass) -> dict:
8
- """
9
- Convierte el código 'estatCel' en condición de Home Assistant.
10
-
11
- :param codi_estatcel: Código del estado del cielo (celestial state code).
12
- :param current_time: Fecha y hora actual (datetime).
13
- :param hass: Instancia de Home Assistant.
14
- :return: Diccionario con la condición y el icono.
15
- """
16
- # Determinar si es de noche usando la lógica centralizada en helpers.py
17
- is_night_flag = is_night(current_time, hass)
18
-
19
- # Identificar la condición basada en el código
20
- for condition, codes in CONDITION_MAPPING.items():
21
- if codi_estatcel in codes:
22
- # Ajustar para condiciones nocturnas si aplica
23
- if condition == "sunny" and is_night_flag:
24
- return {"condition": "clear-night", "icon": None}
25
- return {"condition": condition, "icon": None}
26
-
27
- # Si no coincide ningún código, devolver condición desconocida
28
- return {"condition": "unknown", "icon": None}
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from .const import CONDITION_MAPPING
5
+ from .helpers import is_night # Importar la función is_night de helpers.py
6
+
7
+ def get_condition_from_statcel(codi_estatcel, current_time: datetime, hass) -> dict:
8
+ """
9
+ Convierte el código 'estatCel' en condición de Home Assistant.
10
+
11
+ :param codi_estatcel: Código del estado del cielo (celestial state code).
12
+ :param current_time: Fecha y hora actual (datetime).
13
+ :param hass: Instancia de Home Assistant.
14
+ :return: Diccionario con la condición y el icono.
15
+ """
16
+ # Determinar si es de noche usando la lógica centralizada en helpers.py
17
+ is_night_flag = is_night(current_time, hass)
18
+
19
+ # Identificar la condición basada en el código
20
+ for condition, codes in CONDITION_MAPPING.items():
21
+ if codi_estatcel in codes:
22
+ # Ajustar para condiciones nocturnas si aplica
23
+ if condition == "sunny" and is_night_flag:
24
+ return {"condition": "clear-night", "icon": None}
25
+ return {"condition": condition, "icon": None}
26
+
27
+ # Si no coincide ningún código, devolver condición desconocida
28
+ return {"condition": "unknown", "icon": None}