Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate OpenWeaterMap to new library (support API 3.0) #116305

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
98fe0c3
Migrate integration to new library
freekode Apr 26, 2024
32bf27b
migrate to new forecast
freekode Apr 26, 2024
f159db5
migrate forecasts responses
freekode Apr 26, 2024
5d57a06
fix test
freekode Apr 26, 2024
ca76d2b
upd dependency
freekode Apr 26, 2024
9532399
return forecast sensors
freekode Apr 27, 2024
1646b18
fix forecast cloud sernsor
freekode Apr 27, 2024
9f7435a
remove commented
freekode Apr 27, 2024
3939fdb
move out from try
freekode Apr 27, 2024
3486b7f
Update homeassistant/components/openweathermap/weather_update_coordin…
freekode Apr 27, 2024
668e631
move out from try
freekode Apr 27, 2024
d45b0d3
fix tests, remove migration
freekode Apr 28, 2024
7da62db
remove timeout
freekode Apr 28, 2024
711931a
add fixtures
freekode Apr 28, 2024
6c35f80
style fixes
freekode Apr 28, 2024
ae546d7
add abort test
freekode Apr 28, 2024
eb9e61e
upd tests
freekode Apr 29, 2024
7babe60
Merge branch 'dev' into owm-upgrade
freekode Apr 29, 2024
e933bb3
Merge branch 'dev' into owm-upgrade
freekode Apr 30, 2024
074276d
add description placeholder
freekode Apr 30, 2024
5d3c648
upd lib version
freekode Apr 30, 2024
57cae47
Merge branch 'dev' into owm-upgrade
freekode Apr 30, 2024
29b517e
add error description
Apr 30, 2024
06925bf
v2.5 support
freekode May 4, 2024
8f2ee6b
Merge branch 'dev' into owm-upgrade
freekode May 4, 2024
6c9de77
Merge branch 'dev' into owm-upgrade
freekode May 5, 2024
6433137
v2.5 support
freekode May 5, 2024
7ea62cd
fix strings
freekode May 5, 2024
8634bc8
fix strings
freekode May 5, 2024
13848d8
Merge branch 'dev' into owm-upgrade
freekode May 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 3 additions & 14 deletions homeassistant/components/openweathermap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import logging
from typing import Any

from pyowm import OWM
from pyowm.utils.config import get_default_config
from pyopenweathermap import OWMClient

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
Expand Down Expand Up @@ -40,14 +39,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
api_key = entry.data[CONF_API_KEY]
latitude = entry.data.get(CONF_LATITUDE, hass.config.latitude)
longitude = entry.data.get(CONF_LONGITUDE, hass.config.longitude)
forecast_mode = _get_config_value(entry, CONF_MODE)
language = _get_config_value(entry, CONF_LANGUAGE)

config_dict = _get_owm_config(language)

owm = OWM(api_key, config_dict).weather_manager()
owm_client = OWMClient(api_key, lang=language)
weather_coordinator = WeatherUpdateCoordinator(
owm, latitude, longitude, forecast_mode, hass
owm_client, latitude, longitude, hass
)

await weather_coordinator.async_config_entry_first_refresh()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should detect that they don't have the v3 api on their account and create an issue in the issue registry telling them what to do.

https://developers.home-assistant.io/docs/core/platform/repairs?_highlight=issue#creating-an-issue

Copy link
Contributor Author

@freekode freekode May 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see
i think i can add support of api v2.5 in the lib, then add a select in HA where you choose which api to use. Then add repair notification to move from 2.5 to 3.0. Although I can't detect is user has subscription or not.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. It should be a config entry option

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, i added support v2.5
added migration, all users will be migrated to onecall v2.5
updated config flow, you can choose between v2.5 and v3.0 (default)
if you choose 2.5, during init repair issue will be created

Copy link
Contributor Author

@freekode freekode May 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry about mess with commit author emails, i will recreate pr later when everything will be done

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new keys (or rather new accounts) don't work with v2.5 already

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continue here #116870

Expand Down Expand Up @@ -108,10 +104,3 @@ def _get_config_value(config_entry: ConfigEntry, key: str) -> Any:
if config_entry.options:
return config_entry.options[key]
return config_entry.data[key]


def _get_owm_config(language: str) -> dict[str, Any]:
"""Get OpenWeatherMap configuration and add language to it."""
config_dict = get_default_config()
config_dict["language"] = language
return config_dict
43 changes: 15 additions & 28 deletions homeassistant/components/openweathermap/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,30 @@

from __future__ import annotations

from pyowm import OWM
from pyowm.commons.exceptions import APIRequestError, UnauthorizedError
from pyopenweathermap import OWMClient, RequestError
import voluptuous as vol

from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.const import (
CONF_API_KEY,
CONF_LANGUAGE,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_MODE,
CONF_NAME,
)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv

from .const import (
CONFIG_FLOW_VERSION,
DEFAULT_FORECAST_MODE,
DEFAULT_LANGUAGE,
DEFAULT_NAME,
DOMAIN,
FORECAST_MODES,
LANGUAGES,
)

Expand All @@ -42,7 +43,7 @@ def async_get_options_flow(
"""Get the options flow for this handler."""
return OpenWeatherMapOptionsFlow(config_entry)

async def async_step_user(self, user_input=None):
async def async_step_user(self, user_input=None) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
errors = {}

Expand All @@ -54,14 +55,10 @@ async def async_step_user(self, user_input=None):
self._abort_if_unique_id_configured()

try:
api_online = await _is_owm_api_online(
self.hass, user_input[CONF_API_KEY], latitude, longitude
)
if not api_online:
api_key_valid = await _is_owm_api_key_valid(user_input[CONF_API_KEY])
if api_key_valid is False:
errors["base"] = "invalid_api_key"
bdraco marked this conversation as resolved.
Show resolved Hide resolved
except UnauthorizedError:
errors["base"] = "invalid_api_key"
except APIRequestError:
except RequestError:
errors["base"] = "cannot_connect"

if not errors:
Expand All @@ -79,9 +76,6 @@ async def async_step_user(self, user_input=None):
vol.Optional(
CONF_LONGITUDE, default=self.hass.config.longitude
): cv.longitude,
vol.Optional(CONF_MODE, default=DEFAULT_FORECAST_MODE): vol.In(
FORECAST_MODES
),
vol.Optional(CONF_LANGUAGE, default=DEFAULT_LANGUAGE): vol.In(
LANGUAGES
),
Expand All @@ -98,7 +92,7 @@ def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry

async def async_step_init(self, user_input=None):
async def async_step_init(self, user_input=None) -> ConfigFlowResult:
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
Expand All @@ -111,13 +105,6 @@ async def async_step_init(self, user_input=None):
def _get_options_schema(self):
return vol.Schema(
{
vol.Optional(
CONF_MODE,
default=self.config_entry.options.get(
CONF_MODE,
self.config_entry.data.get(CONF_MODE, DEFAULT_FORECAST_MODE),
),
): vol.In(FORECAST_MODES),
vol.Optional(
CONF_LANGUAGE,
default=self.config_entry.options.get(
Expand All @@ -129,6 +116,6 @@ def _get_options_schema(self):
)


async def _is_owm_api_online(hass, api_key, lat, lon):
owm = OWM(api_key).weather_manager()
return await hass.async_add_executor_job(owm.weather_at_coords, lat, lon)
async def _is_owm_api_key_valid(api_key):
owm_client = OWMClient(api_key)
return await owm_client.validate_key()
4 changes: 4 additions & 0 deletions homeassistant/components/openweathermap/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@
ATTR_API_UV_INDEX = "uv_index"
ATTR_API_VISIBILITY_DISTANCE = "visibility_distance"
ATTR_API_WEATHER_CODE = "weather_code"
ATTR_API_CLOUD_COVERAGE = "cloud_coverage"
ATTR_API_FORECAST = "forecast"
ATTR_API_CURRENT = "current"
ATTR_API_HOURLY_FORECAST = "hourly_forecast"
ATTR_API_DAILY_FORECAST = "daily_forecast"
UPDATE_LISTENER = "update_listener"
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]

Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/openweathermap/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/openweathermap",
"iot_class": "cloud_polling",
"loggers": ["geojson", "pyowm", "pysocks"],
"requirements": ["pyowm==3.2.0"]
"loggers": ["geojson", "pyopenweathermap", "pysocks"],
bdraco marked this conversation as resolved.
Show resolved Hide resolved
"requirements": ["pyopenweathermap==0.0.5"]
}
18 changes: 9 additions & 9 deletions homeassistant/components/openweathermap/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@
from homeassistant.util import dt as dt_util

from .const import (
ATTR_API_CLOUD_COVERAGE,
ATTR_API_CLOUDS,
ATTR_API_CONDITION,
ATTR_API_CURRENT,
ATTR_API_DAILY_FORECAST,
ATTR_API_DEW_POINT,
ATTR_API_FEELS_LIKE_TEMPERATURE,
ATTR_API_FORECAST,
ATTR_API_FORECAST_CONDITION,
ATTR_API_FORECAST_PRECIPITATION,
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_API_FORECAST_PRESSURE,
Expand Down Expand Up @@ -164,7 +165,7 @@
)
FORECAST_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key=ATTR_API_FORECAST_CONDITION,
key=ATTR_API_CONDITION,
name="Condition",
),
SensorEntityDescription(
Expand Down Expand Up @@ -213,7 +214,7 @@
device_class=SensorDeviceClass.WIND_SPEED,
),
SensorEntityDescription(
key=ATTR_API_CLOUDS,
key=ATTR_API_CLOUD_COVERAGE,
name="Cloud coverage",
native_unit_of_measurement=PERCENTAGE,
),
Expand Down Expand Up @@ -315,7 +316,9 @@ def __init__(
@property
def native_value(self) -> StateType:
"""Return the state of the device."""
return self._weather_coordinator.data.get(self.entity_description.key, None)
return self._weather_coordinator.data[ATTR_API_CURRENT].get(
self.entity_description.key, None
)
bdraco marked this conversation as resolved.
Show resolved Hide resolved


class OpenWeatherMapForecastSensor(AbstractOpenWeatherMapSensor):
Expand All @@ -335,10 +338,7 @@ def __init__(
@property
def native_value(self) -> StateType | datetime:
"""Return the state of the device."""
forecasts = self._weather_coordinator.data.get(ATTR_API_FORECAST)
if not forecasts:
return None

forecasts = self._weather_coordinator.data[ATTR_API_DAILY_FORECAST]
value = forecasts[0].get(self.entity_description.key, None)
bdraco marked this conversation as resolved.
Show resolved Hide resolved
if (
value
Expand Down
95 changes: 18 additions & 77 deletions homeassistant/components/openweathermap/weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,7 @@

from __future__ import annotations

from typing import cast

from homeassistant.components.weather import (
ATTR_FORECAST_CLOUD_COVERAGE,
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_HUMIDITY,
ATTR_FORECAST_NATIVE_APPARENT_TEMP,
ATTR_FORECAST_NATIVE_PRECIPITATION,
ATTR_FORECAST_NATIVE_PRESSURE,
ATTR_FORECAST_NATIVE_TEMP,
ATTR_FORECAST_NATIVE_TEMP_LOW,
ATTR_FORECAST_NATIVE_WIND_SPEED,
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_FORECAST_TIME,
ATTR_FORECAST_WIND_BEARING,
Forecast,
SingleCoordinatorWeatherEntity,
WeatherEntityFeature,
Expand All @@ -35,21 +21,11 @@
from .const import (
ATTR_API_CLOUDS,
ATTR_API_CONDITION,
ATTR_API_CURRENT,
ATTR_API_DAILY_FORECAST,
ATTR_API_DEW_POINT,
ATTR_API_FEELS_LIKE_TEMPERATURE,
ATTR_API_FORECAST,
ATTR_API_FORECAST_CLOUDS,
ATTR_API_FORECAST_CONDITION,
ATTR_API_FORECAST_FEELS_LIKE_TEMPERATURE,
ATTR_API_FORECAST_HUMIDITY,
ATTR_API_FORECAST_PRECIPITATION,
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_API_FORECAST_PRESSURE,
ATTR_API_FORECAST_TEMP,
ATTR_API_FORECAST_TEMP_LOW,
ATTR_API_FORECAST_TIME,
ATTR_API_FORECAST_WIND_BEARING,
ATTR_API_FORECAST_WIND_SPEED,
ATTR_API_HOURLY_FORECAST,
ATTR_API_HUMIDITY,
ATTR_API_PRESSURE,
ATTR_API_TEMPERATURE,
Expand All @@ -61,27 +37,10 @@
DOMAIN,
ENTRY_NAME,
ENTRY_WEATHER_COORDINATOR,
FORECAST_MODE_DAILY,
FORECAST_MODE_ONECALL_DAILY,
MANUFACTURER,
)
from .weather_update_coordinator import WeatherUpdateCoordinator

FORECAST_MAP = {
ATTR_API_FORECAST_CONDITION: ATTR_FORECAST_CONDITION,
ATTR_API_FORECAST_PRECIPITATION: ATTR_FORECAST_NATIVE_PRECIPITATION,
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY: ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_API_FORECAST_PRESSURE: ATTR_FORECAST_NATIVE_PRESSURE,
ATTR_API_FORECAST_TEMP_LOW: ATTR_FORECAST_NATIVE_TEMP_LOW,
ATTR_API_FORECAST_TEMP: ATTR_FORECAST_NATIVE_TEMP,
ATTR_API_FORECAST_TIME: ATTR_FORECAST_TIME,
ATTR_API_FORECAST_WIND_BEARING: ATTR_FORECAST_WIND_BEARING,
ATTR_API_FORECAST_WIND_SPEED: ATTR_FORECAST_NATIVE_WIND_SPEED,
ATTR_API_FORECAST_CLOUDS: ATTR_FORECAST_CLOUD_COVERAGE,
ATTR_API_FORECAST_HUMIDITY: ATTR_FORECAST_HUMIDITY,
ATTR_API_FORECAST_FEELS_LIKE_TEMPERATURE: ATTR_FORECAST_NATIVE_APPARENT_TEMP,
}


async def async_setup_entry(
hass: HomeAssistant,
Expand Down Expand Up @@ -126,84 +85,66 @@ def __init__(
manufacturer=MANUFACTURER,
name=DEFAULT_NAME,
)
if weather_coordinator.forecast_mode in (
FORECAST_MODE_DAILY,
FORECAST_MODE_ONECALL_DAILY,
):
self._attr_supported_features = WeatherEntityFeature.FORECAST_DAILY
else: # FORECAST_MODE_DAILY or FORECAST_MODE_ONECALL_HOURLY
self._attr_supported_features = WeatherEntityFeature.FORECAST_HOURLY
self._attr_supported_features = (
WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY
)

@property
def condition(self) -> str | None:
"""Return the current condition."""
return self.coordinator.data[ATTR_API_CONDITION]
return self.coordinator.data[ATTR_API_CURRENT][ATTR_API_CONDITION]

@property
def cloud_coverage(self) -> float | None:
"""Return the Cloud coverage in %."""
return self.coordinator.data[ATTR_API_CLOUDS]
return self.coordinator.data[ATTR_API_CURRENT][ATTR_API_CLOUDS]

@property
def native_apparent_temperature(self) -> float | None:
"""Return the apparent temperature."""
return self.coordinator.data[ATTR_API_FEELS_LIKE_TEMPERATURE]
return self.coordinator.data[ATTR_API_CURRENT][ATTR_API_FEELS_LIKE_TEMPERATURE]

@property
def native_temperature(self) -> float | None:
"""Return the temperature."""
return self.coordinator.data[ATTR_API_TEMPERATURE]
return self.coordinator.data[ATTR_API_CURRENT][ATTR_API_TEMPERATURE]

@property
def native_pressure(self) -> float | None:
"""Return the pressure."""
return self.coordinator.data[ATTR_API_PRESSURE]
return self.coordinator.data[ATTR_API_CURRENT][ATTR_API_PRESSURE]

@property
def humidity(self) -> float | None:
"""Return the humidity."""
return self.coordinator.data[ATTR_API_HUMIDITY]
return self.coordinator.data[ATTR_API_CURRENT][ATTR_API_HUMIDITY]

@property
def native_dew_point(self) -> float | None:
"""Return the dew point."""
return self.coordinator.data[ATTR_API_DEW_POINT]
return self.coordinator.data[ATTR_API_CURRENT][ATTR_API_DEW_POINT]

@property
def native_wind_gust_speed(self) -> float | None:
"""Return the wind gust speed."""
return self.coordinator.data[ATTR_API_WIND_GUST]
return self.coordinator.data[ATTR_API_CURRENT][ATTR_API_WIND_GUST]

@property
def native_wind_speed(self) -> float | None:
"""Return the wind speed."""
return self.coordinator.data[ATTR_API_WIND_SPEED]
return self.coordinator.data[ATTR_API_CURRENT][ATTR_API_WIND_SPEED]

@property
def wind_bearing(self) -> float | str | None:
"""Return the wind bearing."""
return self.coordinator.data[ATTR_API_WIND_BEARING]

@property
def _forecast(self) -> list[Forecast] | None:
"""Return the forecast array."""
api_forecasts = self.coordinator.data[ATTR_API_FORECAST]
forecasts = [
{
ha_key: forecast[api_key]
for api_key, ha_key in FORECAST_MAP.items()
if api_key in forecast
}
for forecast in api_forecasts
]
return cast(list[Forecast], forecasts)
return self.coordinator.data[ATTR_API_CURRENT][ATTR_API_WIND_BEARING]

@callback
def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units."""
return self._forecast
return self.coordinator.data[ATTR_API_DAILY_FORECAST]

@callback
def _async_forecast_hourly(self) -> list[Forecast] | None:
"""Return the hourly forecast in native units."""
return self._forecast
return self.coordinator.data[ATTR_API_HOURLY_FORECAST]