Second phase of notify entity platform implementation
·
One min read
Title option for send_message service notify entity platform
Recently we added the notify entity platform. The new notify
platform method implements service send_message
. This service now also accepts an optional title
as an argument. This allows some new integrations that can be migrated now to use the new entity platform:
- cisco_webex_teams
- file
- sendgrid
- syslog
- tibber
The architecture discussion is still ongoing.
When integrations are migrated, users will need to use the new notify.send_message
service, so the migration changes will cause automations to break after the deprecation period is over.
Store runtime data inside the config entry
·
2 min read
Integrations often need to set up and track custom data, such as coordinators, API connections, or code objects. Previously, those were all stored inside hass.data
, which made tracking them difficult.
With recent changes, it's now possible to use entry.runtime_data
for that. The config entry is already available when setting up platforms and gets cleaned up automatically. No more deleting the key from hass.data
after unloading.
It also better supports type-checking. ConfigEntry
is generic now, so passing the data type along is possible. Use a typed data structure like dataclass
for that. To simplify the annotation, it's recommended to define a type alias for it.
An example could look like this:
# <integration>/__init__.py
# The type alias needs to be suffixed with 'ConfigEntry'
MyConfigEntry = ConfigEntry["MyData"]
@dataclass
class MyData:
client: MyClient
other_data: dict[str, Any]
async def async_setup_entry(
hass: HomeAssistant,
entry: MyConfigEntry, # use type alias instead of ConfigEntry
) -> bool:
client = MyClient(...)
# Assign the runtime_data
entry.runtime_data = MyData(client, {...})
# <integration>/switch.py
from . import MyConfigEntry
async def async_setup_entry(
hass: HomeAssistant,
entry: MyConfigEntry, # use type alias instead of ConfigEntry
async_add_entities: AddEntitiesCallback,
) -> None:
# Access the runtime data form the config entry
data = entry.runtime_data
async_add_entities([MySwitch(data.client)])
Always reload after a successful re-auth flow
·
2 min read
Always reload after successful reauthentication
To update and reload the entry after a successful reauthentication flow, the helper async_update_reload_and_abort
can be used. The default behavior of the helper has been changed. By default the entry will always reload if the helper is called. If an entry needs reauthentication, it is not always needed to update the entry if an account was temporary disabled or an API-key was temporary disallowed.
For cases where reloading is not wanted in case the entry is not changed, the reload_even_if_entry_is_unchanged=False
parameter can be passed to the helper.
More about this helper can be found here here.
Example
class OAuth2FlowHandler(
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
):
"""Config flow to handle OAuth2 authentication."""
reauth_entry: ConfigEntry | None = None
async def async_step_reauth(self, user_input=None):
"""Perform reauth upon an API authentication error."""
self.reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(self, user_input=None):
"""Dialog that informs the user that reauth is required."""
if user_input is None:
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({}),
)
return await self.async_step_user()
async def async_oauth_create_entry(self, data: dict) -> dict:
"""Create an oauth config entry or update existing entry for reauth."""
if self.reauth_entry:
# Only reload if the entry was updated
return self.async_update_reload_and_abort(
self.reauth_entry,
data=data,
reload_even_if_entry_is_unchanged=False,
)
return await super().async_oauth_create_entry(data)
Replacing `async_track_state_change` with `async_track_state_change_event`
·
One min read
async_track_state_change
is deprecated and will be removed in Home Assistant 2025.5. async_track_state_change_event
should be used instead.
async_track_state_change
always creates a top-level listener for EVENT_STATE_CHANGED
, which would have to reject all state changes that did not match the desired entities. This design presented a performance problem when there were many integrations using async_track_state_change
. async_track_state_change
has been phased out in core
since the introduction of async_track_state_change_event
, with the last instance being removed in 2024.5.
Example with async_track_state_change
:
from homeassistant.core import State, callback
from homeassistant.helper.event import async_track_state_change
@callback
def _async_on_change(entity_id: str, old_state: State | None, new_state: State | None) -> None:
...
unsub = async_track_state_change(hass, "sensor.one", _async_on_change)
unsub()
Example replacement with async_track_state_change_event
:
from homeassistant.core import Event, EventStateChangedData, callback
from homeassistant.helper.event import async_track_state_change_event
@callback
def _async_on_change(event: Event[EventStateChangedData]) -> None:
entity_id = event.data["entity_id"]
old_state = event.data["old_state"]
new_state = event.data["new_state"]
...
unsub = async_track_state_change_event(hass, "sensor.one", _async_on_change)
unsub()
New notify entity platform
·
One min read
New notify entity platform
The notify platform is now available as an entity platform. The MVP for the new notify
platform implements the method and service send_message
. It accepts message
as a required attribute.Unlike the legacy notify.notify
service we have no targets as argument, as it is an entity we can target multiple notify
entities when calling send_message
.
The architecture discussion is ongoing, and is about the device classes to implement and the implementation of recipient support in the form of contacts via a contact registry.
Existing integrations that implement the legacy notify
services will be migrated in phases. The first step is to migrate the integrations than only use message
as a parameter to notify.
The integrations identified for migration are:
- circuit
- clickatell
- clicksend
- command_line
- demo
- ecobee
- flock
- free_mobile
- knx
- mastodon
As soon as we have title
and/or recipient
support we can migrate more integrations to use the new platform.
When integrations are migrated, users will need to use the new notify.send_message
service, so the migration changes will cause automations to break after the deprecation period is over.
Deprecate old backports and typing alias
·
One min read
In the past, we've backported features from upstream CPython to use them early and improve user and developers' experience. Home Assistant only supports Python 3.12, so these can be used directly from Python. These backports are now deprecated and will be removed in the future.
Deprecated | Replacement | Python version |
---|---|---|
homeassistant.backports.enum.StrEnum | enum.StrEnum | >= 3.11 |
homeassistant.backports.functools.cached_property | functools.cached_property | >= 3.8, >= 3.12 (performance improvement) |
In addition, some typing aliases are also deprecated now.
Deprecated | Replacement |
---|---|
homeassistant.helpers.typing.ContextType | homeassistant.core.Context |
homeassistant.helpers.typing.EventType | homeassistant.core.Event |
homeassistant.helpers.typing.HomeAssistantType | homeassistant.core.HomeAssistant |
homeassistant.helpers.typing.ServiceCallType | homeassistant.core.ServiceCall |
Deprecating `async_add_hass_job`
·
One min read
As part of an effort to improve performance and simplify the core job API, async_add_hass_job
is deprecated and will be removed from Home Assistant in 2025.5.
Calls should be replaced with async_run_hass_job
instead.
How uv saves Home Assistant 215 compute hours per month
·
2 min read
By replacing pip
with uv
in our production images, our build pipeline (and therefore releasing a new version) is a lot faster.Uv
is an extremely fast Python package installer and resolver written in Rust. It is developed by Astral and it's open source. Check it out on GitHub.
In the following table, you can see that we can save around 5 hours of execution time on each build.
Arch | Pip | UV | Savings |
---|---|---|---|
aarch64 | 1h 24m 53s | 5m 18s | ~1h 20m |
armhf | 1h 52m 20s | 6m 2s | ~1h 46m |
armv7 | 1h 26m 43s | 5m 28s | ~1h 21m |
amd64 | 22m 10s | 3m 20s | ~19m |
i386 | 17m 37s | 3m 11s | ~14m |
On average, we run the build pipeline 43 times as we create
- 31 nightlies (one nightly per day)
- 7 beta releases
- 5 stable releases (including patch ones)
In total, we save around 215 hours per month.With this massive improvement, we can now ship hotfixes even faster, as the pipeline to ship a new version now takes around 20 minutes instead of 2.5 hours.
The 215 monthly saved execution hours can be used by other jobs and make the CI experience for all developers and our community better.By replacing pip
with uv
, we improve our sustainability by using fewer resources to build our images.
A big thank you to Astral for developing this amazing tool.Please check out their website and products as they offer, for example, a "lightning" fast linter/formatter for Python too.
Deprecate hass.helpers usage
·
One min read
As of Home Assistant 2024.5, we deprecate the use of hass.helpers
.Using hass.helpers
will issue a warning in the logs.Authors of custom integrations are encouraged to update their codeto prevent any issues before Home Assistant 2024.11.
Starting from Home Assistant 2024.11, hass.helpers
will be removed and will no longer work.
Integrations that use hass.helpers
should be updated to import the functions and classes directlyfrom the integration package and pass the hass
object as first parameter.
New example
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
async def async_setup(hass: HomeAssistant, config):
"""Set up the component."""
client = async_get_clientsession(hass)
Old example
from homeassistant.core import HomeAssistant
async def async_setup(hass: HomeAssistant, config):
"""Set up the component."""
client = hass.helpers.aiohttp_client.async_get_clientsession()
Config Entries can now provide a reconfigure step
·
2 min read
As of Home Assistant Core 2024.4, config entries can now be reconfigured by adding a reconfigure
step in their config flows.
This is not to replace the optional configuration (OptionsFlow
) but instead to allow the user to change the setup configuration after a config entry has been created.
Reconfiguration vs. Reauthentication
The reconfigure
step does not replace a reauth
step and they have different purposes.
Reauthentication should be started automatically by the integration in the case of a login/token/etc. is invalidated, so the user has an option to adjust those settings.
Reconfiguration is started by the user from the config entry options menu and should be implemented to update config entry data which are not optional for the integration to work. Authentication issues are handled with a re-authentication flow. (See reauthentication).
Example
Examples could be changing the latitude and longitude of a WeatherEntity
when moving between homes or having a mobile home, changing the communication port of a local device, etc.
To implement the reconfigure
step, include it in your config flow as:
import voluptuous as vol
class ExampleConfigFlow(ConfigFlow, domain=DOMAIN):
"""Config flow for Example integration."""
async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None):
"""Add reconfigure step to allow to reconfigure a config entry."""
if user_input is not None:
pass # TODO: process user input
return self.async_show_form(
step_id="reconfigure",
data_schema=vol.Schema({vol.Required("password"): str}),
)