Skip to content

Commit

Permalink
Merge pull request #271 from jschlyter/config_flow_refactor
Browse files Browse the repository at this point in the history
Restructure config flow
  • Loading branch information
jschlyter authored Dec 16, 2024
2 parents c6278ec + 72fbbb0 commit a86f906
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 73 deletions.
142 changes: 71 additions & 71 deletions custom_components/polestar_api/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,98 +1,98 @@
"""Config flow for the Polestar EV platform."""

import asyncio
import logging

import voluptuous as vol
from aiohttp import ClientError
from homeassistant import config_entries
from homeassistant.config_entries import ConfigFlowResult
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers.httpx_client import get_async_client

from .const import CONF_VIN, DOMAIN
from .polestar import PolestarCoordinator
from .pypolestar.exception import PolestarAuthException
from .pypolestar.exception import PolestarApiException, PolestarAuthException
from .pypolestar.polestar import PolestarApi

_LOGGER = logging.getLogger(__name__)


class NoCarsFoundException(Exception):
pass


class VinNotFoundException(Exception):
pass


@config_entries.HANDLERS.register(DOMAIN)
class FlowHandler(config_entries.ConfigFlow):
"""Handle a config flow."""

VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL

async def _create_entry(
self, username: str, password: str, vin: str | None
) -> ConfigFlowResult:
"""Register new entry."""
return self.async_create_entry(
title=f"Polestar EV for {username}",
data={CONF_USERNAME: username, CONF_PASSWORD: password, CONF_VIN: vin},
)

async def _create_device(
self, username: str, password: str, vin: str | None
) -> ConfigFlowResult:
"""Create device."""

try:
device = PolestarCoordinator(
hass=self.hass,
username=username,
password=password,
vin=vin,
)
await device.async_init()

# check that we found cars
if not len(device.get_cars()):
return self.async_abort(reason="No cars found")

# check if we have a token, otherwise throw exception
if device.polestar_api.auth.access_token is None:
_LOGGER.exception(
"No token, Could be wrong credentials (invalid email or password))"
async def async_step_user(self, user_input: dict | None = None) -> ConfigFlowResult:
"""User initiated config flow."""
_errors = {}

if user_input is not None:
username = user_input[CONF_USERNAME]
password = user_input[CONF_PASSWORD]
vin = user_input.get(CONF_VIN)

try:
await self._test_credentials(username, password, vin)
except NoCarsFoundException as exc:
_LOGGER.error(exc)
_errors["base"] = "no_cars_found"
except VinNotFoundException as exc:
_LOGGER.error(exc)
_errors["base"] = "vin_not_found"
except PolestarAuthException as exc:
_LOGGER.warning(exc)
_errors["base"] = "auth_failed"
except PolestarApiException as exc:
_LOGGER.error(exc)
_errors["base"] = "api"
else:
return self.async_create_entry(
title=f"Polestar EV for {username}",
data={
CONF_USERNAME: username,
CONF_PASSWORD: password,
CONF_VIN: vin,
},
)
return self.async_abort(reason="No API token")

except asyncio.TimeoutError:
return self.async_abort(reason="API timeout")
except ClientError:
_LOGGER.exception("ClientError")
return self.async_abort(reason="API client failure")
except PolestarAuthException:
return self.async_abort(reason="Login failed")
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected error creating device")
return self.async_abort(reason="API unexpected failure")
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Optional(CONF_VIN): str,
}
),
errors=_errors,
)

return await self._create_entry(username, password, vin)
async def _test_credentials(
self, username: str, password: str, vin: str | None
) -> None:
"""Validate credentials and return VINs of found cars."""

async def async_step_user(self, user_input: dict | None = None) -> ConfigFlowResult:
"""User initiated config flow."""
if user_input is None:
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Optional(CONF_VIN): str,
}
),
)
return await self._create_device(
username=user_input[CONF_USERNAME],
password=user_input[CONF_PASSWORD],
vin=user_input.get(CONF_VIN),
api_client = PolestarApi(
username=username,
password=password,
client_session=get_async_client(self.hass),
)
await api_client.async_init()

async def async_step_import(self, user_input: dict) -> ConfigFlowResult:
"""Import a config entry."""
return await self._create_device(
username=user_input[CONF_USERNAME],
password=user_input[CONF_PASSWORD],
vin=user_input.get(CONF_VIN),
)
if found_vins := api_client.vins:
_LOGGER.debug("Found %d VINs for %s", len(found_vins), username)
else:
_LOGGER.warning("No VINs found for %s", username)
raise NoCarsFoundException

if vin and vin not in found_vins:
_LOGGER.warning("VIN %s not found for %s", vin, username)
raise VinNotFoundException
10 changes: 8 additions & 2 deletions custom_components/polestar_api/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@
}
},
"abort": {
"api_timeout": "Timeout connecting to the api.",
"api_failed": "Unexpected error creating api.",
"api_timeout": "Timeout connecting to the API.",
"api_failed": "Unexpected error creating API.",
"already_configured": "Polestar API is already configured",
"no_token": "No token found in response. Please check your credentials."
},
"error": {
"auth_failed": "Invalid username/password.",
"no_cars_found": "No cars found for your Polestar ID.",
"vin_not_found": "Specified VIN not found for your Polestar ID.",
"api": "Error connecting to Polestar API."
}
},
"entity": {
Expand Down
6 changes: 6 additions & 0 deletions custom_components/polestar_api/translations/sv.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"api_failed": "Oväntat fel vid skapande av API.",
"already_configured": "Polestar API är redan konfigurerat",
"no_token": "Ingen token hittades i svaret. Vänligen kontrollera dina uppgifter."
},
"error": {
"auth_failed": "Felaktigt användarnamn/lösenord.",
"no_cars_found": "Inga fordon hittades för ditt Polestar ID.",
"vin_not_found": "Specificerad VIN hittades inte för ditt Polestar ID.",
"api": "Fel vid anslutning till Polestar API."
}
},
"entity": {
Expand Down

0 comments on commit a86f906

Please sign in to comment.