Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Adds config flow for WeatherKit."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 from typing import Any
7 
8 from apple_weatherkit.client import (
9  WeatherKitApiClient,
10  WeatherKitApiClientAuthenticationError,
11  WeatherKitApiClientCommunicationError,
12  WeatherKitApiClientError,
13 )
14 import voluptuous as vol
15 
16 from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
17 from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE
18 from homeassistant.helpers.aiohttp_client import async_get_clientsession
20  LocationSelector,
21  LocationSelectorConfig,
22  TextSelector,
23  TextSelectorConfig,
24 )
25 
26 from .const import (
27  CONF_KEY_ID,
28  CONF_KEY_PEM,
29  CONF_SERVICE_ID,
30  CONF_TEAM_ID,
31  DOMAIN,
32  LOGGER,
33 )
34 
35 DATA_SCHEMA = vol.Schema(
36  {
37  vol.Required(CONF_LOCATION): LocationSelector(
38  LocationSelectorConfig(radius=False, icon="")
39  ),
40  # Auth
41  vol.Required(CONF_KEY_ID): str,
42  vol.Required(CONF_SERVICE_ID): str,
43  vol.Required(CONF_TEAM_ID): str,
44  vol.Required(CONF_KEY_PEM): TextSelector(
46  multiline=True,
47  )
48  ),
49  }
50 )
51 
52 
54  """Error to indicate a location is unsupported."""
55 
56 
57 class WeatherKitFlowHandler(ConfigFlow, domain=DOMAIN):
58  """Config flow for WeatherKit."""
59 
60  VERSION = 1
61 
62  async def async_step_user(
63  self,
64  user_input: dict[str, Any] | None = None,
65  ) -> ConfigFlowResult:
66  """Handle a flow initialized by the user."""
67  errors = {}
68  if user_input is not None:
69  try:
70  user_input[CONF_KEY_PEM] = self._fix_key_input_fix_key_input(user_input[CONF_KEY_PEM])
71  await self._test_config_test_config(user_input)
72  except WeatherKitUnsupportedLocationError as exception:
73  LOGGER.error(exception)
74  errors["base"] = "unsupported_location"
75  except WeatherKitApiClientAuthenticationError as exception:
76  LOGGER.warning(exception)
77  errors["base"] = "invalid_auth"
78  except WeatherKitApiClientCommunicationError as exception:
79  LOGGER.error(exception)
80  errors["base"] = "cannot_connect"
81  except WeatherKitApiClientError as exception:
82  LOGGER.exception(exception)
83  errors["base"] = "unknown"
84  else:
85  # Flatten location
86  location = user_input.pop(CONF_LOCATION)
87  user_input[CONF_LATITUDE] = location[CONF_LATITUDE]
88  user_input[CONF_LONGITUDE] = location[CONF_LONGITUDE]
89 
90  return self.async_create_entryasync_create_entryasync_create_entry(
91  title=f"{user_input[CONF_LATITUDE]}, {user_input[CONF_LONGITUDE]}",
92  data=user_input,
93  )
94 
95  suggested_values: Mapping[str, Any] = {
96  CONF_LOCATION: {
97  CONF_LATITUDE: self.hass.config.latitude,
98  CONF_LONGITUDE: self.hass.config.longitude,
99  }
100  }
101 
102  data_schema = self.add_suggested_values_to_schemaadd_suggested_values_to_schema(DATA_SCHEMA, suggested_values)
103  return self.async_show_formasync_show_formasync_show_form(
104  step_id="user",
105  data_schema=data_schema,
106  errors=errors,
107  )
108 
109  def _fix_key_input(self, key_input: str) -> str:
110  """Fix common user errors with the key input."""
111  # OSes may sometimes turn two hyphens (--) into an em dash (—)
112  key_input = key_input.replace("—", "--")
113 
114  # Trim whitespace and line breaks
115  key_input = key_input.strip()
116 
117  # Make sure header and footer are present
118  header = "-----BEGIN PRIVATE KEY-----"
119  if not key_input.startswith(header):
120  key_input = f"{header}\n{key_input}"
121 
122  footer = "-----END PRIVATE KEY-----"
123  if not key_input.endswith(footer):
124  key_input += f"\n{footer}"
125 
126  return key_input
127 
128  async def _test_config(self, user_input: dict[str, Any]) -> None:
129  """Validate credentials."""
130  client = WeatherKitApiClient(
131  key_id=user_input[CONF_KEY_ID],
132  service_id=user_input[CONF_SERVICE_ID],
133  team_id=user_input[CONF_TEAM_ID],
134  key_pem=user_input[CONF_KEY_PEM],
135  session=async_get_clientsession(self.hass),
136  )
137 
138  location = user_input[CONF_LOCATION]
139  availability = await client.get_availability(
140  location[CONF_LATITUDE],
141  location[CONF_LONGITUDE],
142  )
143 
144  if not availability:
146  "API does not support this location"
147  )
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:65
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_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)