Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for habitica integration."""
2 
3 from __future__ import annotations
4 
5 from http import HTTPStatus
6 import logging
7 from typing import Any
8 
9 from aiohttp import ClientResponseError
10 from habitipy.aio import HabitipyAsync
11 import voluptuous as vol
12 
13 from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
14 from homeassistant.const import (
15  CONF_API_KEY,
16  CONF_PASSWORD,
17  CONF_URL,
18  CONF_USERNAME,
19  CONF_VERIFY_SSL,
20 )
21 from homeassistant.helpers.aiohttp_client import async_get_clientsession
23  TextSelector,
24  TextSelectorConfig,
25  TextSelectorType,
26 )
27 
28 from .const import (
29  CONF_API_USER,
30  DEFAULT_URL,
31  DOMAIN,
32  FORGOT_PASSWORD_URL,
33  HABITICANS_URL,
34  SIGN_UP_URL,
35  SITE_DATA_URL,
36 )
37 
38 STEP_ADVANCED_DATA_SCHEMA = vol.Schema(
39  {
40  vol.Required(CONF_API_USER): str,
41  vol.Required(CONF_API_KEY): str,
42  vol.Optional(CONF_URL, default=DEFAULT_URL): str,
43  vol.Required(CONF_VERIFY_SSL, default=True): bool,
44  }
45 )
46 
47 STEP_LOGIN_DATA_SCHEMA = vol.Schema(
48  {
49  vol.Required(CONF_USERNAME): TextSelector(
51  type=TextSelectorType.EMAIL,
52  autocomplete="email",
53  )
54  ),
55  vol.Required(CONF_PASSWORD): TextSelector(
57  type=TextSelectorType.PASSWORD,
58  autocomplete="current-password",
59  )
60  ),
61  }
62 )
63 
64 _LOGGER = logging.getLogger(__name__)
65 
66 
67 class HabiticaConfigFlow(ConfigFlow, domain=DOMAIN):
68  """Handle a config flow for habitica."""
69 
70  VERSION = 1
71 
72  async def async_step_user(
73  self, user_input: dict[str, Any] | None = None
74  ) -> ConfigFlowResult:
75  """Handle the initial step."""
76 
77  return self.async_show_menuasync_show_menu(
78  step_id="user",
79  menu_options=["login", "advanced"],
80  description_placeholders={
81  "signup": SIGN_UP_URL,
82  "habiticans": HABITICANS_URL,
83  },
84  )
85 
86  async def async_step_login(
87  self, user_input: dict[str, Any] | None = None
88  ) -> ConfigFlowResult:
89  """Config flow with username/password.
90 
91  Simplified configuration setup that retrieves API credentials
92  from Habitica.com by authenticating with login and password.
93  """
94  errors: dict[str, str] = {}
95  if user_input is not None:
96  try:
97  session = async_get_clientsession(self.hass)
98  api = await self.hass.async_add_executor_job(
99  HabitipyAsync,
100  {
101  "login": "",
102  "password": "",
103  "url": DEFAULT_URL,
104  },
105  )
106  login_response = await api.user.auth.local.login.post(
107  session=session,
108  username=user_input[CONF_USERNAME],
109  password=user_input[CONF_PASSWORD],
110  )
111 
112  except ClientResponseError as ex:
113  if ex.status == HTTPStatus.UNAUTHORIZED:
114  errors["base"] = "invalid_auth"
115  else:
116  errors["base"] = "cannot_connect"
117  except Exception:
118  _LOGGER.exception("Unexpected exception")
119  errors["base"] = "unknown"
120  else:
121  await self.async_set_unique_idasync_set_unique_id(login_response["id"])
122  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
123  return self.async_create_entryasync_create_entryasync_create_entry(
124  title=login_response["username"],
125  data={
126  CONF_API_USER: login_response["id"],
127  CONF_API_KEY: login_response["apiToken"],
128  CONF_USERNAME: login_response["username"],
129  CONF_URL: DEFAULT_URL,
130  CONF_VERIFY_SSL: True,
131  },
132  )
133 
134  return self.async_show_formasync_show_formasync_show_form(
135  step_id="login",
136  data_schema=self.add_suggested_values_to_schemaadd_suggested_values_to_schema(
137  data_schema=STEP_LOGIN_DATA_SCHEMA, suggested_values=user_input
138  ),
139  errors=errors,
140  description_placeholders={"forgot_password": FORGOT_PASSWORD_URL},
141  )
142 
144  self, user_input: dict[str, Any] | None = None
145  ) -> ConfigFlowResult:
146  """Advanced configuration with User Id and API Token.
147 
148  Advanced configuration allows connecting to Habitica instances
149  hosted on different domains or to self-hosted instances.
150  """
151  errors: dict[str, str] = {}
152  if user_input is not None:
153  try:
154  session = async_get_clientsession(
155  self.hass, verify_ssl=user_input.get(CONF_VERIFY_SSL, True)
156  )
157  api = await self.hass.async_add_executor_job(
158  HabitipyAsync,
159  {
160  "login": user_input[CONF_API_USER],
161  "password": user_input[CONF_API_KEY],
162  "url": user_input.get(CONF_URL, DEFAULT_URL),
163  },
164  )
165  api_response = await api.user.get(
166  session=session,
167  userFields="auth",
168  )
169  except ClientResponseError as ex:
170  if ex.status == HTTPStatus.UNAUTHORIZED:
171  errors["base"] = "invalid_auth"
172  else:
173  errors["base"] = "cannot_connect"
174  except Exception:
175  _LOGGER.exception("Unexpected exception")
176  errors["base"] = "unknown"
177  else:
178  await self.async_set_unique_idasync_set_unique_id(user_input[CONF_API_USER])
179  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
180  user_input[CONF_USERNAME] = api_response["auth"]["local"]["username"]
181  return self.async_create_entryasync_create_entryasync_create_entry(
182  title=user_input[CONF_USERNAME], data=user_input
183  )
184 
185  return self.async_show_formasync_show_formasync_show_form(
186  step_id="advanced",
187  data_schema=self.add_suggested_values_to_schemaadd_suggested_values_to_schema(
188  data_schema=STEP_ADVANCED_DATA_SCHEMA, suggested_values=user_input
189  ),
190  errors=errors,
191  description_placeholders={
192  "site_data": SITE_DATA_URL,
193  "default_url": DEFAULT_URL,
194  },
195  )
ConfigFlowResult async_step_advanced(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:145
ConfigFlowResult async_step_login(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:88
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:74
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_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)
vol.Schema add_suggested_values_to_schema(self, vol.Schema data_schema, Mapping[str, Any]|None suggested_values)
_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_show_menu(self, *str|None step_id=None, Container[str] menu_options, Mapping[str, str]|None description_placeholders=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)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)