Deprecate HEOS sign_in and sign_out actions (#134616)

This commit is contained in:
Andrew Sayre 2025-01-04 17:13:46 -06:00 committed by GitHub
parent 11d80065ef
commit f68c16586d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 80 additions and 18 deletions

View File

@ -27,10 +27,12 @@ from homeassistant.const import (
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers import device_registry as dr, entity_registry as er
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_connect,
async_dispatcher_send, async_dispatcher_send,
) )
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import Throttle from homeassistant.util import Throttle
from . import services from . import services
@ -46,6 +48,8 @@ PLATFORMS = [Platform.MEDIA_PLAYER]
MIN_UPDATE_SOURCES = timedelta(seconds=1) MIN_UPDATE_SOURCES = timedelta(seconds=1)
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -62,6 +66,12 @@ class HeosRuntimeData:
type HeosConfigEntry = ConfigEntry[HeosRuntimeData] type HeosConfigEntry = ConfigEntry[HeosRuntimeData]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the HEOS component."""
services.register(hass)
return True
async def async_setup_entry(hass: HomeAssistant, entry: HeosConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: HeosConfigEntry) -> bool:
"""Initialize config entry which represents the HEOS controller.""" """Initialize config entry which represents the HEOS controller."""
# For backwards compat # For backwards compat
@ -141,7 +151,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: HeosConfigEntry) -> bool
controller_manager, group_manager, source_manager, players controller_manager, group_manager, source_manager, players
) )
services.register(hass, controller)
group_manager.connect_update() group_manager.connect_update()
entry.async_on_unload(group_manager.disconnect_update) entry.async_on_unload(group_manager.disconnect_update)
@ -153,9 +162,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: HeosConfigEntry) -> bool
async def async_unload_entry(hass: HomeAssistant, entry: HeosConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: HeosConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
await entry.runtime_data.controller_manager.disconnect() await entry.runtime_data.controller_manager.disconnect()
services.remove(hass)
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@ -1,8 +1,6 @@
rules: rules:
# Bronze # Bronze
action-setup: action-setup: done
status: todo
comment: Future enhancement to move custom actions for login/out into an options flow.
appropriate-polling: appropriate-polling:
status: done status: done
comment: Integration is a local push integration comment: Integration is a local push integration

View File

@ -1,13 +1,14 @@
"""Services for the HEOS integration.""" """Services for the HEOS integration."""
import functools
import logging import logging
from pyheos import CommandFailedError, Heos, HeosError, const from pyheos import CommandFailedError, Heos, HeosError, const
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, issue_registry as ir
from .const import ( from .const import (
ATTR_PASSWORD, ATTR_PASSWORD,
@ -26,30 +27,50 @@ HEOS_SIGN_IN_SCHEMA = vol.Schema(
HEOS_SIGN_OUT_SCHEMA = vol.Schema({}) HEOS_SIGN_OUT_SCHEMA = vol.Schema({})
def register(hass: HomeAssistant, controller: Heos): def register(hass: HomeAssistant):
"""Register HEOS services.""" """Register HEOS services."""
hass.services.async_register( hass.services.async_register(
DOMAIN, DOMAIN,
SERVICE_SIGN_IN, SERVICE_SIGN_IN,
functools.partial(_sign_in_handler, controller), _sign_in_handler,
schema=HEOS_SIGN_IN_SCHEMA, schema=HEOS_SIGN_IN_SCHEMA,
) )
hass.services.async_register( hass.services.async_register(
DOMAIN, DOMAIN,
SERVICE_SIGN_OUT, SERVICE_SIGN_OUT,
functools.partial(_sign_out_handler, controller), _sign_out_handler,
schema=HEOS_SIGN_OUT_SCHEMA, schema=HEOS_SIGN_OUT_SCHEMA,
) )
def remove(hass: HomeAssistant): def _get_controller(hass: HomeAssistant) -> Heos:
"""Unregister HEOS services.""" """Get the HEOS controller instance."""
hass.services.async_remove(DOMAIN, SERVICE_SIGN_IN)
hass.services.async_remove(DOMAIN, SERVICE_SIGN_OUT) _LOGGER.warning(
"Actions 'heos.sign_in' and 'heos.sign_out' are deprecated and will be removed in the 2025.8.0 release"
)
ir.async_create_issue(
hass,
DOMAIN,
"sign_in_out_deprecated",
breaks_in_ha_version="2025.8.0",
is_fixable=False,
severity=ir.IssueSeverity.WARNING,
translation_key="sign_in_out_deprecated",
)
entry = hass.config_entries.async_entry_for_domain_unique_id(DOMAIN, DOMAIN)
if not entry or not entry.state == ConfigEntryState.LOADED:
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="integration_not_loaded"
)
return entry.runtime_data.controller_manager.controller
async def _sign_in_handler(controller: Heos, service: ServiceCall) -> None: async def _sign_in_handler(service: ServiceCall) -> None:
"""Sign in to the HEOS account.""" """Sign in to the HEOS account."""
controller = _get_controller(service.hass)
if controller.connection_state != const.STATE_CONNECTED: if controller.connection_state != const.STATE_CONNECTED:
_LOGGER.error("Unable to sign in because HEOS is not connected") _LOGGER.error("Unable to sign in because HEOS is not connected")
return return
@ -63,8 +84,10 @@ async def _sign_in_handler(controller: Heos, service: ServiceCall) -> None:
_LOGGER.error("Unable to sign in: %s", err) _LOGGER.error("Unable to sign in: %s", err)
async def _sign_out_handler(controller: Heos, service: ServiceCall) -> None: async def _sign_out_handler(service: ServiceCall) -> None:
"""Sign out of the HEOS account.""" """Sign out of the HEOS account."""
controller = _get_controller(service.hass)
if controller.connection_state != const.STATE_CONNECTED: if controller.connection_state != const.STATE_CONNECTED:
_LOGGER.error("Unable to sign out because HEOS is not connected") _LOGGER.error("Unable to sign out because HEOS is not connected")
return return

View File

@ -89,5 +89,16 @@
"name": "Sign out", "name": "Sign out",
"description": "Signs out of the HEOS account." "description": "Signs out of the HEOS account."
} }
},
"exceptions": {
"integration_not_loaded": {
"message": "The HEOS integration is not loaded"
}
},
"issues": {
"sign_in_out_deprecated": {
"title": "HEOS Actions Deprecated",
"description": "Actions 'heos.sign_in' and 'heos.sign_out' are deprecated and will be removed in the 2025.8.0 release. Enter your HEOS Account credentials in the configuration options and the integration will manage authentication automatically."
}
} }
} }

View File

@ -11,6 +11,7 @@ from homeassistant.components.heos.const import (
SERVICE_SIGN_OUT, SERVICE_SIGN_OUT,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -91,6 +92,20 @@ async def test_sign_in_unknown_error(
assert "Unable to sign in" in caplog.text assert "Unable to sign in" in caplog.text
async def test_sign_in_not_loaded_raises(hass: HomeAssistant, config_entry) -> None:
"""Test the sign-in service when entry not loaded raises exception."""
await setup_component(hass, config_entry)
await hass.config_entries.async_unload(config_entry.entry_id)
with pytest.raises(HomeAssistantError, match="The HEOS integration is not loaded"):
await hass.services.async_call(
DOMAIN,
SERVICE_SIGN_IN,
{ATTR_USERNAME: "test@test.com", ATTR_PASSWORD: "password"},
blocking=True,
)
async def test_sign_out(hass: HomeAssistant, config_entry, controller) -> None: async def test_sign_out(hass: HomeAssistant, config_entry, controller) -> None:
"""Test the sign-out service.""" """Test the sign-out service."""
await setup_component(hass, config_entry) await setup_component(hass, config_entry)
@ -113,6 +128,15 @@ async def test_sign_out_not_connected(
assert "Unable to sign out because HEOS is not connected" in caplog.text assert "Unable to sign out because HEOS is not connected" in caplog.text
async def test_sign_out_not_loaded_raises(hass: HomeAssistant, config_entry) -> None:
"""Test the sign-out service when entry not loaded raises exception."""
await setup_component(hass, config_entry)
await hass.config_entries.async_unload(config_entry.entry_id)
with pytest.raises(HomeAssistantError, match="The HEOS integration is not loaded"):
await hass.services.async_call(DOMAIN, SERVICE_SIGN_OUT, {}, blocking=True)
async def test_sign_out_unknown_error( async def test_sign_out_unknown_error(
hass: HomeAssistant, config_entry, controller, caplog: pytest.LogCaptureFixture hass: HomeAssistant, config_entry, controller, caplog: pytest.LogCaptureFixture
) -> None: ) -> None: