Skip to content

Commit

Permalink
Improve reauth, Config Flow and error handling (#452)
Browse files Browse the repository at this point in the history
  • Loading branch information
iMicknl authored Jul 14, 2021
1 parent 847ee29 commit 76d566c
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 91 deletions.
16 changes: 6 additions & 10 deletions custom_components/tahoma/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_EXCLUDE, CONF_PASSWORD, CONF_SOURCE, CONF_USERNAME
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import (
config_validation as cv,
device_registry as dr,
Expand Down Expand Up @@ -117,18 +117,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
client.get_places(),
]
devices, scenarios, gateways, places = await asyncio.gather(*tasks)
except BadCredentialsException:
_LOGGER.error("Invalid authentication.")
return False
except BadCredentialsException as exception:
raise ConfigEntryAuthFailed from exception
except TooManyRequestsException as exception:
_LOGGER.error("Too many requests, try again later.")
raise ConfigEntryNotReady from exception
raise ConfigEntryNotReady("Too many requests, try again later") from exception
except (TimeoutError, ClientError, ServerDisconnectedError) as exception:
_LOGGER.error("Failed to connect.")
raise ConfigEntryNotReady from exception
raise ConfigEntryNotReady("Failed to connect") from exception
except MaintenanceException as exception:
_LOGGER.error("Server is down for maintenance.")
raise ConfigEntryNotReady from exception
raise ConfigEntryNotReady("Server is down for maintenance") from exception
except Exception as exception: # pylint: disable=broad-except
_LOGGER.exception(exception)
return False
Expand Down
68 changes: 54 additions & 14 deletions custom_components/tahoma/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.helpers import config_validation as cv
from pyhoma.client import TahomaClient
from pyhoma.exceptions import (
Expand All @@ -26,14 +27,6 @@

_LOGGER = logging.getLogger(__name__)

DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Required(CONF_HUB, default=DEFAULT_HUB): vol.In(SUPPORTED_ENDPOINTS.keys()),
}
)


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Somfy TaHoma."""
Expand All @@ -47,6 +40,12 @@ def async_get_options_flow(config_entry):
"""Handle the flow."""
return OptionsFlowHandler(config_entry)

def __init__(self):
"""Start the Overkiz config flow."""
self._reauth_entry = None
self._default_username = None
self._default_hub = DEFAULT_HUB

async def async_validate_input(self, user_input):
"""Validate user credentials."""
username = user_input.get(CONF_USERNAME)
Expand All @@ -57,18 +56,37 @@ async def async_validate_input(self, user_input):

async with TahomaClient(username, password, api_url=endpoint) as client:
await client.login()
return self.async_create_entry(
title=username,
data=user_input,

# Set first gateway as unique id
gateways = await client.get_gateways()
if gateways:
gateway_id = gateways[0].id
await self.async_set_unique_id(gateway_id)

# Create new config entry
if (
self._reauth_entry is None
or self._reauth_entry.unique_id != self.unique_id
):
self._abort_if_unique_id_configured()
return self.async_create_entry(title=username, data=user_input)

# Modify existing entry in reauth scenario
self.hass.config_entries.async_update_entry(
self._reauth_entry, data=user_input
)

await self.hass.config_entries.async_reload(self._reauth_entry.entry_id)

return self.async_abort(reason="reauth_successful")

async def async_step_user(self, user_input=None):
"""Handle the initial step via config flow."""
errors = {}

if user_input:
await self.async_set_unique_id(user_input.get(CONF_USERNAME))
self._abort_if_unique_id_configured()
self._default_username = user_input[CONF_USERNAME]
self._default_hub = user_input[CONF_HUB]

try:
return await self.async_validate_input(user_input)
Expand All @@ -80,14 +98,36 @@ async def async_step_user(self, user_input=None):
errors["base"] = "cannot_connect"
except MaintenanceException:
errors["base"] = "server_in_maintenance"
except AbortFlow:
raise
except Exception as exception: # pylint: disable=broad-except
errors["base"] = "unknown"
_LOGGER.exception(exception)

return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_USERNAME, default=self._default_username): str,
vol.Required(CONF_PASSWORD): str,
vol.Required(CONF_HUB, default=self._default_hub): vol.In(
SUPPORTED_ENDPOINTS.keys()
),
}
),
errors=errors,
)

async def async_step_reauth(self, user_input=None):
"""Perform reauth if the user credentials have changed."""
self._reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
self._default_username = user_input[CONF_USERNAME]
self._default_hub = user_input[CONF_HUB]

return await self.async_step_user()

async def async_step_import(self, import_config: dict):
"""Handle the initial step via YAML configuration."""
if not import_config:
Expand Down
3 changes: 2 additions & 1 deletion custom_components/tahoma/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from aiohttp import ServerDisconnectedError
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import device_registry
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from pyhoma.client import TahomaClient
Expand Down Expand Up @@ -66,7 +67,7 @@ async def _async_update_data(self) -> Dict[str, Device]:
try:
events = await self.client.fetch_events()
except BadCredentialsException as exception:
raise UpdateFailed("Invalid authentication.") from exception
raise ConfigEntryAuthFailed() from exception
except TooManyRequestsException as exception:
raise UpdateFailed("Too many requests, try again later.") from exception
except MaintenanceException as exception:
Expand Down
2 changes: 1 addition & 1 deletion custom_components/tahoma/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
}
},
"options": {
Expand Down
10 changes: 5 additions & 5 deletions custom_components/tahoma/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication",
"server_in_maintenance": "Server is down for maintenance.",
"too_many_requests": "Too many requests, try again later.",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"server_in_maintenance": "Server is down for maintenance",
"unknown": "[%key:common::config_flow::error::unknown%]"
"unknown": "Unexpected error"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "Account is already configured"
}
},
"options": {
Expand Down
4 changes: 2 additions & 2 deletions custom_components/tahoma/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
},
"error": {
"cannot_connect": "Connexion impossible",
"too_many_requests": "Trop de requêtes, veuillez réessayer plus tard.",
"too_many_requests": "Trop de requêtes, veuillez réessayer plus tard",
"invalid_auth": "Mot de passe ou nom d'utilisateur incorrect",
"server_in_maintenance": "Le serveur est en cours de maintenance",
"unknown": "Une erreur inconnue est survenue."
"unknown": "Une erreur inconnue est survenue"
},
"abort": {
"already_configured": "Votre compte a déjà été ajouté pour cette intégration."
Expand Down
10 changes: 5 additions & 5 deletions custom_components/tahoma/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
}
},
"error": {
"too_many_requests": "Te veel verzoeken, probeer het later opnieuw.",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"too_many_requests": "Te veel verzoeken, probeer het later opnieuw",
"cannot_connect": "Kan geen verbinding maken",
"invalid_auth": "Ongeldige authenticatie",
"server_in_maintenance": "De server is offline voor onderhoud",
"unknown": "[%key:common::config_flow::error::unknown%]"
"unknown": "Onverwachte fout"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "Account is al geconfigureerd"
}
},
"options": {
Expand Down
2 changes: 1 addition & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
-r requirements.txt
homeassistant==2021.4.0b4
homeassistant==2021.7.0b0
2 changes: 1 addition & 1 deletion requirements_test.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
-r requirements_dev.txt
pytest-homeassistant-custom-component==0.3.0
pytest-homeassistant-custom-component==0.4.2
7 changes: 6 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,9 @@ def skip_notifications_fixture():
with patch("homeassistant.components.persistent_notification.async_create"), patch(
"homeassistant.components.persistent_notification.async_dismiss"
):
yield
yield


@pytest.fixture(autouse=True)
def auto_enable_custom_integrations(enable_custom_integrations):
yield
Loading

0 comments on commit 76d566c

Please sign in to comment.