1 """Component to configure Home Assistant via an API."""
3 from __future__
import annotations
6 from collections.abc
import Callable, Coroutine
7 from http
import HTTPStatus
9 from typing
import Any, cast
11 from aiohttp
import web
12 import voluptuous
as vol
22 from .const
import ACTION_CREATE_UPDATE, ACTION_DELETE
28 """Configure a Group endpoint."""
35 key_schema: Callable[[Any], str],
37 post_write_hook: Callable[[str, str], Coroutine[Any, Any,
None]] |
None =
None,
38 data_schema: Callable[[dict[str, Any]], Any] |
None =
None,
39 data_validator: Callable[
40 [HomeAssistant, str, dict[str, Any]],
41 Coroutine[Any, Any, dict[str, Any] |
None],
45 """Initialize a config view."""
46 self.url = f
"/api/config/{component}/{config_type}/{{config_key}}"
47 self.name = f
"api:config:{component}:{config_type}"
49 self.key_schema = key_schema
50 self.data_schema = data_schema
51 self.post_write_hook = post_write_hook
52 self.data_validator = data_validator
53 self.mutation_lock = asyncio.Lock()
54 if (self.data_schema
is None and self.data_validator
is None)
or (
55 self.data_schema
is not None and self.data_validator
is not None
58 "Must specify exactly one of data_schema or data_validator"
62 """Empty config if file not found."""
63 raise NotImplementedError
66 self, hass: HomeAssistant, data: _DataT, config_key: str
67 ) -> dict[str, Any] |
None:
69 raise NotImplementedError
76 new_value: dict[str, Any],
79 raise NotImplementedError
82 self, hass: HomeAssistant, data: _DataT, config_key: str
83 ) -> dict[str, Any] |
None:
85 raise NotImplementedError
88 async
def get(self, request: web.Request, config_key: str) -> web.Response:
89 """Fetch device specific config."""
90 hass = request.app[KEY_HASS]
91 async
with self.mutation_lock:
92 current = await self.read_config(hass)
93 value = self._get_value(hass, current, config_key)
96 return self.json_message(
"Resource not found", HTTPStatus.NOT_FOUND)
98 return self.json(value)
101 async
def post(self, request: web.Request, config_key: str) -> web.Response:
102 """Validate config and return results."""
104 data = await request.json()
106 return self.json_message(
"Invalid JSON specified", HTTPStatus.BAD_REQUEST)
109 self.key_schema(config_key)
110 except vol.Invalid
as err:
111 return self.json_message(f
"Key malformed: {err}", HTTPStatus.BAD_REQUEST)
113 hass = request.app[KEY_HASS]
118 if self.data_validator:
119 await self.data_validator(hass, config_key, data)
122 self.data_schema(data)
123 except (vol.Invalid, HomeAssistantError)
as err:
124 return self.json_message(
125 f
"Message malformed: {err}", HTTPStatus.BAD_REQUEST
128 path = hass.config.path(self.path)
130 async
with self.mutation_lock:
131 current = await self.read_config(hass)
132 self._write_value(hass, current, config_key, data)
134 await hass.async_add_executor_job(_write, path, current)
136 if self.post_write_hook
is not None:
137 hass.async_create_task(
138 self.post_write_hook(ACTION_CREATE_UPDATE, config_key)
141 return self.json({
"result":
"ok"})
144 async
def delete(self, request: web.Request, config_key: str) -> web.Response:
145 """Remove an entry."""
146 hass = request.app[KEY_HASS]
147 async
with self.mutation_lock:
148 current = await self.read_config(hass)
149 value = self._get_value(hass, current, config_key)
150 path = hass.config.path(self.path)
153 return self.json_message(
"Resource not found", HTTPStatus.BAD_REQUEST)
155 self._delete_value(hass, current, config_key)
156 await hass.async_add_executor_job(_write, path, current)
158 if self.post_write_hook
is not None:
159 hass.async_create_task(self.post_write_hook(ACTION_DELETE, config_key))
161 return self.json({
"result":
"ok"})
164 """Read the config."""
165 current = await hass.async_add_executor_job(_read, hass.config.path(self.path))
167 current = self._empty_config()
168 return cast(_DataT, current)
172 """Configure a list of entries."""
175 """Return an empty config."""
179 self, hass: HomeAssistant, data: dict[str, dict[str, Any]], config_key: str
180 ) -> dict[str, Any] |
None:
182 return data.get(config_key)
187 data: dict[str, dict[str, Any]],
189 new_value: dict[str, Any],
192 data.setdefault(config_key, {}).
update(new_value)
195 self, hass: HomeAssistant, data: dict[str, dict[str, Any]], config_key: str
198 return data.pop(config_key)
202 """Configure key based config entries."""
205 """Return an empty config."""
209 self, hass: HomeAssistant, data: list[dict[str, Any]], config_key: str
210 ) -> dict[str, Any] |
None:
212 return next((val
for val
in data
if val.get(CONF_ID) == config_key),
None)
217 data: list[dict[str, Any]],
219 new_value: dict[str, Any],
222 if (value := self.
_get_value_get_value(hass, data, config_key))
is None:
223 value = {CONF_ID: config_key}
226 value.update(new_value)
229 self, hass: HomeAssistant, data: list[dict[str, Any]], config_key: str
233 idx
for idx, val
in enumerate(data)
if val.get(CONF_ID) == config_key
238 def _read(path: str) -> JSON_TYPE |
None:
239 """Read YAML helper."""
240 if not os.path.isfile(path):
246 def _write(path: str, data: dict | list) ->
None:
247 """Write YAML helper."""
250 contents =
dump(data)
None _delete_value(self, HomeAssistant hass, list[dict[str, Any]] data, str config_key)
dict[str, Any]|None _get_value(self, HomeAssistant hass, list[dict[str, Any]] data, str config_key)
None _write_value(self, HomeAssistant hass, list[dict[str, Any]] data, str config_key, dict[str, Any] new_value)
list[Any] _empty_config(self)
dict[str, Any]|None _get_value(self, HomeAssistant hass, dict[str, dict[str, Any]] data, str config_key)
dict[str, Any] _delete_value(self, HomeAssistant hass, dict[str, dict[str, Any]] data, str config_key)
dict[str, Any] _empty_config(self)
None _write_value(self, HomeAssistant hass, dict[str, dict[str, Any]] data, str config_key, dict[str, Any] new_value)
None __init__(self, str component, str config_type, str path, Callable[[Any], str] key_schema, *Callable[[str, str], Coroutine[Any, Any, None]]|None post_write_hook=None, Callable[[dict[str, Any]], Any]|None data_schema=None, Callable[[HomeAssistant, str, dict[str, Any]], Coroutine[Any, Any, dict[str, Any]|None],]|None data_validator=None)
_DataT _empty_config(self)
dict[str, Any]|None _delete_value(self, HomeAssistant hass, _DataT data, str config_key)
dict[str, Any]|None _get_value(self, HomeAssistant hass, _DataT data, str config_key)
web.Response post(self, web.Request request, str config_key)
web.Response get(self, web.Request request, str config_key)
web.Response delete(self, web.Request request, str config_key)
None _write_value(self, HomeAssistant hass, _DataT data, str config_key, dict[str, Any] new_value)
None _write(str path, dict|list data)
JSON_TYPE|None _read(str path)
_DataT read_config(self, HomeAssistant hass)
IssData update(pyiss.ISS iss)
None write_utf8_file_atomic(str filename, bytes|str utf8_data, bool private=False, str mode="w")
str dump(dict|list _dict)
JSON_TYPE|None load_yaml(str|os.PathLike[str] fname, Secrets|None secrets=None)