mirror of
https://github.com/home-assistant/core.git
synced 2026-02-15 07:36:16 +00:00
Drop hass argument from verify_domain_control (#147946)
This commit is contained in:
@@ -162,12 +162,12 @@ def setup_service_functions(
|
||||
It appears that all TCC-compatible systems support the same three zones modes.
|
||||
"""
|
||||
|
||||
@verify_domain_control(hass, DOMAIN)
|
||||
@verify_domain_control(DOMAIN)
|
||||
async def force_refresh(call: ServiceCall) -> None:
|
||||
"""Obtain the latest state data via the vendor's RESTful API."""
|
||||
await coordinator.async_refresh()
|
||||
|
||||
@verify_domain_control(hass, DOMAIN)
|
||||
@verify_domain_control(DOMAIN)
|
||||
async def set_system_mode(call: ServiceCall) -> None:
|
||||
"""Set the system mode."""
|
||||
assert coordinator.tcs is not None # mypy
|
||||
@@ -179,7 +179,7 @@ def setup_service_functions(
|
||||
}
|
||||
async_dispatcher_send(hass, DOMAIN, payload)
|
||||
|
||||
@verify_domain_control(hass, DOMAIN)
|
||||
@verify_domain_control(DOMAIN)
|
||||
async def set_zone_override(call: ServiceCall) -> None:
|
||||
"""Set the zone override (setpoint)."""
|
||||
entity_id = call.data[ATTR_ENTITY_ID]
|
||||
|
||||
@@ -124,7 +124,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: GeniusHubConfigEntry) ->
|
||||
def setup_service_functions(hass: HomeAssistant, broker):
|
||||
"""Set up the service functions."""
|
||||
|
||||
@verify_domain_control(hass, DOMAIN)
|
||||
@verify_domain_control(DOMAIN)
|
||||
async def set_zone_mode(call: ServiceCall) -> None:
|
||||
"""Set the system mode."""
|
||||
entity_id = call.data[ATTR_ENTITY_ID]
|
||||
|
||||
@@ -124,7 +124,7 @@ SCHEMA_SET_HOME_COOLING_MODE = vol.Schema(
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Set up the HomematicIP Cloud services."""
|
||||
|
||||
@verify_domain_control(hass, DOMAIN)
|
||||
@verify_domain_control(DOMAIN)
|
||||
async def async_call_hmipc_service(service: ServiceCall) -> None:
|
||||
"""Call correct HomematicIP Cloud service."""
|
||||
service_name = service.service
|
||||
|
||||
@@ -64,7 +64,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_HUE_ACTIVATE_SCENE,
|
||||
verify_domain_control(hass, DOMAIN)(hue_activate_scene),
|
||||
verify_domain_control(DOMAIN)(hue_activate_scene),
|
||||
schema=vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_GROUP_NAME): cv.string,
|
||||
|
||||
@@ -89,7 +89,7 @@ async def async_setup_entry(
|
||||
elif service_call.service == SERVICE_RESTORE:
|
||||
entity.restore()
|
||||
|
||||
@service.verify_domain_control(hass, DOMAIN)
|
||||
@service.verify_domain_control(DOMAIN)
|
||||
async def async_service_handle(service_call: core.ServiceCall) -> None:
|
||||
"""Handle for services."""
|
||||
entities = await platform.async_extract_from_service(service_call)
|
||||
|
||||
@@ -290,7 +290,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up SimpliSafe as config entry."""
|
||||
_async_standardize_config_entry(hass, entry)
|
||||
|
||||
_verify_domain_control = verify_domain_control(hass, DOMAIN)
|
||||
_verify_domain_control = verify_domain_control(DOMAIN)
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
|
||||
try:
|
||||
|
||||
@@ -35,7 +35,7 @@ ATTR_WITH_GROUP = "with_group"
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Register Sonos services."""
|
||||
|
||||
@service.verify_domain_control(hass, DOMAIN)
|
||||
@service.verify_domain_control(DOMAIN)
|
||||
async def async_service_handle(service_call: ServiceCall) -> None:
|
||||
"""Handle dispatched services."""
|
||||
platform_entities = hass.data.get(DATA_DOMAIN_PLATFORM_ENTITIES, {}).get(
|
||||
|
||||
@@ -138,6 +138,41 @@ def deprecated_function[**_P, _R](
|
||||
return deprecated_decorator
|
||||
|
||||
|
||||
def deprecated_hass_argument[**_P, _T](
|
||||
breaks_in_ha_version: str | None = None,
|
||||
) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]:
|
||||
"""Decorate function to indicate that first argument hass will be ignored."""
|
||||
|
||||
def _decorator(func: Callable[_P, _T]) -> Callable[_P, _T]:
|
||||
@functools.wraps(func)
|
||||
def _inner(*args: _P.args, **kwargs: _P.kwargs) -> _T:
|
||||
from homeassistant.core import HomeAssistant # noqa: PLC0415
|
||||
|
||||
in_arg = len(args) > 0 and isinstance(args[0], HomeAssistant)
|
||||
in_kwarg = "hass" in kwargs and isinstance(kwargs["hass"], HomeAssistant)
|
||||
|
||||
if in_arg or in_kwarg:
|
||||
_print_deprecation_warning_internal(
|
||||
"hass",
|
||||
func.__module__,
|
||||
f"{func.__name__} without hass argument",
|
||||
"argument",
|
||||
f"passed to {func.__name__}",
|
||||
breaks_in_ha_version,
|
||||
log_when_no_integration_is_found=True,
|
||||
)
|
||||
if in_arg:
|
||||
args = args[1:] # type: ignore[assignment]
|
||||
if in_kwarg:
|
||||
kwargs.pop("hass")
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return _inner
|
||||
|
||||
return _decorator
|
||||
|
||||
|
||||
def _print_deprecation_warning(
|
||||
obj: Any,
|
||||
replacement: str,
|
||||
|
||||
@@ -60,7 +60,7 @@ from . import (
|
||||
template,
|
||||
translation,
|
||||
)
|
||||
from .deprecation import deprecated_class, deprecated_function
|
||||
from .deprecation import deprecated_class, deprecated_function, deprecated_hass_argument
|
||||
from .selector import TargetSelector
|
||||
from .typing import ConfigType, TemplateVarsType, VolDictType, VolSchemaType
|
||||
|
||||
@@ -995,10 +995,10 @@ def async_register_admin_service(
|
||||
)
|
||||
|
||||
|
||||
@bind_hass
|
||||
@deprecated_hass_argument(breaks_in_ha_version="2026.10")
|
||||
@callback
|
||||
def verify_domain_control(
|
||||
hass: HomeAssistant, domain: str
|
||||
domain: str,
|
||||
) -> Callable[[Callable[[ServiceCall], Any]], Callable[[ServiceCall], Any]]:
|
||||
"""Ensure permission to access any entity under domain in service call."""
|
||||
|
||||
@@ -1014,6 +1014,7 @@ def verify_domain_control(
|
||||
if not call.context.user_id:
|
||||
return await service_handler(call)
|
||||
|
||||
hass = call.hass
|
||||
user = await hass.auth.async_get_user(call.context.user_id)
|
||||
|
||||
if user is None:
|
||||
|
||||
@@ -17,6 +17,7 @@ from homeassistant.helpers.deprecation import (
|
||||
check_if_deprecated_constant,
|
||||
deprecated_class,
|
||||
deprecated_function,
|
||||
deprecated_hass_argument,
|
||||
deprecated_substitute,
|
||||
dir_with_deprecated_constants,
|
||||
get_deprecated,
|
||||
@@ -638,3 +639,77 @@ def test_enum_with_deprecated_members_integration_not_found(
|
||||
TestEnum.DOGS # noqa: B018
|
||||
|
||||
assert len(caplog.record_tuples) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("positional_arguments", "keyword_arguments"),
|
||||
[
|
||||
# without kwargs
|
||||
([], {}),
|
||||
(["first_arg"], {}),
|
||||
(["first_arg", "second_arg"], {}),
|
||||
# with single kwargs
|
||||
([], {"first_kwarg": "first_value"}),
|
||||
(["first_arg"], {"first_kwarg": "first_value"}),
|
||||
(["first_arg", "second_arg"], {"first_kwarg": "first_value"}),
|
||||
# with double kwargs
|
||||
([], {"first_kwarg": "first_value", "second_kwarg": "second_value"}),
|
||||
(["first_arg"], {"first_kwarg": "first_value", "second_kwarg": "second_value"}),
|
||||
(
|
||||
["first_arg", "second_arg"],
|
||||
{"first_kwarg": "first_value", "second_kwarg": "second_value"},
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("breaks_in_ha_version", "extra_msg"),
|
||||
[
|
||||
(None, ""),
|
||||
("2099.1", " It will be removed in HA Core 2099.1."),
|
||||
],
|
||||
)
|
||||
def test_deprecated_hass_argument(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
positional_arguments: list[str],
|
||||
keyword_arguments: dict[str, str],
|
||||
breaks_in_ha_version: str | None,
|
||||
extra_msg: str,
|
||||
) -> None:
|
||||
"""Test deprecated_hass_argument decorator."""
|
||||
|
||||
calls = []
|
||||
|
||||
@deprecated_hass_argument(breaks_in_ha_version=breaks_in_ha_version)
|
||||
def mock_deprecated_function(*args: str, **kwargs: str) -> None:
|
||||
calls.append((args, kwargs))
|
||||
|
||||
mock_deprecated_function(*positional_arguments, **keyword_arguments)
|
||||
assert (
|
||||
"The deprecated argument hass was passed to mock_deprecated_function."
|
||||
f"{extra_msg}"
|
||||
" Use mock_deprecated_function without hass argument instead"
|
||||
) not in caplog.text
|
||||
assert len(calls) == 1
|
||||
|
||||
mock_deprecated_function(hass, *positional_arguments, **keyword_arguments)
|
||||
assert (
|
||||
"The deprecated argument hass was passed to mock_deprecated_function."
|
||||
f"{extra_msg}"
|
||||
" Use mock_deprecated_function without hass argument instead"
|
||||
) in caplog.text
|
||||
assert len(calls) == 2
|
||||
|
||||
caplog.clear()
|
||||
mock_deprecated_function(*positional_arguments, hass=hass, **keyword_arguments)
|
||||
assert (
|
||||
"The deprecated argument hass was passed to mock_deprecated_function."
|
||||
f"{extra_msg}"
|
||||
" Use mock_deprecated_function without hass argument instead"
|
||||
) in caplog.text
|
||||
assert len(calls) == 3
|
||||
|
||||
# Ensure that the two calls are the same, as the second call should have been
|
||||
# modified to remove the hass argument.
|
||||
assert calls[0] == calls[1]
|
||||
assert calls[0] == calls[2]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Test service helpers."""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Callable, Iterable
|
||||
from copy import deepcopy
|
||||
import dataclasses
|
||||
import io
|
||||
@@ -1785,7 +1785,28 @@ async def test_register_admin_service_return_response(
|
||||
assert result == {"test-reply": "test-value1"}
|
||||
|
||||
|
||||
async def test_domain_control_not_async(hass: HomeAssistant, mock_entities) -> None:
|
||||
_DEPRECATED_VERIFY_DOMAIN_CONTROL_MESSAGE = (
|
||||
"The deprecated argument hass was passed to verify_domain_control. It will be"
|
||||
" removed in HA Core 2026.10. Use verify_domain_control without hass argument"
|
||||
" instead"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
# Check that with or without hass behaves the same
|
||||
("decorator", "in_caplog"),
|
||||
[
|
||||
(service.verify_domain_control, True), # old pass-through
|
||||
(lambda _, domain: service.verify_domain_control(domain), False), # new
|
||||
],
|
||||
)
|
||||
async def test_domain_control_not_async(
|
||||
hass: HomeAssistant,
|
||||
mock_entities,
|
||||
decorator: Callable[[HomeAssistant, str], Any],
|
||||
in_caplog: bool,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test domain verification in a service call with an unknown user."""
|
||||
calls = []
|
||||
|
||||
@@ -1794,10 +1815,26 @@ async def test_domain_control_not_async(hass: HomeAssistant, mock_entities) -> N
|
||||
calls.append(call)
|
||||
|
||||
with pytest.raises(exceptions.HomeAssistantError):
|
||||
service.verify_domain_control(hass, "test_domain")(mock_service_log)
|
||||
decorator(hass, "test_domain")(mock_service_log)
|
||||
|
||||
assert (_DEPRECATED_VERIFY_DOMAIN_CONTROL_MESSAGE in caplog.text) == in_caplog
|
||||
|
||||
|
||||
async def test_domain_control_unknown(hass: HomeAssistant, mock_entities) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
# Check that with or without hass behaves the same
|
||||
("decorator", "in_caplog"),
|
||||
[
|
||||
(service.verify_domain_control, True), # old pass-through
|
||||
(lambda _, domain: service.verify_domain_control(domain), False), # new
|
||||
],
|
||||
)
|
||||
async def test_domain_control_unknown(
|
||||
hass: HomeAssistant,
|
||||
mock_entities,
|
||||
decorator: Callable[[HomeAssistant, str], Any],
|
||||
in_caplog: bool,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test domain verification in a service call with an unknown user."""
|
||||
calls = []
|
||||
|
||||
@@ -1809,9 +1846,7 @@ async def test_domain_control_unknown(hass: HomeAssistant, mock_entities) -> Non
|
||||
"homeassistant.helpers.entity_registry.async_get",
|
||||
return_value=Mock(entities=mock_entities),
|
||||
):
|
||||
protected_mock_service = service.verify_domain_control(hass, "test_domain")(
|
||||
mock_service_log
|
||||
)
|
||||
protected_mock_service = decorator(hass, "test_domain")(mock_service_log)
|
||||
|
||||
hass.services.async_register(
|
||||
"test_domain", "test_service", protected_mock_service, schema=None
|
||||
@@ -1827,9 +1862,23 @@ async def test_domain_control_unknown(hass: HomeAssistant, mock_entities) -> Non
|
||||
)
|
||||
assert len(calls) == 0
|
||||
|
||||
assert (_DEPRECATED_VERIFY_DOMAIN_CONTROL_MESSAGE in caplog.text) == in_caplog
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
# Check that with or without hass behaves the same
|
||||
("decorator", "in_caplog"),
|
||||
[
|
||||
(service.verify_domain_control, True), # old pass-through
|
||||
(lambda _, domain: service.verify_domain_control(domain), False), # new
|
||||
],
|
||||
)
|
||||
async def test_domain_control_unauthorized(
|
||||
hass: HomeAssistant, hass_read_only_user: MockUser
|
||||
hass: HomeAssistant,
|
||||
hass_read_only_user: MockUser,
|
||||
decorator: Callable[[HomeAssistant, str], Any],
|
||||
in_caplog: bool,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test domain verification in a service call with an unauthorized user."""
|
||||
mock_registry(
|
||||
@@ -1849,9 +1898,7 @@ async def test_domain_control_unauthorized(
|
||||
"""Define a protected service."""
|
||||
calls.append(call)
|
||||
|
||||
protected_mock_service = service.verify_domain_control(hass, "test_domain")(
|
||||
mock_service_log
|
||||
)
|
||||
protected_mock_service = decorator(hass, "test_domain")(mock_service_log)
|
||||
|
||||
hass.services.async_register(
|
||||
"test_domain", "test_service", protected_mock_service, schema=None
|
||||
@@ -1868,9 +1915,23 @@ async def test_domain_control_unauthorized(
|
||||
|
||||
assert len(calls) == 0
|
||||
|
||||
assert (_DEPRECATED_VERIFY_DOMAIN_CONTROL_MESSAGE in caplog.text) == in_caplog
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
# Check that with or without hass behaves the same
|
||||
("decorator", "in_caplog"),
|
||||
[
|
||||
(service.verify_domain_control, True), # old pass-through
|
||||
(lambda _, domain: service.verify_domain_control(domain), False), # new
|
||||
],
|
||||
)
|
||||
async def test_domain_control_admin(
|
||||
hass: HomeAssistant, hass_admin_user: MockUser
|
||||
hass: HomeAssistant,
|
||||
hass_admin_user: MockUser,
|
||||
decorator: Callable[[HomeAssistant, str], Any],
|
||||
in_caplog: bool,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test domain verification in a service call with an admin user."""
|
||||
mock_registry(
|
||||
@@ -1890,9 +1951,7 @@ async def test_domain_control_admin(
|
||||
"""Define a protected service."""
|
||||
calls.append(call)
|
||||
|
||||
protected_mock_service = service.verify_domain_control(hass, "test_domain")(
|
||||
mock_service_log
|
||||
)
|
||||
protected_mock_service = decorator(hass, "test_domain")(mock_service_log)
|
||||
|
||||
hass.services.async_register(
|
||||
"test_domain", "test_service", protected_mock_service, schema=None
|
||||
@@ -1908,8 +1967,23 @@ async def test_domain_control_admin(
|
||||
|
||||
assert len(calls) == 1
|
||||
|
||||
assert (_DEPRECATED_VERIFY_DOMAIN_CONTROL_MESSAGE in caplog.text) == in_caplog
|
||||
|
||||
async def test_domain_control_no_user(hass: HomeAssistant) -> None:
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
# Check that with or without hass behaves the same
|
||||
("decorator", "in_caplog"),
|
||||
[
|
||||
(service.verify_domain_control, True), # old pass-through
|
||||
(lambda _, domain: service.verify_domain_control(domain), False), # new
|
||||
],
|
||||
)
|
||||
async def test_domain_control_no_user(
|
||||
hass: HomeAssistant,
|
||||
decorator: Callable[[HomeAssistant, str], Any],
|
||||
in_caplog: bool,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test domain verification in a service call with no user."""
|
||||
mock_registry(
|
||||
hass,
|
||||
@@ -1928,9 +2002,7 @@ async def test_domain_control_no_user(hass: HomeAssistant) -> None:
|
||||
"""Define a protected service."""
|
||||
calls.append(call)
|
||||
|
||||
protected_mock_service = service.verify_domain_control(hass, "test_domain")(
|
||||
mock_service_log
|
||||
)
|
||||
protected_mock_service = decorator(hass, "test_domain")(mock_service_log)
|
||||
|
||||
hass.services.async_register(
|
||||
"test_domain", "test_service", protected_mock_service, schema=None
|
||||
@@ -1946,6 +2018,8 @@ async def test_domain_control_no_user(hass: HomeAssistant) -> None:
|
||||
|
||||
assert len(calls) == 1
|
||||
|
||||
assert (_DEPRECATED_VERIFY_DOMAIN_CONTROL_MESSAGE in caplog.text) == in_caplog
|
||||
|
||||
|
||||
async def test_extract_from_service_available_device(hass: HomeAssistant) -> None:
|
||||
"""Test the extraction of entity from service and device is available."""
|
||||
|
||||
Reference in New Issue
Block a user