Fix config modules being imported in the event loop (#112462)
* Fix config modules being imported in the event loop There was a late import in this integration because of the circular import. The code has been rearranged to avoid the circular imports * fixes * fixes * fix patching * make eager * remove unrelated change from this branchpull/112472/head
parent
f03be2fd9e
commit
3f9dbd3e25
|
@ -1,48 +1,44 @@
|
|||
"""Component to configure Home Assistant via an API."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable, Coroutine
|
||||
from http import HTTPStatus
|
||||
import importlib
|
||||
import os
|
||||
from typing import Any, Generic, TypeVar, cast
|
||||
|
||||
from aiohttp import web
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import frontend
|
||||
from homeassistant.components.http import HomeAssistantView, require_admin
|
||||
from homeassistant.const import CONF_ID, EVENT_COMPONENT_LOADED
|
||||
from homeassistant.const import EVENT_COMPONENT_LOADED
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.setup import ATTR_COMPONENT
|
||||
from homeassistant.util.file import write_utf8_file_atomic
|
||||
from homeassistant.util.yaml import dump, load_yaml
|
||||
from homeassistant.util.yaml.loader import JSON_TYPE
|
||||
|
||||
_DataT = TypeVar("_DataT", dict[str, dict[str, Any]], list[dict[str, Any]])
|
||||
|
||||
DOMAIN = "config"
|
||||
from . import (
|
||||
area_registry,
|
||||
auth,
|
||||
auth_provider_homeassistant,
|
||||
automation,
|
||||
config_entries,
|
||||
core,
|
||||
device_registry,
|
||||
entity_registry,
|
||||
floor_registry,
|
||||
label_registry,
|
||||
scene,
|
||||
script,
|
||||
)
|
||||
from .const import DOMAIN
|
||||
|
||||
SECTIONS = (
|
||||
"area_registry",
|
||||
"auth",
|
||||
"auth_provider_homeassistant",
|
||||
"automation",
|
||||
"config_entries",
|
||||
"core",
|
||||
"device_registry",
|
||||
"entity_registry",
|
||||
"floor_registry",
|
||||
"label_registry",
|
||||
"script",
|
||||
"scene",
|
||||
area_registry,
|
||||
auth,
|
||||
auth_provider_homeassistant,
|
||||
automation,
|
||||
config_entries,
|
||||
core,
|
||||
device_registry,
|
||||
entity_registry,
|
||||
floor_registry,
|
||||
label_registry,
|
||||
script,
|
||||
scene,
|
||||
)
|
||||
ACTION_CREATE_UPDATE = "create_update"
|
||||
ACTION_DELETE = "delete"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
|
||||
|
||||
|
@ -53,231 +49,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
hass, "config", "config", "hass:cog", require_admin=True
|
||||
)
|
||||
|
||||
for panel_name in SECTIONS:
|
||||
panel = importlib.import_module(f".{panel_name}", __name__)
|
||||
|
||||
for panel in SECTIONS:
|
||||
if panel.async_setup(hass):
|
||||
key = f"{DOMAIN}.{panel_name}"
|
||||
name = panel.__name__.split(".")[-1]
|
||||
key = f"{DOMAIN}.{name}"
|
||||
hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: key})
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class BaseEditConfigView(HomeAssistantView, Generic[_DataT]):
|
||||
"""Configure a Group endpoint."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
component: str,
|
||||
config_type: str,
|
||||
path: str,
|
||||
key_schema: Callable[[Any], str],
|
||||
data_schema: Callable[[dict[str, Any]], Any],
|
||||
*,
|
||||
post_write_hook: Callable[[str, str], Coroutine[Any, Any, None]] | None = None,
|
||||
data_validator: Callable[
|
||||
[HomeAssistant, str, dict[str, Any]],
|
||||
Coroutine[Any, Any, dict[str, Any] | None],
|
||||
]
|
||||
| None = None,
|
||||
) -> None:
|
||||
"""Initialize a config view."""
|
||||
self.url = f"/api/config/{component}/{config_type}/{{config_key}}"
|
||||
self.name = f"api:config:{component}:{config_type}"
|
||||
self.path = path
|
||||
self.key_schema = key_schema
|
||||
self.data_schema = data_schema
|
||||
self.post_write_hook = post_write_hook
|
||||
self.data_validator = data_validator
|
||||
self.mutation_lock = asyncio.Lock()
|
||||
|
||||
def _empty_config(self) -> _DataT:
|
||||
"""Empty config if file not found."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _get_value(
|
||||
self, hass: HomeAssistant, data: _DataT, config_key: str
|
||||
) -> dict[str, Any] | None:
|
||||
"""Get value."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _write_value(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
data: _DataT,
|
||||
config_key: str,
|
||||
new_value: dict[str, Any],
|
||||
) -> None:
|
||||
"""Set value."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _delete_value(
|
||||
self, hass: HomeAssistant, data: _DataT, config_key: str
|
||||
) -> dict[str, Any] | None:
|
||||
"""Delete value."""
|
||||
raise NotImplementedError
|
||||
|
||||
@require_admin
|
||||
async def get(self, request: web.Request, config_key: str) -> web.Response:
|
||||
"""Fetch device specific config."""
|
||||
hass: HomeAssistant = request.app["hass"]
|
||||
async with self.mutation_lock:
|
||||
current = await self.read_config(hass)
|
||||
value = self._get_value(hass, current, config_key)
|
||||
|
||||
if value is None:
|
||||
return self.json_message("Resource not found", HTTPStatus.NOT_FOUND)
|
||||
|
||||
return self.json(value)
|
||||
|
||||
@require_admin
|
||||
async def post(self, request: web.Request, config_key: str) -> web.Response:
|
||||
"""Validate config and return results."""
|
||||
try:
|
||||
data = await request.json()
|
||||
except ValueError:
|
||||
return self.json_message("Invalid JSON specified", HTTPStatus.BAD_REQUEST)
|
||||
|
||||
try:
|
||||
self.key_schema(config_key)
|
||||
except vol.Invalid as err:
|
||||
return self.json_message(f"Key malformed: {err}", HTTPStatus.BAD_REQUEST)
|
||||
|
||||
hass: HomeAssistant = request.app["hass"]
|
||||
|
||||
try:
|
||||
# We just validate, we don't store that data because
|
||||
# we don't want to store the defaults.
|
||||
if self.data_validator:
|
||||
await self.data_validator(hass, config_key, data)
|
||||
else:
|
||||
self.data_schema(data)
|
||||
except (vol.Invalid, HomeAssistantError) as err:
|
||||
return self.json_message(
|
||||
f"Message malformed: {err}", HTTPStatus.BAD_REQUEST
|
||||
)
|
||||
|
||||
path = hass.config.path(self.path)
|
||||
|
||||
async with self.mutation_lock:
|
||||
current = await self.read_config(hass)
|
||||
self._write_value(hass, current, config_key, data)
|
||||
|
||||
await hass.async_add_executor_job(_write, path, current)
|
||||
|
||||
if self.post_write_hook is not None:
|
||||
hass.async_create_task(
|
||||
self.post_write_hook(ACTION_CREATE_UPDATE, config_key)
|
||||
)
|
||||
|
||||
return self.json({"result": "ok"})
|
||||
|
||||
@require_admin
|
||||
async def delete(self, request: web.Request, config_key: str) -> web.Response:
|
||||
"""Remove an entry."""
|
||||
hass: HomeAssistant = request.app["hass"]
|
||||
async with self.mutation_lock:
|
||||
current = await self.read_config(hass)
|
||||
value = self._get_value(hass, current, config_key)
|
||||
path = hass.config.path(self.path)
|
||||
|
||||
if value is None:
|
||||
return self.json_message("Resource not found", HTTPStatus.BAD_REQUEST)
|
||||
|
||||
self._delete_value(hass, current, config_key)
|
||||
await hass.async_add_executor_job(_write, path, current)
|
||||
|
||||
if self.post_write_hook is not None:
|
||||
hass.async_create_task(self.post_write_hook(ACTION_DELETE, config_key))
|
||||
|
||||
return self.json({"result": "ok"})
|
||||
|
||||
async def read_config(self, hass: HomeAssistant) -> _DataT:
|
||||
"""Read the config."""
|
||||
current = await hass.async_add_executor_job(_read, hass.config.path(self.path))
|
||||
if not current:
|
||||
current = self._empty_config()
|
||||
return cast(_DataT, current)
|
||||
|
||||
|
||||
class EditKeyBasedConfigView(BaseEditConfigView[dict[str, dict[str, Any]]]):
|
||||
"""Configure a list of entries."""
|
||||
|
||||
def _empty_config(self) -> dict[str, Any]:
|
||||
"""Return an empty config."""
|
||||
return {}
|
||||
|
||||
def _get_value(
|
||||
self, hass: HomeAssistant, data: dict[str, dict[str, Any]], config_key: str
|
||||
) -> dict[str, Any] | None:
|
||||
"""Get value."""
|
||||
return data.get(config_key)
|
||||
|
||||
def _write_value(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
data: dict[str, dict[str, Any]],
|
||||
config_key: str,
|
||||
new_value: dict[str, Any],
|
||||
) -> None:
|
||||
"""Set value."""
|
||||
data.setdefault(config_key, {}).update(new_value)
|
||||
|
||||
def _delete_value(
|
||||
self, hass: HomeAssistant, data: dict[str, dict[str, Any]], config_key: str
|
||||
) -> dict[str, Any]:
|
||||
"""Delete value."""
|
||||
return data.pop(config_key)
|
||||
|
||||
|
||||
class EditIdBasedConfigView(BaseEditConfigView[list[dict[str, Any]]]):
|
||||
"""Configure key based config entries."""
|
||||
|
||||
def _empty_config(self) -> list[Any]:
|
||||
"""Return an empty config."""
|
||||
return []
|
||||
|
||||
def _get_value(
|
||||
self, hass: HomeAssistant, data: list[dict[str, Any]], config_key: str
|
||||
) -> dict[str, Any] | None:
|
||||
"""Get value."""
|
||||
return next((val for val in data if val.get(CONF_ID) == config_key), None)
|
||||
|
||||
def _write_value(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
data: list[dict[str, Any]],
|
||||
config_key: str,
|
||||
new_value: dict[str, Any],
|
||||
) -> None:
|
||||
"""Set value."""
|
||||
if (value := self._get_value(hass, data, config_key)) is None:
|
||||
value = {CONF_ID: config_key}
|
||||
data.append(value)
|
||||
|
||||
value.update(new_value)
|
||||
|
||||
def _delete_value(
|
||||
self, hass: HomeAssistant, data: list[dict[str, Any]], config_key: str
|
||||
) -> None:
|
||||
"""Delete value."""
|
||||
index = next(
|
||||
idx for idx, val in enumerate(data) if val.get(CONF_ID) == config_key
|
||||
)
|
||||
data.pop(index)
|
||||
|
||||
|
||||
def _read(path: str) -> JSON_TYPE | None:
|
||||
"""Read YAML helper."""
|
||||
if not os.path.isfile(path):
|
||||
return None
|
||||
|
||||
return load_yaml(path)
|
||||
|
||||
|
||||
def _write(path: str, data: dict | list) -> None:
|
||||
"""Write YAML helper."""
|
||||
# Do it before opening file. If dump causes error it will now not
|
||||
# truncate the file.
|
||||
contents = dump(data)
|
||||
write_utf8_file_atomic(path, contents)
|
||||
|
|
|
@ -14,7 +14,8 @@ from homeassistant.const import CONF_ID, SERVICE_RELOAD
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||
|
||||
from . import ACTION_DELETE, EditIdBasedConfigView
|
||||
from .const import ACTION_DELETE
|
||||
from .view import EditIdBasedConfigView
|
||||
|
||||
|
||||
@callback
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
"""Constants for config."""
|
||||
|
||||
ACTION_CREATE_UPDATE = "create_update"
|
||||
ACTION_DELETE = "delete"
|
||||
DOMAIN = "config"
|
|
@ -10,7 +10,8 @@ from homeassistant.const import CONF_ID, SERVICE_RELOAD
|
|||
from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||
|
||||
from . import ACTION_DELETE, EditIdBasedConfigView
|
||||
from .const import ACTION_DELETE
|
||||
from .view import EditIdBasedConfigView
|
||||
|
||||
|
||||
@callback
|
||||
|
|
|
@ -13,7 +13,8 @@ from homeassistant.const import SERVICE_RELOAD
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||
|
||||
from . import ACTION_DELETE, EditKeyBasedConfigView
|
||||
from .const import ACTION_DELETE
|
||||
from .view import EditKeyBasedConfigView
|
||||
|
||||
|
||||
@callback
|
||||
|
|
|
@ -0,0 +1,243 @@
|
|||
"""Component to configure Home Assistant via an API."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable, Coroutine
|
||||
from http import HTTPStatus
|
||||
import os
|
||||
from typing import Any, Generic, TypeVar, cast
|
||||
|
||||
from aiohttp import web
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.http import HomeAssistantView, require_admin
|
||||
from homeassistant.const import CONF_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.util.file import write_utf8_file_atomic
|
||||
from homeassistant.util.yaml import dump, load_yaml
|
||||
from homeassistant.util.yaml.loader import JSON_TYPE
|
||||
|
||||
from .const import ACTION_CREATE_UPDATE, ACTION_DELETE
|
||||
|
||||
_DataT = TypeVar("_DataT", dict[str, dict[str, Any]], list[dict[str, Any]])
|
||||
|
||||
|
||||
class BaseEditConfigView(HomeAssistantView, Generic[_DataT]):
|
||||
"""Configure a Group endpoint."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
component: str,
|
||||
config_type: str,
|
||||
path: str,
|
||||
key_schema: Callable[[Any], str],
|
||||
data_schema: Callable[[dict[str, Any]], Any],
|
||||
*,
|
||||
post_write_hook: Callable[[str, str], Coroutine[Any, Any, None]] | None = None,
|
||||
data_validator: Callable[
|
||||
[HomeAssistant, str, dict[str, Any]],
|
||||
Coroutine[Any, Any, dict[str, Any] | None],
|
||||
]
|
||||
| None = None,
|
||||
) -> None:
|
||||
"""Initialize a config view."""
|
||||
self.url = f"/api/config/{component}/{config_type}/{{config_key}}"
|
||||
self.name = f"api:config:{component}:{config_type}"
|
||||
self.path = path
|
||||
self.key_schema = key_schema
|
||||
self.data_schema = data_schema
|
||||
self.post_write_hook = post_write_hook
|
||||
self.data_validator = data_validator
|
||||
self.mutation_lock = asyncio.Lock()
|
||||
|
||||
def _empty_config(self) -> _DataT:
|
||||
"""Empty config if file not found."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _get_value(
|
||||
self, hass: HomeAssistant, data: _DataT, config_key: str
|
||||
) -> dict[str, Any] | None:
|
||||
"""Get value."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _write_value(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
data: _DataT,
|
||||
config_key: str,
|
||||
new_value: dict[str, Any],
|
||||
) -> None:
|
||||
"""Set value."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _delete_value(
|
||||
self, hass: HomeAssistant, data: _DataT, config_key: str
|
||||
) -> dict[str, Any] | None:
|
||||
"""Delete value."""
|
||||
raise NotImplementedError
|
||||
|
||||
@require_admin
|
||||
async def get(self, request: web.Request, config_key: str) -> web.Response:
|
||||
"""Fetch device specific config."""
|
||||
hass: HomeAssistant = request.app["hass"]
|
||||
async with self.mutation_lock:
|
||||
current = await self.read_config(hass)
|
||||
value = self._get_value(hass, current, config_key)
|
||||
|
||||
if value is None:
|
||||
return self.json_message("Resource not found", HTTPStatus.NOT_FOUND)
|
||||
|
||||
return self.json(value)
|
||||
|
||||
@require_admin
|
||||
async def post(self, request: web.Request, config_key: str) -> web.Response:
|
||||
"""Validate config and return results."""
|
||||
try:
|
||||
data = await request.json()
|
||||
except ValueError:
|
||||
return self.json_message("Invalid JSON specified", HTTPStatus.BAD_REQUEST)
|
||||
|
||||
try:
|
||||
self.key_schema(config_key)
|
||||
except vol.Invalid as err:
|
||||
return self.json_message(f"Key malformed: {err}", HTTPStatus.BAD_REQUEST)
|
||||
|
||||
hass: HomeAssistant = request.app["hass"]
|
||||
|
||||
try:
|
||||
# We just validate, we don't store that data because
|
||||
# we don't want to store the defaults.
|
||||
if self.data_validator:
|
||||
await self.data_validator(hass, config_key, data)
|
||||
else:
|
||||
self.data_schema(data)
|
||||
except (vol.Invalid, HomeAssistantError) as err:
|
||||
return self.json_message(
|
||||
f"Message malformed: {err}", HTTPStatus.BAD_REQUEST
|
||||
)
|
||||
|
||||
path = hass.config.path(self.path)
|
||||
|
||||
async with self.mutation_lock:
|
||||
current = await self.read_config(hass)
|
||||
self._write_value(hass, current, config_key, data)
|
||||
|
||||
await hass.async_add_executor_job(_write, path, current)
|
||||
|
||||
if self.post_write_hook is not None:
|
||||
hass.async_create_task(
|
||||
self.post_write_hook(ACTION_CREATE_UPDATE, config_key)
|
||||
)
|
||||
|
||||
return self.json({"result": "ok"})
|
||||
|
||||
@require_admin
|
||||
async def delete(self, request: web.Request, config_key: str) -> web.Response:
|
||||
"""Remove an entry."""
|
||||
hass: HomeAssistant = request.app["hass"]
|
||||
async with self.mutation_lock:
|
||||
current = await self.read_config(hass)
|
||||
value = self._get_value(hass, current, config_key)
|
||||
path = hass.config.path(self.path)
|
||||
|
||||
if value is None:
|
||||
return self.json_message("Resource not found", HTTPStatus.BAD_REQUEST)
|
||||
|
||||
self._delete_value(hass, current, config_key)
|
||||
await hass.async_add_executor_job(_write, path, current)
|
||||
|
||||
if self.post_write_hook is not None:
|
||||
hass.async_create_task(self.post_write_hook(ACTION_DELETE, config_key))
|
||||
|
||||
return self.json({"result": "ok"})
|
||||
|
||||
async def read_config(self, hass: HomeAssistant) -> _DataT:
|
||||
"""Read the config."""
|
||||
current = await hass.async_add_executor_job(_read, hass.config.path(self.path))
|
||||
if not current:
|
||||
current = self._empty_config()
|
||||
return cast(_DataT, current)
|
||||
|
||||
|
||||
class EditKeyBasedConfigView(BaseEditConfigView[dict[str, dict[str, Any]]]):
|
||||
"""Configure a list of entries."""
|
||||
|
||||
def _empty_config(self) -> dict[str, Any]:
|
||||
"""Return an empty config."""
|
||||
return {}
|
||||
|
||||
def _get_value(
|
||||
self, hass: HomeAssistant, data: dict[str, dict[str, Any]], config_key: str
|
||||
) -> dict[str, Any] | None:
|
||||
"""Get value."""
|
||||
return data.get(config_key)
|
||||
|
||||
def _write_value(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
data: dict[str, dict[str, Any]],
|
||||
config_key: str,
|
||||
new_value: dict[str, Any],
|
||||
) -> None:
|
||||
"""Set value."""
|
||||
data.setdefault(config_key, {}).update(new_value)
|
||||
|
||||
def _delete_value(
|
||||
self, hass: HomeAssistant, data: dict[str, dict[str, Any]], config_key: str
|
||||
) -> dict[str, Any]:
|
||||
"""Delete value."""
|
||||
return data.pop(config_key)
|
||||
|
||||
|
||||
class EditIdBasedConfigView(BaseEditConfigView[list[dict[str, Any]]]):
|
||||
"""Configure key based config entries."""
|
||||
|
||||
def _empty_config(self) -> list[Any]:
|
||||
"""Return an empty config."""
|
||||
return []
|
||||
|
||||
def _get_value(
|
||||
self, hass: HomeAssistant, data: list[dict[str, Any]], config_key: str
|
||||
) -> dict[str, Any] | None:
|
||||
"""Get value."""
|
||||
return next((val for val in data if val.get(CONF_ID) == config_key), None)
|
||||
|
||||
def _write_value(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
data: list[dict[str, Any]],
|
||||
config_key: str,
|
||||
new_value: dict[str, Any],
|
||||
) -> None:
|
||||
"""Set value."""
|
||||
if (value := self._get_value(hass, data, config_key)) is None:
|
||||
value = {CONF_ID: config_key}
|
||||
data.append(value)
|
||||
|
||||
value.update(new_value)
|
||||
|
||||
def _delete_value(
|
||||
self, hass: HomeAssistant, data: list[dict[str, Any]], config_key: str
|
||||
) -> None:
|
||||
"""Delete value."""
|
||||
index = next(
|
||||
idx for idx, val in enumerate(data) if val.get(CONF_ID) == config_key
|
||||
)
|
||||
data.pop(index)
|
||||
|
||||
|
||||
def _read(path: str) -> JSON_TYPE | None:
|
||||
"""Read YAML helper."""
|
||||
if not os.path.isfile(path):
|
||||
return None
|
||||
|
||||
return load_yaml(path)
|
||||
|
||||
|
||||
def _write(path: str, data: dict | list) -> None:
|
||||
"""Write YAML helper."""
|
||||
# Do it before opening file. If dump causes error it will now not
|
||||
# truncate the file.
|
||||
contents = dump(data)
|
||||
write_utf8_file_atomic(path, contents)
|
|
@ -51,11 +51,11 @@ def mock_config_store(data=None):
|
|||
return result
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.config._read",
|
||||
"homeassistant.components.config.view._read",
|
||||
side_effect=mock_read,
|
||||
autospec=True,
|
||||
), patch(
|
||||
"homeassistant.components.config._write",
|
||||
"homeassistant.components.config.view._write",
|
||||
side_effect=mock_write,
|
||||
autospec=True,
|
||||
), patch(
|
||||
|
|
|
@ -8,6 +8,7 @@ import pytest
|
|||
|
||||
from homeassistant.bootstrap import async_setup_component
|
||||
from homeassistant.components import config
|
||||
from homeassistant.components.config import automation
|
||||
from homeassistant.const import STATE_ON, STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
@ -41,7 +42,7 @@ async def test_get_automation_config(
|
|||
setup_automation,
|
||||
) -> None:
|
||||
"""Test getting automation config."""
|
||||
with patch.object(config, "SECTIONS", ["automation"]):
|
||||
with patch.object(config, "SECTIONS", [automation]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
client = await hass_client()
|
||||
|
@ -64,7 +65,7 @@ async def test_update_automation_config(
|
|||
setup_automation,
|
||||
) -> None:
|
||||
"""Test updating automation config."""
|
||||
with patch.object(config, "SECTIONS", ["automation"]):
|
||||
with patch.object(config, "SECTIONS", [automation]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("automation")) == []
|
||||
|
@ -153,7 +154,7 @@ async def test_update_automation_config_with_error(
|
|||
validation_error: str,
|
||||
) -> None:
|
||||
"""Test updating automation config with errors."""
|
||||
with patch.object(config, "SECTIONS", ["automation"]):
|
||||
with patch.object(config, "SECTIONS", [automation]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("automation")) == []
|
||||
|
@ -206,7 +207,7 @@ async def test_update_automation_config_with_blueprint_substitution_error(
|
|||
validation_error: str,
|
||||
) -> None:
|
||||
"""Test updating automation config with errors."""
|
||||
with patch.object(config, "SECTIONS", ["automation"]):
|
||||
with patch.object(config, "SECTIONS", [automation]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("automation")) == []
|
||||
|
@ -242,7 +243,7 @@ async def test_update_remove_key_automation_config(
|
|||
setup_automation,
|
||||
) -> None:
|
||||
"""Test updating automation config while removing a key."""
|
||||
with patch.object(config, "SECTIONS", ["automation"]):
|
||||
with patch.object(config, "SECTIONS", [automation]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("automation")) == []
|
||||
|
@ -281,7 +282,7 @@ async def test_bad_formatted_automations(
|
|||
setup_automation,
|
||||
) -> None:
|
||||
"""Test that we handle automations without ID."""
|
||||
with patch.object(config, "SECTIONS", ["automation"]):
|
||||
with patch.object(config, "SECTIONS", [automation]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("automation")) == []
|
||||
|
@ -347,7 +348,7 @@ async def test_delete_automation(
|
|||
|
||||
assert len(entity_registry.entities) == 2
|
||||
|
||||
with patch.object(config, "SECTIONS", ["automation"]):
|
||||
with patch.object(config, "SECTIONS", [automation]):
|
||||
assert await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("automation")) == [
|
||||
|
@ -385,7 +386,7 @@ async def test_api_calls_require_admin(
|
|||
setup_automation,
|
||||
) -> None:
|
||||
"""Test cloud APIs endpoints do not work as a normal user."""
|
||||
with patch.object(config, "SECTIONS", ["automation"]):
|
||||
with patch.object(config, "SECTIONS", [automation]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
hass_config_store["automations.yaml"] = [{"id": "sun"}, {"id": "moon"}]
|
||||
|
|
|
@ -6,6 +6,7 @@ import pytest
|
|||
|
||||
from homeassistant.bootstrap import async_setup_component
|
||||
from homeassistant.components import config
|
||||
from homeassistant.components.config import core
|
||||
from homeassistant.components.websocket_api.const import TYPE_RESULT
|
||||
from homeassistant.const import (
|
||||
CONF_UNIT_SYSTEM,
|
||||
|
@ -23,7 +24,7 @@ from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
|||
@pytest.fixture
|
||||
async def client(hass, hass_ws_client):
|
||||
"""Fixture that can interact with the config manager API."""
|
||||
with patch.object(config, "SECTIONS", ["core"]):
|
||||
with patch.object(config, "SECTIONS", [core]):
|
||||
assert await async_setup_component(hass, "config", {})
|
||||
return await hass_ws_client(hass)
|
||||
|
||||
|
@ -32,7 +33,7 @@ async def test_validate_config_ok(
|
|||
hass: HomeAssistant, hass_client: ClientSessionGenerator
|
||||
) -> None:
|
||||
"""Test checking config."""
|
||||
with patch.object(config, "SECTIONS", ["core"]):
|
||||
with patch.object(config, "SECTIONS", [core]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
client = await hass_client()
|
||||
|
@ -95,7 +96,7 @@ async def test_validate_config_requires_admin(
|
|||
hass_read_only_access_token: str,
|
||||
) -> None:
|
||||
"""Test checking configuration does not work as a normal user."""
|
||||
with patch.object(config, "SECTIONS", ["core"]):
|
||||
with patch.object(config, "SECTIONS", [core]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
client = await hass_client(hass_read_only_access_token)
|
||||
|
@ -180,7 +181,7 @@ async def test_websocket_core_update_not_admin(
|
|||
) -> None:
|
||||
"""Test core config fails for non admin."""
|
||||
hass_admin_user.groups = []
|
||||
with patch.object(config, "SECTIONS", ["core"]):
|
||||
with patch.object(config, "SECTIONS", [core]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
|
|
|
@ -7,6 +7,7 @@ import pytest
|
|||
|
||||
from homeassistant.bootstrap import async_setup_component
|
||||
from homeassistant.components import config
|
||||
from homeassistant.components.config import scene
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
|
@ -27,7 +28,7 @@ async def test_create_scene(
|
|||
setup_scene,
|
||||
) -> None:
|
||||
"""Test creating a scene."""
|
||||
with patch.object(config, "SECTIONS", ["scene"]):
|
||||
with patch.object(config, "SECTIONS", [scene]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("scene")) == []
|
||||
|
@ -74,7 +75,7 @@ async def test_update_scene(
|
|||
setup_scene,
|
||||
) -> None:
|
||||
"""Test updating a scene."""
|
||||
with patch.object(config, "SECTIONS", ["scene"]):
|
||||
with patch.object(config, "SECTIONS", [scene]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("scene")) == []
|
||||
|
@ -122,7 +123,7 @@ async def test_bad_formatted_scene(
|
|||
setup_scene,
|
||||
) -> None:
|
||||
"""Test that we handle scene without ID."""
|
||||
with patch.object(config, "SECTIONS", ["scene"]):
|
||||
with patch.object(config, "SECTIONS", [scene]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("scene")) == []
|
||||
|
@ -192,7 +193,7 @@ async def test_delete_scene(
|
|||
|
||||
assert len(entity_registry.entities) == 2
|
||||
|
||||
with patch.object(config, "SECTIONS", ["scene"]):
|
||||
with patch.object(config, "SECTIONS", [scene]):
|
||||
assert await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("scene")) == [
|
||||
|
@ -232,7 +233,7 @@ async def test_api_calls_require_admin(
|
|||
setup_scene,
|
||||
) -> None:
|
||||
"""Test scene APIs endpoints do not work as a normal user."""
|
||||
with patch.object(config, "SECTIONS", ["scene"]):
|
||||
with patch.object(config, "SECTIONS", [scene]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
hass_config_store["scenes.yaml"] = [
|
||||
|
|
|
@ -8,6 +8,7 @@ import pytest
|
|||
|
||||
from homeassistant.bootstrap import async_setup_component
|
||||
from homeassistant.components import config
|
||||
from homeassistant.components.config import script
|
||||
from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
@ -32,7 +33,7 @@ async def test_get_script_config(
|
|||
hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_config_store
|
||||
) -> None:
|
||||
"""Test getting script config."""
|
||||
with patch.object(config, "SECTIONS", ["script"]):
|
||||
with patch.object(config, "SECTIONS", [script]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
client = await hass_client()
|
||||
|
@ -55,7 +56,7 @@ async def test_update_script_config(
|
|||
hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_config_store
|
||||
) -> None:
|
||||
"""Test updating script config."""
|
||||
with patch.object(config, "SECTIONS", ["script"]):
|
||||
with patch.object(config, "SECTIONS", [script]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("script")) == []
|
||||
|
@ -91,7 +92,7 @@ async def test_invalid_object_id(
|
|||
hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_config_store
|
||||
) -> None:
|
||||
"""Test creating a script with an invalid object_id."""
|
||||
with patch.object(config, "SECTIONS", ["script"]):
|
||||
with patch.object(config, "SECTIONS", [script]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("script")) == []
|
||||
|
@ -156,7 +157,7 @@ async def test_update_script_config_with_error(
|
|||
validation_error: str,
|
||||
) -> None:
|
||||
"""Test updating script config with errors."""
|
||||
with patch.object(config, "SECTIONS", ["script"]):
|
||||
with patch.object(config, "SECTIONS", [script]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("script")) == []
|
||||
|
@ -207,7 +208,7 @@ async def test_update_script_config_with_blueprint_substitution_error(
|
|||
validation_error: str,
|
||||
) -> None:
|
||||
"""Test updating script config with errors."""
|
||||
with patch.object(config, "SECTIONS", ["script"]):
|
||||
with patch.object(config, "SECTIONS", [script]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("script")) == []
|
||||
|
@ -240,7 +241,7 @@ async def test_update_remove_key_script_config(
|
|||
hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_config_store
|
||||
) -> None:
|
||||
"""Test updating script config while removing a key."""
|
||||
with patch.object(config, "SECTIONS", ["script"]):
|
||||
with patch.object(config, "SECTIONS", [script]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("script")) == []
|
||||
|
@ -287,7 +288,7 @@ async def test_delete_script(
|
|||
hass_config_store,
|
||||
) -> None:
|
||||
"""Test deleting a script."""
|
||||
with patch.object(config, "SECTIONS", ["script"]):
|
||||
with patch.object(config, "SECTIONS", [script]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
assert sorted(hass.states.async_entity_ids("script")) == [
|
||||
|
@ -326,7 +327,7 @@ async def test_api_calls_require_admin(
|
|||
hass_config_store,
|
||||
) -> None:
|
||||
"""Test script APIs endpoints do not work as a normal user."""
|
||||
with patch.object(config, "SECTIONS", ["script"]):
|
||||
with patch.object(config, "SECTIONS", [script]):
|
||||
await async_setup_component(hass, "config", {})
|
||||
|
||||
hass_config_store["scripts.yaml"] = {
|
||||
|
|
Loading…
Reference in New Issue