Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for Mastodon."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 from mastodon.Mastodon import MastodonNetworkError, MastodonUnauthorizedError
8 import voluptuous as vol
9 from yarl import URL
10 
11 from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
12 from homeassistant.const import (
13  CONF_ACCESS_TOKEN,
14  CONF_CLIENT_ID,
15  CONF_CLIENT_SECRET,
16  CONF_NAME,
17 )
19  TextSelector,
20  TextSelectorConfig,
21  TextSelectorType,
22 )
23 from homeassistant.util import slugify
24 
25 from .const import CONF_BASE_URL, DEFAULT_URL, DOMAIN, LOGGER
26 from .utils import construct_mastodon_username, create_mastodon_client
27 
28 STEP_USER_DATA_SCHEMA = vol.Schema(
29  {
30  vol.Required(
31  CONF_BASE_URL,
32  ): TextSelector(TextSelectorConfig(type=TextSelectorType.URL)),
33  vol.Required(
34  CONF_CLIENT_ID,
35  ): TextSelector(TextSelectorConfig(type=TextSelectorType.PASSWORD)),
36  vol.Required(
37  CONF_CLIENT_SECRET,
38  ): TextSelector(TextSelectorConfig(type=TextSelectorType.PASSWORD)),
39  vol.Required(
40  CONF_ACCESS_TOKEN,
41  ): TextSelector(TextSelectorConfig(type=TextSelectorType.PASSWORD)),
42  }
43 )
44 
45 
46 def base_url_from_url(url: str) -> str:
47  """Return the base url from a url."""
48  return str(URL(url).origin())
49 
50 
51 class MastodonConfigFlow(ConfigFlow, domain=DOMAIN):
52  """Handle a config flow."""
53 
54  VERSION = 1
55  MINOR_VERSION = 2
56  config_entry: ConfigEntry
57 
59  self,
60  base_url: str,
61  client_id: str,
62  client_secret: str,
63  access_token: str,
64  ) -> tuple[
65  dict[str, str] | None,
66  dict[str, str] | None,
67  dict[str, str],
68  ]:
69  """Check connection to the Mastodon instance."""
70  try:
71  client = create_mastodon_client(
72  base_url,
73  client_id,
74  client_secret,
75  access_token,
76  )
77  instance = client.instance()
78  account = client.account_verify_credentials()
79 
80  except MastodonNetworkError:
81  return None, None, {"base": "network_error"}
82  except MastodonUnauthorizedError:
83  return None, None, {"base": "unauthorized_error"}
84  except Exception: # noqa: BLE001
85  LOGGER.exception("Unexpected error")
86  return None, None, {"base": "unknown"}
87  return instance, account, {}
88 
90  self,
91  user_input: dict[str, Any] | None = None,
92  errors: dict[str, str] | None = None,
93  description_placeholders: dict[str, str] | None = None,
94  step_id: str = "user",
95  ) -> ConfigFlowResult:
96  """Show the user form."""
97  if user_input is None:
98  user_input = {}
99  return self.async_show_formasync_show_formasync_show_form(
100  step_id=step_id,
101  data_schema=self.add_suggested_values_to_schemaadd_suggested_values_to_schema(
102  STEP_USER_DATA_SCHEMA, user_input
103  ),
104  description_placeholders=description_placeholders,
105  errors=errors,
106  )
107 
108  async def async_step_user(
109  self, user_input: dict[str, Any] | None = None
110  ) -> ConfigFlowResult:
111  """Handle a flow initialized by the user."""
112  errors: dict[str, str] | None = None
113  if user_input:
114  user_input[CONF_BASE_URL] = base_url_from_url(user_input[CONF_BASE_URL])
115 
116  instance, account, errors = await self.hass.async_add_executor_job(
117  self.check_connectioncheck_connection,
118  user_input[CONF_BASE_URL],
119  user_input[CONF_CLIENT_ID],
120  user_input[CONF_CLIENT_SECRET],
121  user_input[CONF_ACCESS_TOKEN],
122  )
123 
124  if not errors:
125  name = construct_mastodon_username(instance, account)
126  await self.async_set_unique_idasync_set_unique_id(slugify(name))
127  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
128  return self.async_create_entryasync_create_entryasync_create_entry(
129  title=name,
130  data=user_input,
131  )
132 
133  return self.show_user_formshow_user_form(user_input, errors)
134 
135  async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
136  """Import a config entry from configuration.yaml."""
137  errors: dict[str, str] | None = None
138 
139  LOGGER.debug("Importing Mastodon from configuration.yaml")
140 
141  base_url = base_url_from_url(str(import_data.get(CONF_BASE_URL, DEFAULT_URL)))
142  client_id = str(import_data.get(CONF_CLIENT_ID))
143  client_secret = str(import_data.get(CONF_CLIENT_SECRET))
144  access_token = str(import_data.get(CONF_ACCESS_TOKEN))
145  name = import_data.get(CONF_NAME)
146 
147  instance, account, errors = await self.hass.async_add_executor_job(
148  self.check_connectioncheck_connection,
149  base_url,
150  client_id,
151  client_secret,
152  access_token,
153  )
154 
155  if not errors:
156  name = construct_mastodon_username(instance, account)
157  await self.async_set_unique_idasync_set_unique_id(slugify(name))
158  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
159 
160  if not name:
161  name = construct_mastodon_username(instance, account)
162 
163  return self.async_create_entryasync_create_entryasync_create_entry(
164  title=name,
165  data={
166  CONF_BASE_URL: base_url,
167  CONF_CLIENT_ID: client_id,
168  CONF_CLIENT_SECRET: client_secret,
169  CONF_ACCESS_TOKEN: access_token,
170  },
171  )
172 
173  reason = next(iter(errors.items()))[1]
174  return self.async_abortasync_abortasync_abort(reason=reason)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:110
ConfigFlowResult show_user_form(self, dict[str, Any]|None user_input=None, dict[str, str]|None errors=None, dict[str, str]|None description_placeholders=None, str step_id="user")
Definition: config_flow.py:95
tuple[ dict[str, str]|None, dict[str, str]|None, dict[str, str],] check_connection(self, str base_url, str client_id, str client_secret, str access_token)
Definition: config_flow.py:68
ConfigFlowResult async_step_import(self, dict[str, Any] import_data)
Definition: config_flow.py:135
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)
str
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_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)
Mastodon create_mastodon_client(str base_url, str client_id, str client_secret, str access_token)
Definition: utils.py:12
str construct_mastodon_username(dict[str, str]|None instance, dict[str, str]|None account)
Definition: utils.py:24