core/homeassistant/components/config/__init__.py

248 lines
7.0 KiB
Python
Raw Normal View History

"""Component to configure Home Assistant via an API."""
import asyncio
import importlib
2017-02-21 05:53:55 +00:00
import os
import voluptuous as vol
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import EVENT_COMPONENT_LOADED, CONF_ID
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import ATTR_COMPONENT
2017-02-21 05:53:55 +00:00
from homeassistant.util.yaml import load_yaml, dump
2019-07-31 19:25:30 +00:00
DOMAIN = "config"
SECTIONS = (
2019-07-31 19:25:30 +00:00
"area_registry",
"auth",
"auth_provider_homeassistant",
"automation",
"config_entries",
"core",
"customize",
"device_registry",
"entity_registry",
"group",
"script",
)
2019-07-31 19:25:30 +00:00
ON_DEMAND = ("zwave",)
async def async_setup(hass, config):
"""Set up the config component."""
hass.components.frontend.async_register_built_in_panel(
2019-07-31 19:25:30 +00:00
"config", "config", "hass:settings", require_admin=True
)
async def setup_panel(panel_name):
"""Set up a panel."""
panel = importlib.import_module(f".{panel_name}", __name__)
if not panel:
return
success = await panel.async_setup(hass)
if success:
key = f"{DOMAIN}.{panel_name}"
hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: key})
@callback
def component_loaded(event):
"""Respond to components being loaded."""
panel_name = event.data.get(ATTR_COMPONENT)
if panel_name in ON_DEMAND:
hass.async_create_task(setup_panel(panel_name))
hass.bus.async_listen(EVENT_COMPONENT_LOADED, component_loaded)
tasks = [setup_panel(panel_name) for panel_name in SECTIONS]
for panel_name in ON_DEMAND:
if panel_name in hass.config.components:
tasks.append(setup_panel(panel_name))
if tasks:
await asyncio.wait(tasks)
return True
2017-02-21 05:53:55 +00:00
class BaseEditConfigView(HomeAssistantView):
2017-02-21 05:53:55 +00:00
"""Configure a Group endpoint."""
2019-07-31 19:25:30 +00:00
def __init__(
self,
component,
config_type,
path,
key_schema,
data_schema,
*,
post_write_hook=None,
data_validator=None,
2019-07-31 19:25:30 +00:00
):
2017-02-21 05:53:55 +00:00
"""Initialize a config view."""
self.url = f"/api/config/{component}/{config_type}/{{config_key}}"
self.name = f"api:config:{component}:{config_type}"
2017-02-21 05:53:55 +00:00
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
2017-02-21 05:53:55 +00:00
def _empty_config(self):
"""Empty config if file not found."""
raise NotImplementedError
def _get_value(self, hass, data, config_key):
"""Get value."""
raise NotImplementedError
def _write_value(self, hass, data, config_key, new_value):
"""Set value."""
raise NotImplementedError
def _delete_value(self, hass, data, config_key):
"""Delete value."""
raise NotImplementedError
async def get(self, request, config_key):
2017-02-21 05:53:55 +00:00
"""Fetch device specific config."""
2019-07-31 19:25:30 +00:00
hass = request.app["hass"]
current = await self.read_config(hass)
value = self._get_value(hass, current, config_key)
if value is None:
2019-07-31 19:25:30 +00:00
return self.json_message("Resource not found", 404)
return self.json(value)
2017-02-21 05:53:55 +00:00
async def post(self, request, config_key):
2017-02-21 05:53:55 +00:00
"""Validate config and return results."""
try:
data = await request.json()
2017-02-21 05:53:55 +00:00
except ValueError:
2019-07-31 19:25:30 +00:00
return self.json_message("Invalid JSON specified", 400)
2017-02-21 05:53:55 +00:00
try:
self.key_schema(config_key)
except vol.Invalid as err:
return self.json_message(f"Key malformed: {err}", 400)
2017-02-21 05:53:55 +00:00
hass = request.app["hass"]
2017-02-21 05:53:55 +00:00
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, data)
else:
self.data_schema(data)
except (vol.Invalid, HomeAssistantError) as err:
return self.json_message(f"Message malformed: {err}", 400)
2017-02-21 05:53:55 +00:00
path = hass.config.path(self.path)
current = await self.read_config(hass)
self._write_value(hass, current, config_key, data)
2017-02-21 05:53:55 +00:00
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(hass))
2019-07-31 19:25:30 +00:00
return self.json({"result": "ok"})
async def delete(self, request, config_key):
"""Remove an entry."""
2019-07-31 19:25:30 +00:00
hass = request.app["hass"]
current = await self.read_config(hass)
value = self._get_value(hass, current, config_key)
path = hass.config.path(self.path)
if value is None:
2019-07-31 19:25:30 +00:00
return self.json_message("Resource not found", 404)
self._delete_value(hass, current, config_key)
await hass.async_add_executor_job(_write, path, current)
2017-02-21 05:53:55 +00:00
if self.post_write_hook is not None:
hass.async_create_task(self.post_write_hook(hass))
2017-02-21 05:53:55 +00:00
2019-07-31 19:25:30 +00:00
return self.json({"result": "ok"})
2017-02-21 05:53:55 +00:00
async def read_config(self, hass):
"""Read the config."""
2019-07-31 19:25:30 +00:00
current = await hass.async_add_job(_read, hass.config.path(self.path))
if not current:
current = self._empty_config()
return current
class EditKeyBasedConfigView(BaseEditConfigView):
"""Configure a list of entries."""
def _empty_config(self):
"""Return an empty config."""
return {}
def _get_value(self, hass, data, config_key):
"""Get value."""
2018-02-21 12:16:08 +00:00
return data.get(config_key)
def _write_value(self, hass, data, config_key, new_value):
"""Set value."""
data.setdefault(config_key, {}).update(new_value)
def _delete_value(self, hass, data, config_key):
"""Delete value."""
return data.pop(config_key)
class EditIdBasedConfigView(BaseEditConfigView):
"""Configure key based config entries."""
def _empty_config(self):
"""Return an empty config."""
return []
def _get_value(self, hass, data, config_key):
"""Get value."""
2019-07-31 19:25:30 +00:00
return next((val for val in data if val.get(CONF_ID) == config_key), None)
def _write_value(self, hass, data, config_key, new_value):
"""Set value."""
value = self._get_value(hass, data, config_key)
if value is None:
value = {CONF_ID: config_key}
data.append(value)
value.update(new_value)
def _delete_value(self, hass, data, config_key):
"""Delete value."""
index = next(
2019-07-31 19:25:30 +00:00
idx for idx, val in enumerate(data) if val.get(CONF_ID) == config_key
)
data.pop(index)
2017-02-21 05:53:55 +00:00
def _read(path):
"""Read YAML helper."""
if not os.path.isfile(path):
return None
2017-02-21 05:53:55 +00:00
return load_yaml(path)
def _write(path, data):
"""Write YAML helper."""
2017-02-26 23:28:12 +00:00
# Do it before opening file. If dump causes error it will now not
# truncate the file.
data = dump(data)
2019-07-31 19:25:30 +00:00
with open(path, "w", encoding="utf-8") as outfile:
2017-02-26 23:28:12 +00:00
outfile.write(data)