1 """Websocket API for blueprint."""
3 from __future__
import annotations
6 from collections.abc
import Callable, Coroutine
8 from typing
import Any, cast
10 import voluptuous
as vol
18 from .
import importer, models
19 from .const
import DOMAIN
20 from .errors
import BlueprintException, FailedToLoad, FileAlreadyExists
21 from .schemas
import BLUEPRINT_SCHEMA
26 """Set up the websocket API."""
27 websocket_api.async_register_command(hass, ws_delete_blueprint)
28 websocket_api.async_register_command(hass, ws_import_blueprint)
29 websocket_api.async_register_command(hass, ws_list_blueprints)
30 websocket_api.async_register_command(hass, ws_save_blueprint)
31 websocket_api.async_register_command(hass, ws_substitute_blueprint)
42 Coroutine[Any, Any,
None],
44 ) -> websocket_api.AsyncWebSocketCommandHandler:
45 """Decorate a function to pass in the domain blueprints."""
47 @functools.wraps(func)
48 async
def with_domain_blueprints(
56 if domain_blueprints
is None:
57 connection.send_error(
58 msg[
"id"], websocket_api.ERR_INVALID_FORMAT,
"Unsupported domain"
62 await func(hass, connection, msg, domain_blueprints)
64 return with_domain_blueprints
67 @websocket_api.websocket_command(
{
vol.Required("type"):
"blueprint/list",
68 vol.Required(
"domain"): cv.string,
71 @websocket_api.async_response
77 """List available blueprints."""
79 results: dict[str, Any] = {}
81 if msg[
"domain"]
not in domain_blueprints:
82 connection.send_result(msg[
"id"], results)
87 for path, value
in domain_results.items():
90 "metadata": value.metadata,
93 results[path] = {
"error":
str(value)}
95 connection.send_result(msg[
"id"], results)
98 @websocket_api.websocket_command(
{
vol.Required("type"):
"blueprint/import",
99 vol.Required(
"url"): cv.url,
102 @websocket_api.async_response
108 """Import a blueprint."""
109 async
with asyncio.timeout(10):
110 imported_blueprint = await importer.fetch_blueprint_from_url(hass, msg[
"url"])
112 if imported_blueprint
is None:
113 connection.send_error(
114 msg[
"id"], websocket_api.ERR_NOT_SUPPORTED,
"This url is not supported"
119 domain = imported_blueprint.blueprint.metadata[
"domain"]
123 if domain_blueprints
is None:
124 connection.send_error(
125 msg[
"id"], websocket_api.ERR_INVALID_FORMAT,
"Unsupported domain"
129 suggested_path = f
"{imported_blueprint.suggested_filename}.yaml"
131 exists = bool(await domain_blueprints.async_get_blueprint(suggested_path))
135 connection.send_result(
138 "suggested_filename": imported_blueprint.suggested_filename,
139 "raw_data": imported_blueprint.raw_data,
141 "metadata": imported_blueprint.blueprint.metadata,
143 "validation_errors": imported_blueprint.blueprint.validate(),
149 @websocket_api.websocket_command(
{
vol.Required("type"):
"blueprint/save",
150 vol.Required(
"domain"): cv.string,
151 vol.Required(
"path"): cv.path,
152 vol.Required(
"yaml"): cv.string,
153 vol.Optional(
"source_url"): cv.url,
154 vol.Optional(
"allow_override"): bool,
157 @websocket_api.async_response
158 @_ws_with_blueprint_domain
165 """Save a blueprint."""
168 domain = msg[
"domain"]
171 yaml_data = cast(dict[str, Any], yaml.parse_yaml(msg[
"yaml"]))
173 yaml_data, expected_domain=domain, schema=BLUEPRINT_SCHEMA
175 if "source_url" in msg:
176 blueprint.update_metadata(source_url=msg[
"source_url"])
177 except HomeAssistantError
as err:
178 connection.send_error(msg[
"id"], websocket_api.ERR_INVALID_FORMAT,
str(err))
181 if not path.endswith(
".yaml"):
182 path = f
"{path}.yaml"
185 overrides_existing = await domain_blueprints.async_add_blueprint(
186 blueprint, path, allow_override=msg.get(
"allow_override",
False)
188 except FileAlreadyExists:
189 connection.send_error(msg[
"id"],
"already_exists",
"File already exists")
191 except OSError
as err:
192 connection.send_error(msg[
"id"], websocket_api.ERR_UNKNOWN_ERROR,
str(err))
195 connection.send_result(
198 "overrides_existing": overrides_existing,
203 @websocket_api.websocket_command(
{
vol.Required("type"):
"blueprint/delete",
204 vol.Required(
"domain"): cv.string,
205 vol.Required(
"path"): cv.path,
208 @websocket_api.async_response
209 @_ws_with_blueprint_domain
216 """Delete a blueprint."""
218 await domain_blueprints.async_remove_blueprint(msg[
"path"])
219 except OSError
as err:
220 connection.send_error(msg[
"id"], websocket_api.ERR_UNKNOWN_ERROR,
str(err))
223 connection.send_result(
228 @websocket_api.websocket_command(
{
vol.Required("type"):
"blueprint/substitute",
229 vol.Required(
"domain"): cv.string,
230 vol.Required(
"path"): cv.path,
231 vol.Required(
"input"): dict,
234 @websocket_api.async_response
235 @_ws_with_blueprint_domain
242 """Process a blueprinted config to allow editing."""
244 blueprint_config = {
"use_blueprint": {
"path": msg[
"path"],
"input": msg[
"input"]}}
247 blueprint_inputs = await domain_blueprints.async_inputs_from_config(
250 except BlueprintException
as err:
251 connection.send_error(msg[
"id"], websocket_api.ERR_UNKNOWN_ERROR,
str(err))
255 config = blueprint_inputs.async_substitute()
256 except yaml.UndefinedSubstitution
as err:
257 connection.send_error(msg[
"id"], websocket_api.ERR_UNKNOWN_ERROR,
str(err))
260 connection.send_result(msg[
"id"], {
"substituted_config": config})
261
blueprint.DomainBlueprints async_get_blueprints(HomeAssistant hass)
None ws_substitute_blueprint(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg, models.DomainBlueprints domain_blueprints)
None ws_list_blueprints(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None ws_delete_blueprint(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg, models.DomainBlueprints domain_blueprints)
None ws_import_blueprint(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None async_setup(HomeAssistant hass)
websocket_api.AsyncWebSocketCommandHandler _ws_with_blueprint_domain(Callable[[HomeAssistant, websocket_api.ActiveConnection, dict[str, Any], models.DomainBlueprints,], Coroutine[Any, Any, None],] func)
None ws_save_blueprint(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg, models.DomainBlueprints domain_blueprints)
web.Response get(self, web.Request request, str config_key)