Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for Random helper."""
2 
3 from collections.abc import Callable, Coroutine, Mapping
4 from enum import StrEnum
5 from typing import Any, cast
6 
7 import voluptuous as vol
8 
9 from homeassistant.components.binary_sensor import BinarySensorDeviceClass
10 from homeassistant.components.sensor import DEVICE_CLASS_UNITS, SensorDeviceClass
11 from homeassistant.const import (
12  CONF_DEVICE_CLASS,
13  CONF_MAXIMUM,
14  CONF_MINIMUM,
15  CONF_NAME,
16  CONF_UNIT_OF_MEASUREMENT,
17  Platform,
18 )
19 from homeassistant.core import callback
22  SchemaCommonFlowHandler,
23  SchemaConfigFlowHandler,
24  SchemaFlowFormStep,
25  SchemaFlowMenuStep,
26 )
28  SelectSelector,
29  SelectSelectorConfig,
30  SelectSelectorMode,
31  TextSelector,
32 )
33 
34 from .const import DOMAIN
35 from .sensor import DEFAULT_MAX, DEFAULT_MIN
36 
37 
38 class _FlowType(StrEnum):
39  CONFIG = "config"
40  OPTION = "option"
41 
42 
43 def _generate_schema(domain: str, flow_type: _FlowType) -> vol.Schema:
44  """Generate schema."""
45  schema: dict[vol.Marker, Any] = {}
46 
47  if flow_type == _FlowType.CONFIG:
48  schema[vol.Required(CONF_NAME)] = TextSelector()
49 
50  if domain == Platform.BINARY_SENSOR:
51  schema[vol.Optional(CONF_DEVICE_CLASS)] = SelectSelector(
53  options=[cls.value for cls in BinarySensorDeviceClass],
54  sort=True,
55  mode=SelectSelectorMode.DROPDOWN,
56  translation_key="binary_sensor_device_class",
57  ),
58  )
59 
60  if domain == Platform.SENSOR:
61  schema.update(
62  {
63  vol.Optional(CONF_MINIMUM, default=DEFAULT_MIN): cv.positive_int,
64  vol.Optional(CONF_MAXIMUM, default=DEFAULT_MAX): cv.positive_int,
65  vol.Optional(CONF_DEVICE_CLASS): SelectSelector(
67  options=[
68  cls.value
69  for cls in SensorDeviceClass
70  if cls != SensorDeviceClass.ENUM
71  ],
72  sort=True,
73  mode=SelectSelectorMode.DROPDOWN,
74  translation_key="sensor_device_class",
75  ),
76  ),
77  vol.Optional(CONF_UNIT_OF_MEASUREMENT): SelectSelector(
79  options=[
80  str(unit)
81  for units in DEVICE_CLASS_UNITS.values()
82  for unit in units
83  if unit is not None
84  ],
85  sort=True,
86  mode=SelectSelectorMode.DROPDOWN,
87  translation_key="sensor_unit_of_measurement",
88  custom_value=True,
89  ),
90  ),
91  }
92  )
93 
94  return vol.Schema(schema)
95 
96 
97 async def choose_options_step(options: dict[str, Any]) -> str:
98  """Return next step_id for options flow according to entity_type."""
99  return cast(str, options["entity_type"])
100 
101 
102 def _validate_unit(options: dict[str, Any]) -> None:
103  """Validate unit of measurement."""
104  if (
105  (device_class := options.get(CONF_DEVICE_CLASS))
106  and (units := DEVICE_CLASS_UNITS.get(device_class))
107  and (unit := options.get(CONF_UNIT_OF_MEASUREMENT)) not in units
108  ):
109  sorted_units = sorted(
110  [f"'{unit!s}'" if unit else "no unit of measurement" for unit in units],
111  key=str.casefold,
112  )
113  if len(sorted_units) == 1:
114  units_string = sorted_units[0]
115  else:
116  units_string = f"one of {', '.join(sorted_units)}"
117 
118  raise vol.Invalid(
119  f"'{unit}' is not a valid unit for device class '{device_class}'; "
120  f"expected {units_string}"
121  )
122 
123 
125  entity_type: str,
126 ) -> Callable[
127  [SchemaCommonFlowHandler, dict[str, Any]],
128  Coroutine[Any, Any, dict[str, Any]],
129 ]:
130  """Do post validation of user input.
131 
132  For sensors: Validate unit of measurement.
133  """
134 
135  async def _validate_user_input(
136  _: SchemaCommonFlowHandler,
137  user_input: dict[str, Any],
138  ) -> dict[str, Any]:
139  """Add entity type to user input."""
140  if entity_type == Platform.SENSOR:
141  _validate_unit(user_input)
142  return {"entity_type": entity_type} | user_input
143 
144  return _validate_user_input
145 
146 
147 RANDOM_TYPES = [
148  Platform.BINARY_SENSOR.value,
149  Platform.SENSOR.value,
150 ]
151 
152 CONFIG_FLOW = {
153  "user": SchemaFlowMenuStep(RANDOM_TYPES),
154  Platform.BINARY_SENSOR: SchemaFlowFormStep(
155  _generate_schema(Platform.BINARY_SENSOR, _FlowType.CONFIG),
156  validate_user_input=validate_user_input(Platform.BINARY_SENSOR),
157  ),
158  Platform.SENSOR: SchemaFlowFormStep(
159  _generate_schema(Platform.SENSOR, _FlowType.CONFIG),
160  validate_user_input=validate_user_input(Platform.SENSOR),
161  ),
162 }
163 
164 
165 OPTIONS_FLOW = {
166  "init": SchemaFlowFormStep(next_step=choose_options_step),
167  Platform.BINARY_SENSOR: SchemaFlowFormStep(
168  _generate_schema(Platform.BINARY_SENSOR, _FlowType.OPTION),
169  validate_user_input=validate_user_input(Platform.BINARY_SENSOR),
170  ),
171  Platform.SENSOR: SchemaFlowFormStep(
172  _generate_schema(Platform.SENSOR, _FlowType.OPTION),
173  validate_user_input=validate_user_input(Platform.SENSOR),
174  ),
175 }
176 
177 
179  """Handle config flow for random helper."""
180 
181  config_flow = CONFIG_FLOW
182  options_flow = OPTIONS_FLOW
183 
184  @callback
185  def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
186  """Return config entry title."""
187  return cast(str, options["name"])
Callable[[SchemaCommonFlowHandler, dict[str, Any]], Coroutine[Any, Any, dict[str, Any]],] validate_user_input(str entity_type)
Definition: config_flow.py:129
None _validate_unit(dict[str, Any] options)
Definition: config_flow.py:102
str choose_options_step(dict[str, Any] options)
Definition: config_flow.py:97
vol.Schema _generate_schema(str domain, _FlowType flow_type)
Definition: config_flow.py:43