1 """Support to allow pieces of code to request configuration from the user.
3 Initiate a request by calling the `request_config` method with a callback.
4 This will return a request id that has to be used for future calls.
5 A callback has to be provided to `request_config` which will be called when
6 the user has submitted configuration information.
9 from __future__
import annotations
11 from collections.abc
import Callable
12 from contextlib
import suppress
13 from datetime
import datetime
14 import functools
as ft
15 from typing
import Any
22 callback
as async_callback,
31 _KEY_INSTANCE =
"configurator"
33 DATA_REQUESTS =
"configurator_requests"
35 ATTR_CONFIGURE_ID =
"configure_id"
36 ATTR_DESCRIPTION =
"description"
37 ATTR_DESCRIPTION_IMAGE =
"description_image"
38 ATTR_ERRORS =
"errors"
39 ATTR_FIELDS =
"fields"
40 ATTR_LINK_NAME =
"link_name"
41 ATTR_LINK_URL =
"link_url"
42 ATTR_SUBMIT_CAPTION =
"submit_caption"
44 DOMAIN =
"configurator"
46 ENTITY_ID_FORMAT = DOMAIN +
".{}"
48 SERVICE_CONFIGURE =
"configure"
49 STATE_CONFIGURE =
"configure"
50 STATE_CONFIGURED =
"configured"
52 type ConfiguratorCallback = Callable[[list[dict[str, str]]],
None]
54 CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
62 callback: ConfiguratorCallback |
None =
None,
63 description: str |
None =
None,
64 description_image: str |
None =
None,
65 submit_caption: str |
None =
None,
66 fields: list[dict[str, str]] |
None =
None,
67 link_name: str |
None =
None,
68 link_url: str |
None =
None,
69 entity_picture: str |
None =
None,
71 """Create a new request for configuration.
73 Will return an ID to be used for sequent calls.
75 if description
and link_name
is not None and link_url
is not None:
76 description += f
"\n\n[{link_name}]({link_url})"
78 if description
and description_image
is not None:
79 description += f
"\n\n"
81 if (instance := hass.data.get(_KEY_INSTANCE))
is None:
84 request_id = instance.async_request_config(
85 name, callback, description, submit_caption, fields, entity_picture
88 if DATA_REQUESTS
not in hass.data:
89 hass.data[DATA_REQUESTS] = {}
98 """Create a new request for configuration.
100 Will return an ID to be used for sequent calls.
102 return run_callback_threadsafe(
103 hass.loop, ft.partial(async_request_config, hass, *args, **kwargs)
110 """Add errors to a config request."""
111 with suppress(KeyError):
116 def notify_errors(hass: HomeAssistant, request_id: str, error: str) ->
None:
117 """Add errors to a config request."""
118 return run_callback_threadsafe(
119 hass.loop, async_notify_errors, hass, request_id, error
126 """Mark a configuration request as done."""
127 with suppress(KeyError):
133 """Mark a configuration request as done."""
134 return run_callback_threadsafe(
135 hass.loop, async_request_done, hass, request_id
139 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
140 """Set up the configurator component."""
145 """Return typed configurator_requests data."""
146 return hass.data[DATA_REQUESTS]
150 """The class to keep track of current configuration requests."""
153 """Initialize the configurator."""
156 self._requests: dict[
157 str, tuple[str, list[dict[str, str]], ConfiguratorCallback |
None]
159 hass.services.async_register(
167 callback: ConfiguratorCallback |
None,
168 description: str |
None,
169 submit_caption: str |
None,
170 fields: list[dict[str, str]] |
None,
171 entity_picture: str |
None,
173 """Set up a request for configuration."""
181 self._requests[request_id] = (entity_id, fields, callback)
184 ATTR_CONFIGURE_ID: request_id,
186 ATTR_FRIENDLY_NAME: name,
187 ATTR_ENTITY_PICTURE: entity_picture,
194 (ATTR_DESCRIPTION, description),
195 (ATTR_SUBMIT_CAPTION, submit_caption),
201 self.
hasshass.states.async_set(entity_id, STATE_CONFIGURE, data)
207 """Update the state with errors."""
211 entity_id = self._requests[request_id][0]
213 if (state := self.
hasshass.states.get(entity_id))
is None:
216 new_data =
dict(state.attributes)
217 new_data[ATTR_ERRORS] = error
219 self.
hasshass.states.async_set(entity_id, STATE_CONFIGURE, new_data)
223 """Remove the configuration request."""
227 entity_id = self._requests.pop(request_id)[0]
233 self.
hasshass.states.async_set(entity_id, STATE_CONFIGURED)
236 def deferred_remove(now: datetime) ->
None:
237 """Remove the request state."""
238 self.
hasshass.states.async_remove(entity_id)
243 """Handle a configure service call."""
244 request_id: str |
None = call.data.get(ATTR_CONFIGURE_ID)
249 _, _, callback = self._requests[request_id]
253 job := self.
hasshass.async_run_hass_job(
254 HassJob(callback), call.data.get(ATTR_FIELDS, {})
260 """Generate a unique configurator ID."""
262 return f
"{id(self)}-{self._cur_id}"
265 """Validate that the request belongs to this instance."""
266 return request_id
in self._requests
None async_handle_service_call(self, ServiceCall call)
None __init__(self, HomeAssistant hass)
None async_notify_errors(self, str request_id, str error)
str async_request_config(self, str name, ConfiguratorCallback|None callback, str|None description, str|None submit_caption, list[dict[str, str]]|None fields, str|None entity_picture)
None async_request_done(self, str request_id)
bool _validate_request_id(self, str request_id)
str _generate_unique_id(self)
dict[str, Configurator] _get_requests(HomeAssistant hass)
str request_config(HomeAssistant hass, *Any args, **Any kwargs)
None request_done(HomeAssistant hass, str request_id)
str async_request_config(HomeAssistant hass, str name, ConfiguratorCallback|None callback=None, str|None description=None, str|None description_image=None, str|None submit_caption=None, list[dict[str, str]]|None fields=None, str|None link_name=None, str|None link_url=None, str|None entity_picture=None)
None notify_errors(HomeAssistant hass, str request_id, str error)
None async_request_done(HomeAssistant hass, str request_id)
None async_notify_errors(HomeAssistant hass, str request_id, str error)
bool async_setup(HomeAssistant hass, ConfigType config)
str async_generate_entity_id(str entity_id_format, str|None name, Iterable[str]|None current_ids=None, HomeAssistant|None hass=None)
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)