diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 92280a599643..29782c4dcc8c 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -19,7 +19,6 @@ from typing import ( override, ) -import probatio from propcache.api import cached_property from homeassistant.config_entries import ConfigEntry @@ -31,15 +30,7 @@ from homeassistant.const import ( UnitOfSpeed, UnitOfTemperature, ) -from homeassistant.core import ( - CALLBACK_TYPE, - HomeAssistant, - ServiceCall, - ServiceResponse, - SupportsResponse, - callback, -) -from homeassistant.exceptions import HomeAssistantError +from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import ABCCachedProperties, Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent @@ -74,11 +65,13 @@ from .const import ( # noqa: F401 DATA_COMPONENT, DOMAIN, INTENT_GET_WEATHER, + SERVICE_GET_FORECASTS, UNIT_CONVERSIONS, VALID_UNITS, WeatherEntityFeature, WeatherEntityStateAttribute, ) +from .services import async_setup_services from .websocket_api import async_setup as async_setup_ws_api _LOGGER = logging.getLogger(__name__) @@ -131,7 +124,6 @@ ATTR_FORECAST_UV_INDEX: Final = "uv_index" ROUNDING_PRECISION = 2 -SERVICE_GET_FORECASTS: Final = "get_forecasts" _ObservationUpdateCoordinatorT = TypeVar( "_ObservationUpdateCoordinatorT", @@ -210,17 +202,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: component = hass.data[DATA_COMPONENT] = EntityComponent[WeatherEntity]( _LOGGER, DOMAIN, hass, SCAN_INTERVAL ) - component.async_register_entity_service( - SERVICE_GET_FORECASTS, - {probatio.Required("type"): probatio.In(("daily", "hourly", "twice_daily"))}, - async_get_forecasts_service, - required_features=[ - WeatherEntityFeature.FORECAST_DAILY, - WeatherEntityFeature.FORECAST_HOURLY, - WeatherEntityFeature.FORECAST_TWICE_DAILY, - ], - supports_response=SupportsResponse.ONLY, - ) + async_setup_services(hass) async_setup_ws_api(hass) await component.async_setup(config) return True @@ -1007,40 +989,6 @@ class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_A listener(converted_forecast_list) -def raise_unsupported_forecast(entity_id: str, forecast_type: str) -> None: - """Raise error on attempt to get an unsupported forecast.""" - raise HomeAssistantError( - f"Weather entity '{entity_id}' does not support '{forecast_type}' forecast" - ) - - -async def async_get_forecasts_service( - weather: WeatherEntity, service_call: ServiceCall -) -> ServiceResponse: - """Get weather forecast.""" - forecast_type = service_call.data["type"] - supported_features = weather.supported_features or 0 - if forecast_type == "daily": - if (supported_features & WeatherEntityFeature.FORECAST_DAILY) == 0: - raise_unsupported_forecast(weather.entity_id, forecast_type) - native_forecast_list = await weather.async_forecast_daily() - elif forecast_type == "hourly": - if (supported_features & WeatherEntityFeature.FORECAST_HOURLY) == 0: - raise_unsupported_forecast(weather.entity_id, forecast_type) - native_forecast_list = await weather.async_forecast_hourly() - else: - if (supported_features & WeatherEntityFeature.FORECAST_TWICE_DAILY) == 0: - raise_unsupported_forecast(weather.entity_id, forecast_type) - native_forecast_list = await weather.async_forecast_twice_daily() - if native_forecast_list is None: - converted_forecast_list = [] - else: - converted_forecast_list = weather._convert_forecast(native_forecast_list) # noqa: SLF001 - return { - "forecast": converted_forecast_list, - } - - class CoordinatorWeatherEntity( CoordinatorEntity[_ObservationUpdateCoordinatorT], WeatherEntity, diff --git a/homeassistant/components/weather/const.py b/homeassistant/components/weather/const.py index 3e56f5e118de..74d73c76f38a 100644 --- a/homeassistant/components/weather/const.py +++ b/homeassistant/components/weather/const.py @@ -75,6 +75,8 @@ class WeatherEntityStateAttribute(StrEnum): DOMAIN: Final = "weather" + +SERVICE_GET_FORECASTS: Final = "get_forecasts" DATA_COMPONENT: HassKey[EntityComponent[WeatherEntity]] = HassKey(DOMAIN) INTENT_GET_WEATHER = "HassGetWeather" diff --git a/homeassistant/components/weather/services.py b/homeassistant/components/weather/services.py new file mode 100644 index 000000000000..bc81ef7d1045 --- /dev/null +++ b/homeassistant/components/weather/services.py @@ -0,0 +1,69 @@ +"""Services for the weather integration.""" + +from typing import TYPE_CHECKING + +import probatio + +from homeassistant.core import ( + HomeAssistant, + ServiceCall, + ServiceResponse, + SupportsResponse, + callback, +) +from homeassistant.exceptions import HomeAssistantError + +from .const import DATA_COMPONENT, SERVICE_GET_FORECASTS, WeatherEntityFeature + +if TYPE_CHECKING: + from . import WeatherEntity + + +def raise_unsupported_forecast(entity_id: str, forecast_type: str) -> None: + """Raise error on attempt to get an unsupported forecast.""" + raise HomeAssistantError( + f"Weather entity '{entity_id}' does not support '{forecast_type}' forecast" + ) + + +async def _async_get_forecasts_service( + weather: WeatherEntity, service_call: ServiceCall +) -> ServiceResponse: + """Get weather forecast.""" + forecast_type = service_call.data["type"] + supported_features = weather.supported_features or 0 + if forecast_type == "daily": + if (supported_features & WeatherEntityFeature.FORECAST_DAILY) == 0: + raise_unsupported_forecast(weather.entity_id, forecast_type) + native_forecast_list = await weather.async_forecast_daily() + elif forecast_type == "hourly": + if (supported_features & WeatherEntityFeature.FORECAST_HOURLY) == 0: + raise_unsupported_forecast(weather.entity_id, forecast_type) + native_forecast_list = await weather.async_forecast_hourly() + else: + if (supported_features & WeatherEntityFeature.FORECAST_TWICE_DAILY) == 0: + raise_unsupported_forecast(weather.entity_id, forecast_type) + native_forecast_list = await weather.async_forecast_twice_daily() + if native_forecast_list is None: + converted_forecast_list = [] + else: + converted_forecast_list = weather._convert_forecast(native_forecast_list) # noqa: SLF001 + return { + "forecast": converted_forecast_list, + } + + +@callback +def async_setup_services(hass: HomeAssistant) -> None: + """Register the weather services.""" + hass.data[DATA_COMPONENT].async_register_entity_service( + SERVICE_GET_FORECASTS, + {probatio.Required("type"): probatio.In(("daily", "hourly", "twice_daily"))}, + _async_get_forecasts_service, + required_features=[ + WeatherEntityFeature.FORECAST_DAILY, + WeatherEntityFeature.FORECAST_HOURLY, + WeatherEntityFeature.FORECAST_TWICE_DAILY, + ], + supports_response=SupportsResponse.ONLY, + )