1 """Helpers for creating schema based data entry flows."""
3 from __future__
import annotations
5 from abc
import ABC, abstractmethod
6 from collections.abc
import Callable, Container, Coroutine, Mapping
8 from dataclasses
import dataclass
10 from typing
import Any, cast
12 import voluptuous
as vol
23 from .
import entity_registry
as er, selector
24 from .typing
import UNDEFINED, UndefinedType
28 """Validation failed."""
33 """Define a config or options flow step."""
36 @dataclass(slots=
True)
38 """Define a config or options flow form step."""
42 | Callable[[SchemaCommonFlowHandler], Coroutine[Any, Any, vol.Schema |
None]]
45 """Optional voluptuous schema, or function which returns a schema or None, for
46 requesting and validating user input.
48 - If a function is specified, the function will be passed the current
49 `SchemaCommonFlowHandler`.
50 - If schema validation fails, the step will be retried. If the schema is None, no
51 user input is requested.
54 validate_user_input: (
56 [SchemaCommonFlowHandler, dict[str, Any]],
57 Coroutine[Any, Any, dict[str, Any]],
61 """Optional function to validate user input.
63 - The `validate_user_input` function is called if the schema validates successfully.
64 - The first argument is a reference to the current `SchemaCommonFlowHandler`.
65 - The second argument is the user input from the current step.
66 - The `validate_user_input` should raise `SchemaFlowError` if user input is invalid.
70 Callable[[dict[str, Any]], Coroutine[Any, Any, str |
None]] | str |
None
72 """Optional property to identify next step.
74 - If `next_step` is a function, it is called if the schema validates successfully or
75 if no schema is defined. The `next_step` function is passed the union of
76 config entry options and user input from previous steps. If the function returns
77 None, the flow is ended with `FlowResultType.CREATE_ENTRY`.
78 - If `next_step` is None, the flow is ended with `FlowResultType.CREATE_ENTRY`.
82 Callable[[SchemaCommonFlowHandler], Coroutine[Any, Any, dict[str, Any]]]
86 """Optional property to populate suggested values.
88 - If `suggested_values` is UNDEFINED, each key in the schema will get a suggested
89 value from an option with the same key.
91 Note: if a step is retried due to a validation failure, then the user input will
92 have priority over the suggested values.
95 preview: str |
None =
None
96 """Optional preview component."""
99 @dataclass(slots=True)
101 """Define a config or options flow menu step."""
104 options: Container[str]
108 """Handle a schema based config or options flow."""
112 handler: SchemaConfigFlowHandler | SchemaOptionsFlowHandler,
113 flow: Mapping[str, SchemaFlowStep],
114 options: dict[str, Any] |
None,
116 """Initialize a common handler."""
119 self.
_options_options = options
if options
is not None else {}
120 self._flow_state: dict[str, Any] = {}
124 """Return parent handler."""
129 """Return the options linked to the current flow handler."""
134 """Return the flow state, used to store temporary data.
136 It can be used for example to store the key or the index of a sub-item
137 that will be edited in the next step.
139 return self._flow_state
142 self, step_id: str, user_input: dict[str, Any] |
None =
None
143 ) -> ConfigFlowResult:
145 if isinstance(self.
_flow_flow[step_id], SchemaFlowFormStep):
149 async
def _get_schema(self, form_step: SchemaFlowFormStep) -> vol.Schema |
None:
150 if form_step.schema
is None:
152 if isinstance(form_step.schema, vol.Schema):
153 return form_step.schema
154 return await form_step.schema(self)
157 self, step_id: str, user_input: dict[str, Any] |
None =
None
158 ) -> ConfigFlowResult:
159 """Handle a form step."""
160 form_step: SchemaFlowFormStep = cast(SchemaFlowFormStep, self.
_flow_flow[step_id])
163 user_input
is not None
164 and (data_schema := await self.
_get_schema_get_schema(form_step))
165 and data_schema.schema
166 and not self.
_handler_handler.show_advanced_options
169 for key
in data_schema.schema:
170 if isinstance(key, (vol.Optional, vol.Required)):
173 and key.description.get(
"advanced")
174 and key.default
is not vol.UNDEFINED
175 and key
not in self.
_options_options
177 user_input[
str(key.schema)] = cast(
178 Callable[[], Any], key.default
181 if user_input
is not None and form_step.validate_user_input
is not None:
184 user_input = await form_step.validate_user_input(self, user_input)
185 except SchemaFlowError
as exc:
186 return await self.
_show_next_step_show_next_step(step_id, exc, user_input)
188 if user_input
is not None:
191 self.
_options_options, user_input, data_schema
194 if user_input
is not None or form_step.schema
is None:
201 values: dict[str, Any],
202 user_input: dict[str, Any],
203 data_schema: vol.Schema |
None,
205 values.update(user_input)
206 if data_schema
and data_schema.schema:
207 for key
in data_schema.schema:
209 isinstance(key, vol.Optional)
210 and key
not in user_input
214 and key.description.get(
"advanced")
215 and not self.
_handler_handler.show_advanced_options
219 values.pop(key.schema,
None)
222 self, form_step: SchemaFlowFormStep
223 ) -> ConfigFlowResult:
224 next_step_id_or_end_flow: str |
None
226 if callable(form_step.next_step):
227 next_step_id_or_end_flow = await form_step.next_step(self.
_options_options)
229 next_step_id_or_end_flow = form_step.next_step
231 if next_step_id_or_end_flow
is None:
234 return await self.
_show_next_step_show_next_step(next_step_id_or_end_flow)
239 error: SchemaFlowError |
None =
None,
240 user_input: dict[str, Any] |
None =
None,
241 ) -> ConfigFlowResult:
242 """Show form for next step."""
243 if isinstance(self.
_flow_flow[next_step_id], SchemaFlowMenuStep):
244 menu_step = cast(SchemaFlowMenuStep, self.
_flow_flow[next_step_id])
245 return self.
_handler_handler.async_show_menu(
246 step_id=next_step_id,
247 menu_options=menu_step.options,
250 form_step = cast(SchemaFlowFormStep, self.
_flow_flow[next_step_id])
252 if (data_schema := await self.
_get_schema_get_schema(form_step))
is None:
255 suggested_values: dict[str, Any] = {}
256 if form_step.suggested_values
is UNDEFINED:
257 suggested_values = self.
_options_options
258 elif form_step.suggested_values:
259 suggested_values = await form_step.suggested_values(self)
263 suggested_values = copy.deepcopy(suggested_values)
265 suggested_values, user_input, await self.
_get_schema_get_schema(form_step)
268 if data_schema.schema:
270 data_schema = self.
_handler_handler.add_suggested_values_to_schema(
271 data_schema, suggested_values
274 errors = {
"base":
str(error)}
if error
else None
278 if not callable(form_step.next_step):
279 last_step = form_step.next_step
is None
280 return self.
_handler_handler.async_show_form(
281 step_id=next_step_id,
282 data_schema=data_schema,
285 preview=form_step.preview,
289 self, step_id: str, user_input: dict[str, Any] |
None =
None
290 ) -> ConfigFlowResult:
291 """Handle a menu step."""
292 menu_step: SchemaFlowMenuStep = cast(SchemaFlowMenuStep, self.
_flow_flow[step_id])
293 return self.
_handler_handler.async_show_menu(
295 menu_options=menu_step.options,
300 """Handle a schema based config flow."""
302 config_flow: Mapping[str, SchemaFlowStep]
303 options_flow: Mapping[str, SchemaFlowStep] |
None =
None
308 """Initialize a subclass."""
312 def _async_get_options_flow(
313 config_entry: ConfigEntry,
315 """Get the options flow for this handler."""
316 if cls.options_flow
is None:
330 for step
in cls.config_flow:
331 setattr(cls, f
"async_step_{step}", cls.
_async_step_async_step(step))
334 """Initialize config flow."""
339 """Set up preview."""
344 """Return options flow support for this handler."""
345 return cls.options_flow
is not None
351 [SchemaConfigFlowHandler, dict[str, Any] |
None],
352 Coroutine[Any, Any, ConfigFlowResult],
354 """Generate a step handler."""
357 self: SchemaConfigFlowHandler, user_input: dict[str, Any] |
None =
None
358 ) -> ConfigFlowResult:
359 """Handle a config flow step."""
360 return await self.
_common_handler_common_handler.async_step(step_id, user_input)
367 """Return config entry title.
369 The options parameter contains config entry options, which is the union of user
370 input from the config flow steps.
375 """Take necessary actions after the config flow is finished, if needed.
377 The options parameter contains config entry options, which is the union of user
378 input from the config flow steps.
384 hass: HomeAssistant, options: Mapping[str, Any]
386 """Take necessary actions after the options flow is finished, if needed.
388 The options parameter contains config entry options, which is the union of
389 stored options and user input from the options flow steps.
395 data: Mapping[str, Any],
397 ) -> ConfigFlowResult:
398 """Finish config flow and create a config entry."""
406 """Handle a schema based options flow."""
410 config_entry: ConfigEntry,
411 options_flow: Mapping[str, SchemaFlowStep],
412 async_options_flow_finished: Callable[[HomeAssistant, Mapping[str, Any]],
None]
414 async_setup_preview: Callable[[HomeAssistant], Coroutine[Any, Any,
None]]
417 """Initialize options flow.
419 If needed, `async_options_flow_finished` can be set to take necessary actions
420 after the options flow is finished. The second parameter contains config entry
421 options, which is the union of stored options and user input from the options
428 for step
in options_flow:
431 f
"async_step_{step}",
432 types.MethodType(self.
_async_step_async_step(step), self),
435 if async_setup_preview:
436 setattr(self,
"async_setup_preview", async_setup_preview)
440 """Return a mutable copy of the config entry options."""
447 [SchemaConfigFlowHandler, dict[str, Any] |
None],
448 Coroutine[Any, Any, ConfigFlowResult],
450 """Generate a step handler."""
453 self: SchemaConfigFlowHandler, user_input: dict[str, Any] |
None =
None
454 ) -> ConfigFlowResult:
455 """Handle an options flow step."""
456 return await self.
_common_handler_common_handler.async_step(step_id, user_input)
463 data: Mapping[str, Any],
465 ) -> ConfigFlowResult:
466 """Finish config flow and create a config entry."""
474 hass: HomeAssistant, entity_id_or_uuid: str
476 """Generate title for a config entry wrapping a single entity.
478 If the entity is registered, use the registry entry's name.
479 If the entity is in the state machine, use the name from the state.
480 Otherwise, fall back to the object ID.
482 registry = er.async_get(hass)
483 entity_id = er.async_validate_entity_id(registry, entity_id_or_uuid)
485 entry = registry.async_get(entity_id)
487 return entry.name
or entry.original_name
or object_id
488 state = hass.states.get(entity_id)
490 return state.name
or object_id
496 handler: SchemaOptionsFlowHandler,
499 """Return an entity selector which excludes own entities."""
500 entity_registry = er.async_get(handler.hass)
501 entities = er.async_entries_for_config_entry(
503 handler.config_entry.entry_id,
505 entity_ids = [ent.entity_id
for ent
in entities]
507 final_selector_config = entity_selector_config.copy()
508 final_selector_config[
"exclude_entities"] = entity_ids
OptionsFlow async_get_options_flow(ConfigEntry config_entry)
None async_setup_preview(HomeAssistant hass)
ConfigFlowResult async_step(self, str step_id, dict[str, Any]|None user_input=None)
ConfigFlowResult _async_menu_step(self, str step_id, dict[str, Any]|None user_input=None)
dict[str, Any] flow_state(self)
None _update_and_remove_omitted_optional_keys(self, dict[str, Any] values, dict[str, Any] user_input, vol.Schema|None data_schema)
None __init__(self, SchemaConfigFlowHandler|SchemaOptionsFlowHandler handler, Mapping[str, SchemaFlowStep] flow, dict[str, Any]|None options)
ConfigFlowResult _show_next_step(self, str next_step_id, SchemaFlowError|None error=None, dict[str, Any]|None user_input=None)
ConfigFlowResult _async_form_step(self, str step_id, dict[str, Any]|None user_input=None)
dict[str, Any] options(self)
vol.Schema|None _get_schema(self, SchemaFlowFormStep form_step)
ConfigFlowResult _show_next_step_or_create_entry(self, SchemaFlowFormStep form_step)
SchemaConfigFlowHandler|SchemaOptionsFlowHandler parent_handler(self)
None async_config_flow_finished(self, Mapping[str, Any] options)
bool async_supports_options_flow(cls, ConfigEntry config_entry)
Callable[[SchemaConfigFlowHandler, dict[str, Any]|None], Coroutine[Any, Any, ConfigFlowResult],] _async_step(str step_id)
None async_setup_preview(HomeAssistant hass)
str async_config_entry_title(self, Mapping[str, Any] options)
None async_options_flow_finished(HomeAssistant hass, Mapping[str, Any] options)
None __init_subclass__(cls, **Any kwargs)
ConfigFlowResult async_create_entry(self, Mapping[str, Any] data, **Any kwargs)
None __init__(self, ConfigEntry config_entry, Mapping[str, SchemaFlowStep] options_flow, Callable[[HomeAssistant, Mapping[str, Any]], None]|None async_options_flow_finished=None, Callable[[HomeAssistant], Coroutine[Any, Any, None]]|None async_setup_preview=None)
ConfigFlowResult async_create_entry(self, Mapping[str, Any] data, **Any kwargs)
_async_options_flow_finished
Callable[[SchemaConfigFlowHandler, dict[str, Any]|None], Coroutine[Any, Any, ConfigFlowResult],] _async_step(str step_id)
dict[str, Any] options(self)
tuple[str, str] split_entity_id(str entity_id)
selector.EntitySelector entity_selector_without_own_entities(SchemaOptionsFlowHandler handler, selector.EntitySelectorConfig entity_selector_config)
str wrapped_entity_config_entry_title(HomeAssistant hass, str entity_id_or_uuid)