Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for flume integration."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 import logging
7 import os
8 from typing import Any
9 
10 from pyflume import FlumeAuth, FlumeDeviceList
11 from requests.exceptions import RequestException
12 import voluptuous as vol
13 
14 from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
15 from homeassistant.const import (
16  CONF_CLIENT_ID,
17  CONF_CLIENT_SECRET,
18  CONF_PASSWORD,
19  CONF_USERNAME,
20 )
21 from homeassistant.core import HomeAssistant
22 from homeassistant.exceptions import HomeAssistantError
23 
24 from .const import BASE_TOKEN_FILENAME, DOMAIN
25 
26 _LOGGER = logging.getLogger(__name__)
27 
28 # If flume ever implements a login page for oauth
29 # we can use the oauth2 support built into Home Assistant.
30 #
31 # Currently they only implement the token endpoint
32 #
33 DATA_SCHEMA = vol.Schema(
34  {
35  vol.Required(CONF_USERNAME): str,
36  vol.Required(CONF_PASSWORD): str,
37  vol.Required(CONF_CLIENT_ID): str,
38  vol.Required(CONF_CLIENT_SECRET): str,
39  }
40 )
41 
42 
44  hass: HomeAssistant, data: dict[str, Any], clear_token_file: bool
45 ) -> FlumeDeviceList:
46  """Validate in the executor."""
47  flume_token_full_path = hass.config.path(
48  f"{BASE_TOKEN_FILENAME}-{data[CONF_USERNAME]}"
49  )
50  if clear_token_file and os.path.exists(flume_token_full_path):
51  os.unlink(flume_token_full_path)
52 
53  return FlumeDeviceList(
54  FlumeAuth(
55  data[CONF_USERNAME],
56  data[CONF_PASSWORD],
57  data[CONF_CLIENT_ID],
58  data[CONF_CLIENT_SECRET],
59  flume_token_file=flume_token_full_path,
60  )
61  )
62 
63 
64 async def validate_input(
65  hass: HomeAssistant, data: dict[str, Any], clear_token_file: bool = False
66 ) -> dict[str, Any]:
67  """Validate the user input allows us to connect.
68 
69  Data has the keys from DATA_SCHEMA with values provided by the user.
70  """
71  try:
72  flume_devices = await hass.async_add_executor_job(
73  _validate_input, hass, data, clear_token_file
74  )
75  except RequestException as err:
76  raise CannotConnect from err
77  except Exception as err:
78  _LOGGER.exception("Auth exception")
79  raise InvalidAuth from err
80  if not flume_devices or not flume_devices.device_list:
81  raise CannotConnect
82 
83  # Return info that you want to store in the config entry.
84  return {"title": data[CONF_USERNAME]}
85 
86 
87 class FlumeConfigFlow(ConfigFlow, domain=DOMAIN):
88  """Handle a config flow for flume."""
89 
90  VERSION = 1
91 
92  def __init__(self) -> None:
93  """Init flume config flow."""
94  self._reauth_unique_id_reauth_unique_id: str | None = None
95 
96  async def async_step_user(
97  self, user_input: dict[str, Any] | None = None
98  ) -> ConfigFlowResult:
99  """Handle the initial step."""
100  errors: dict[str, str] = {}
101  if user_input is not None:
102  await self.async_set_unique_idasync_set_unique_id(user_input[CONF_USERNAME])
103  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
104 
105  try:
106  info = await validate_input(self.hass, user_input)
107  return self.async_create_entryasync_create_entryasync_create_entry(title=info["title"], data=user_input)
108  except CannotConnect:
109  errors["base"] = "cannot_connect"
110  except InvalidAuth:
111  errors[CONF_PASSWORD] = "invalid_auth"
112 
113  return self.async_show_formasync_show_formasync_show_form(
114  step_id="user", data_schema=DATA_SCHEMA, errors=errors
115  )
116 
117  async def async_step_reauth(
118  self, entry_data: Mapping[str, Any]
119  ) -> ConfigFlowResult:
120  """Handle reauth."""
121  self._reauth_unique_id_reauth_unique_id = self.context["unique_id"]
122  return await self.async_step_reauth_confirmasync_step_reauth_confirm()
123 
125  self, user_input: dict[str, Any] | None = None
126  ) -> ConfigFlowResult:
127  """Handle reauth input."""
128  errors: dict[str, str] = {}
129  existing_entry = await self.async_set_unique_idasync_set_unique_id(self._reauth_unique_id_reauth_unique_id)
130  assert existing_entry
131  if user_input is not None:
132  new_data = {**existing_entry.data, CONF_PASSWORD: user_input[CONF_PASSWORD]}
133  try:
134  await validate_input(self.hass, new_data, clear_token_file=True)
135  except CannotConnect:
136  errors["base"] = "cannot_connect"
137  except InvalidAuth:
138  errors[CONF_PASSWORD] = "invalid_auth"
139  else:
140  self.hass.config_entries.async_update_entry(
141  existing_entry, data=new_data
142  )
143  await self.hass.config_entries.async_reload(existing_entry.entry_id)
144  return self.async_abortasync_abortasync_abort(reason="reauth_successful")
145 
146  return self.async_show_formasync_show_formasync_show_form(
147  description_placeholders={
148  CONF_USERNAME: existing_entry.data[CONF_USERNAME]
149  },
150  step_id="reauth_confirm",
151  data_schema=vol.Schema(
152  {
153  vol.Required(CONF_PASSWORD): str,
154  }
155  ),
156  errors=errors,
157  )
158 
159 
161  """Error to indicate we cannot connect."""
162 
163 
164 class InvalidAuth(HomeAssistantError):
165  """Error to indicate there is invalid auth."""
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
Definition: config_flow.py:119
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:98
ConfigFlowResult async_step_reauth_confirm(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:126
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
ConfigEntry|None async_set_unique_id(self, str|None unique_id=None, *bool raise_on_progress=True)
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
ConfigFlowResult async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
_FlowResultT async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
dict[str, Any] validate_input(HomeAssistant hass, dict[str, Any] data, bool clear_token_file=False)
Definition: config_flow.py:66
FlumeDeviceList _validate_input(HomeAssistant hass, dict[str, Any] data, bool clear_token_file)
Definition: config_flow.py:45