1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-26 22:18:40 +00:00

Modernize template weather platform and add config flow (#156399)

This commit is contained in:
Petro31
2025-12-19 16:28:26 -05:00
committed by GitHub
parent 0436d30062
commit f99a73ef28
6 changed files with 748 additions and 428 deletions

View File

@@ -31,6 +31,7 @@ from homeassistant.const import (
CONF_VALUE_TEMPLATE,
CONF_VERIFY_SSL,
Platform,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import section
@@ -132,6 +133,15 @@ from .vacuum import (
SERVICE_STOP,
async_create_preview_vacuum,
)
from .weather import (
CONF_CONDITION,
CONF_FORECAST_DAILY,
CONF_FORECAST_HOURLY,
CONF_HUMIDITY,
CONF_TEMPERATURE as CONF_WEATHER_TEMPERATURE,
CONF_TEMPERATURE_UNIT,
async_create_preview_weather,
)
_SCHEMA_STATE: dict[vol.Marker, Any] = {
vol.Required(CONF_STATE): selector.TemplateSelector(),
@@ -394,6 +404,22 @@ def generate_schema(domain: str, flow_type: str) -> vol.Schema:
vol.Optional(SERVICE_LOCATE): selector.ActionSelector(),
}
if domain == Platform.WEATHER:
schema |= {
vol.Required(CONF_CONDITION): selector.TemplateSelector(),
vol.Required(CONF_HUMIDITY): selector.TemplateSelector(),
vol.Required(CONF_WEATHER_TEMPERATURE): selector.TemplateSelector(),
vol.Optional(CONF_TEMPERATURE_UNIT): selector.SelectSelector(
selector.SelectSelectorConfig(
options=[cls.value for cls in UnitOfTemperature],
mode=selector.SelectSelectorMode.DROPDOWN,
sort=True,
),
),
vol.Optional(CONF_FORECAST_DAILY): selector.TemplateSelector(),
vol.Optional(CONF_FORECAST_HOURLY): selector.TemplateSelector(),
}
schema |= {
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
vol.Optional(CONF_ADVANCED_OPTIONS): section(
@@ -414,6 +440,15 @@ options_schema = partial(generate_schema, flow_type="options")
config_schema = partial(generate_schema, flow_type="config")
async def _get_forecast_description_place_holders(
handler: SchemaCommonFlowHandler,
) -> dict[str, str]:
return {
"daily_link": "https://www.home-assistant.io/integrations/template/#daily-weather-forecast",
"hourly_link": "https://www.home-assistant.io/integrations/template/#hourly-weather-forecast",
}
async def choose_options_step(options: dict[str, Any]) -> str:
"""Return next step_id for options flow according to template_type."""
return cast(str, options["template_type"])
@@ -511,6 +546,7 @@ TEMPLATE_TYPES = [
Platform.SWITCH,
Platform.UPDATE,
Platform.VACUUM,
Platform.WEATHER,
]
CONFIG_FLOW = {
@@ -589,6 +625,12 @@ CONFIG_FLOW = {
preview="template",
validate_user_input=validate_user_input(Platform.VACUUM),
),
Platform.WEATHER: SchemaFlowFormStep(
config_schema(Platform.WEATHER),
preview="template",
validate_user_input=validate_user_input(Platform.WEATHER),
description_placeholders=_get_forecast_description_place_holders,
),
}
@@ -668,6 +710,12 @@ OPTIONS_FLOW = {
preview="template",
validate_user_input=validate_user_input(Platform.VACUUM),
),
Platform.WEATHER: SchemaFlowFormStep(
options_schema(Platform.WEATHER),
preview="template",
validate_user_input=validate_user_input(Platform.WEATHER),
description_placeholders=_get_forecast_description_place_holders,
),
}
CREATE_PREVIEW_ENTITY: dict[
@@ -687,6 +735,7 @@ CREATE_PREVIEW_ENTITY: dict[
Platform.SWITCH: async_create_preview_switch,
Platform.UPDATE: async_create_preview_update,
Platform.VACUUM: async_create_preview_vacuum,
Platform.WEATHER: async_create_preview_weather,
}

View File

@@ -463,7 +463,8 @@
"sensor": "[%key:component::sensor::title%]",
"switch": "[%key:component::switch::title%]",
"update": "[%key:component::update::title%]",
"vacuum": "[%key:component::vacuum::title%]"
"vacuum": "[%key:component::vacuum::title%]",
"weather": "[%key:component::weather::title%]"
},
"title": "Template helper"
},
@@ -507,6 +508,36 @@
}
},
"title": "Template vacuum"
},
"weather": {
"data": {
"condition": "Condition",
"device_id": "[%key:common::config_flow::data::device%]",
"forecast_daily": "Forecast daily",
"forecast_hourly": "Forecast hourly",
"humidity": "Humidity",
"name": "[%key:common::config_flow::data::name%]",
"temperature": "Temperature",
"temperature_unit": "Temperature unit"
},
"data_description": {
"condition": "Defines a template to get the current weather condition",
"device_id": "[%key:component::template::common::device_id_description%]",
"forecast_daily": "Defines a template to get the [daily forecast data]({daily_link})",
"forecast_hourly": "Defines a template to get the [hourly forecast data]({hourly_link})",
"humidity": "Defines a template to get the current humidity",
"temperature": "Defines a template to get the current temperature",
"temperature_unit": "The temperature unit"
},
"sections": {
"advanced_options": {
"data": {
"availability": "[%key:component::template::common::availability%]"
},
"name": "[%key:component::template::common::advanced_options%]"
}
},
"title": "Template weather"
}
}
},
@@ -995,6 +1026,36 @@
}
},
"title": "[%key:component::template::config::step::vacuum::title%]"
},
"weather": {
"data": {
"condition": "[%key:component::template::config::step::weather::data::condition%]",
"device_id": "[%key:common::config_flow::data::device%]",
"forecast_daily": "[%key:component::template::config::step::weather::data::forecast_daily%]",
"forecast_hourly": "[%key:component::template::config::step::weather::data::forecast_hourly%]",
"humidity": "[%key:component::template::config::step::weather::data::humidity%]",
"name": "[%key:common::config_flow::data::name%]",
"temperature": "[%key:component::template::config::step::weather::data::temperature%]",
"temperature_unit": "[%key:component::template::config::step::weather::data::temperature_unit%]"
},
"data_description": {
"condition": "[%key:component::template::config::step::weather::data_description::condition%]",
"device_id": "[%key:component::template::common::device_id_description%]",
"forecast_daily": "[%key:component::template::config::step::weather::data_description::forecast_daily%]",
"forecast_hourly": "[%key:component::template::config::step::weather::data_description::forecast_hourly%]",
"humidity": "[%key:component::template::config::step::weather::data_description::humidity%]",
"temperature": "[%key:component::template::config::step::weather::data_description::temperature%]",
"temperature_unit": "[%key:component::template::config::step::weather::data_description::temperature_unit%]"
},
"sections": {
"advanced_options": {
"data": {
"availability": "[%key:component::template::common::availability%]"
},
"name": "[%key:component::template::common::advanced_options%]"
}
},
"title": "[%key:component::template::config::step::weather::title%]"
}
}
},

File diff suppressed because it is too large Load Diff

View File

@@ -53,6 +53,29 @@
'last_wind_speed': None,
})
# ---
# name: test_setup_config_entry
StateSnapshot({
'attributes': ReadOnlyDict({
'assumed_state': True,
'attribution': 'Powered by Home Assistant',
'friendly_name': 'My template',
'humidity': 50,
'precipitation_unit': <UnitOfPrecipitationDepth.MILLIMETERS: 'mm'>,
'pressure_unit': <UnitOfPressure.HPA: 'hPa'>,
'supported_features': 0,
'temperature': 20.0,
'temperature_unit': <UnitOfTemperature.CELSIUS: '°C'>,
'visibility_unit': <UnitOfLength.KILOMETERS: 'km'>,
'wind_speed_unit': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
}),
'context': <ANY>,
'entity_id': 'weather.my_template',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_trigger_weather_services[config0-1-template-get_forecasts]
dict({
'weather.test': dict({

View File

@@ -270,6 +270,16 @@ BINARY_SENSOR_OPTIONS = {
{"start": []},
{},
),
(
"weather",
{"condition": "{{ states('weather.one') }}"},
"sunny",
{"one": "sunny", "two": "cloudy"},
{},
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
{},
),
],
)
@pytest.mark.freeze_time("2024-07-09 00:00:00+00:00")
@@ -463,6 +473,12 @@ async def test_config_flow(
{"start": []},
{"start": []},
),
(
"weather",
{"condition": "{{ states('weather.one') }}"},
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
),
],
)
async def test_config_flow_device(
@@ -752,6 +768,16 @@ async def test_config_flow_device(
{"start": []},
"state",
),
(
"weather",
{"condition": "{{ states('weather.one') }}"},
{"condition": "{{ states('weather.two') }}"},
["sunny", "cloudy"],
{"one": "sunny", "two": "cloudy"},
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
"condition",
),
],
)
@pytest.mark.freeze_time("2024-07-09 00:00:00+00:00")
@@ -1601,6 +1627,12 @@ async def test_option_flow_sensor_preview_config_entry_removed(
{"start": []},
{"start": []},
),
(
"weather",
{"condition": "{{ states('weather.one') }}"},
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
),
],
)
async def test_options_flow_change_device(

View File

@@ -37,13 +37,15 @@ from homeassistant.helpers.restore_state import STORAGE_KEY as RESTORE_STATE_KEY
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from .conftest import ConfigurationStyle
from .conftest import ConfigurationStyle, async_get_flow_preview_state
from tests.common import (
MockConfigEntry,
assert_setup_component,
async_mock_restore_state_shutdown_restart,
mock_restore_cache_with_extra_data,
)
from tests.typing import WebSocketGenerator
ATTR_FORECAST = "forecast"
@@ -122,6 +124,27 @@ async def setup_weather(
)
@pytest.fixture
async def setup_weather_single_attribute(
hass: HomeAssistant,
count: int,
style: ConfigurationStyle,
attribute: str,
attribute_template: str,
weather_config: dict[str, Any],
) -> None:
"""Do setup of weather integration."""
extra = {attribute: attribute_template}
if style == ConfigurationStyle.MODERN:
await async_setup_modern_format(
hass, count, {"name": TEST_OBJECT_ID, **weather_config, **extra}
)
if style == ConfigurationStyle.TRIGGER:
await async_setup_trigger_format(
hass, count, {"name": TEST_OBJECT_ID, **weather_config, **extra}
)
@pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)])
@pytest.mark.parametrize(
"config",
@@ -1129,3 +1152,58 @@ async def test_templated_optional_config(
state = hass.states.get(TEST_WEATHER)
assert state.attributes[attribute] == expected
async def test_setup_config_entry(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
) -> None:
"""Tests creating a weather from a config entry."""
hass.states.async_set(
"weather.test_state",
"sunny",
{},
)
template_config_entry = MockConfigEntry(
data={},
domain=template.DOMAIN,
options={
"name": "My template",
"condition": "{{ states('sensor.test_sensor') }}",
"humidity": "{{ 50 }}",
"temperature": "{{ 20 }}",
"template_type": WEATHER_DOMAIN,
},
title="My template",
)
template_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(template_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("weather.my_template")
assert state is not None
assert state == snapshot
async def test_flow_preview(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the config flow preview."""
state = await async_get_flow_preview_state(
hass,
hass_ws_client,
WEATHER_DOMAIN,
{
"name": "My template",
"condition": "{{ 'sunny' }}",
"humidity": "{{ 50 }}",
"temperature": "{{ 20 }}",
},
)
assert state["state"] == "sunny"