Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Bugfix: Electric Kiwi migrate unique id #135231

Open
wants to merge 41 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a268c52
fix unique id and migrate, fix small issue where title would be Home …
mikey0000 Jan 9, 2025
6249f54
Merge branch 'dev' into ek-fixes
mikey0000 Jan 9, 2025
747be15
fix tests
mikey0000 Jan 9, 2025
f63c1e8
remove quality scale for now and add abort if configured
mikey0000 Jan 9, 2025
538eaba
add version
mikey0000 Jan 9, 2025
e6330f9
add version migration
mikey0000 Jan 9, 2025
879d30a
add check for reauth
mikey0000 Jan 9, 2025
42852f0
check minor version
mikey0000 Jan 9, 2025
48bf863
update the title while we are at it
mikey0000 Jan 9, 2025
3931c97
fix reauth behaviour
mikey0000 Jan 11, 2025
f3a601e
check minor version and remove title change
mikey0000 Jan 11, 2025
011514f
remove assert and commented out bit
mikey0000 Jan 11, 2025
771c278
change to minor version bump as unique id's would still be unique
mikey0000 Jan 12, 2025
5e50e28
fix and add new tests
mikey0000 Jan 14, 2025
6b3576b
fix file permissions
mikey0000 Jan 14, 2025
39b1ebf
Merge branch 'dev' into ek-fixes
mikey0000 Jan 15, 2025
c85b9c2
Merge branch 'dev' into ek-fixes
mikey0000 Jan 15, 2025
6f54037
Merge branch 'dev' into ek-fixes
mikey0000 Jan 21, 2025
01d81a6
bump ek api version
mikey0000 Jan 27, 2025
892446e
fix connection id rename
mikey0000 Jan 27, 2025
13e42e3
bump library version
mikey0000 Jan 27, 2025
98868aa
rebuild
mikey0000 Jan 27, 2025
669c028
bump again
mikey0000 Jan 27, 2025
0a9f16b
bump again to fix missing license
mikey0000 Jan 27, 2025
03bc131
Merge branch 'dev' into ek-fixes
mikey0000 Jan 29, 2025
407a43a
get the library to pass the license check
mikey0000 Jan 30, 2025
4ff12a8
update scopes
mikey0000 Jan 30, 2025
f8e4859
fix library issue
mikey0000 Jan 30, 2025
2085a4c
add a couple more tests for init
mikey0000 Feb 2, 2025
5519ab8
add try except around get active connection to catch failures
mikey0000 Feb 2, 2025
6c2d562
remove no_customers after API change
mikey0000 Feb 2, 2025
ad63dd5
remove duplicate test
mikey0000 Feb 2, 2025
e064c8c
bump ek api version with a few small fixes
mikey0000 Feb 2, 2025
159e7e7
add migration failure test
mikey0000 Feb 2, 2025
ed2af75
tidy oauth create entry with exception
mikey0000 Feb 2, 2025
9191190
re-order test to fix weird issue
mikey0000 Feb 2, 2025
50fdf56
Fix tests
joostlek Feb 3, 2025
bca08c5
Fix tests
joostlek Feb 3, 2025
56c5416
fix failing tests
mikey0000 Feb 3, 2025
cb1f2cd
migrate entities as well
mikey0000 Feb 3, 2025
7d1b55d
update tests for migration
mikey0000 Feb 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 61 additions & 3 deletions homeassistant/components/electric_kiwi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

import aiohttp
from electrickiwi_api import ElectricKiwiApi
from electrickiwi_api.exceptions import ApiException
from electrickiwi_api.exceptions import ApiException, AuthException

from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow
from homeassistant.helpers import (
aiohttp_client,
config_entry_oauth2_flow,
entity_registry as er,
)

from . import api
from .coordinator import (
Expand Down Expand Up @@ -44,7 +48,9 @@
raise ConfigEntryNotReady from err

ek_api = ElectricKiwiApi(
api.AsyncConfigEntryAuth(aiohttp_client.async_get_clientsession(hass), session)
api.ConfigEntryElectricKiwiAuth(
aiohttp_client.async_get_clientsession(hass), session
)
)
hop_coordinator = ElectricKiwiHOPDataCoordinator(hass, entry, ek_api)
account_coordinator = ElectricKiwiAccountDataCoordinator(hass, entry, ek_api)
Expand All @@ -53,6 +59,8 @@
await ek_api.set_active_session()
await hop_coordinator.async_config_entry_first_refresh()
await account_coordinator.async_config_entry_first_refresh()
except AuthException as err:
raise ConfigEntryAuthFailed from err

Check warning on line 63 in homeassistant/components/electric_kiwi/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/electric_kiwi/__init__.py#L62-L63

Added lines #L62 - L63 were not covered by tests
except ApiException as err:
raise ConfigEntryNotReady from err

Expand All @@ -70,3 +78,53 @@
) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)


async def async_migrate_entry(
hass: HomeAssistant, config_entry: ElectricKiwiConfigEntry
) -> bool:
"""Migrate old entry."""
if config_entry.version == 1 and config_entry.minor_version == 1:
implementation = (
await config_entry_oauth2_flow.async_get_config_entry_implementation(
hass, config_entry
)
)

session = config_entry_oauth2_flow.OAuth2Session(
hass, config_entry, implementation
)

ek_api = ElectricKiwiApi(
api.ConfigEntryElectricKiwiAuth(
aiohttp_client.async_get_clientsession(hass), session
)
)
try:
await ek_api.set_active_session()
connection_details = await ek_api.get_connection_details()
except AuthException:
config_entry.async_start_reauth(hass)
return False
except ApiException:
return False
unique_id = str(ek_api.customer_number)
identifier = ek_api.electricity.identifier
hass.config_entries.async_update_entry(
config_entry, unique_id=unique_id, minor_version=2
)
entity_registry = er.async_get(hass)
entity_entries = er.async_entries_for_config_entry(
entity_registry, config_entry_id=config_entry.entry_id
)

for entity in entity_entries:
assert entity.config_entry_id
entity_registry.async_update_entity(
entity.entity_id,
new_unique_id=entity.unique_id.replace(
f"{unique_id}_{connection_details.id}", f"{unique_id}_{identifier}"
),
)

return True
26 changes: 21 additions & 5 deletions homeassistant/components/electric_kiwi/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@

from __future__ import annotations

from typing import cast

from aiohttp import ClientSession
from electrickiwi_api import AbstractAuth

from homeassistant.helpers import config_entry_oauth2_flow
from homeassistant.core import HomeAssistant
from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow

from .const import API_BASE_URL


class AsyncConfigEntryAuth(AbstractAuth):
class ConfigEntryElectricKiwiAuth(AbstractAuth):
"""Provide Electric Kiwi authentication tied to an OAuth2 based config entry."""

def __init__(
Expand All @@ -29,4 +28,21 @@
"""Return a valid access token."""
await self._oauth_session.async_ensure_token_valid()

return cast(str, self._oauth_session.token["access_token"])
return str(self._oauth_session.token["access_token"])

Check warning on line 31 in homeassistant/components/electric_kiwi/api.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/electric_kiwi/api.py#L31

Added line #L31 was not covered by tests


class ConfigFlowElectricKiwiAuth(AbstractAuth):
"""Provide Electric Kiwi authentication tied to an OAuth2 based config flow."""

def __init__(
self,
hass: HomeAssistant,
token: str,
) -> None:
"""Initialize ConfigFlowFitbitApi."""
super().__init__(aiohttp_client.async_get_clientsession(hass), API_BASE_URL)
self._token = token

async def async_get_access_token(self) -> str:
"""Return the token for the Electric Kiwi API."""
return self._token

Check warning on line 48 in homeassistant/components/electric_kiwi/api.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/electric_kiwi/api.py#L48

Added line #L48 was not covered by tests
37 changes: 31 additions & 6 deletions homeassistant/components/electric_kiwi/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
import logging
from typing import Any

from homeassistant.config_entries import ConfigFlowResult
from electrickiwi_api import ElectricKiwiApi
from electrickiwi_api.exceptions import ApiException

from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlowResult
from homeassistant.const import CONF_NAME
from homeassistant.helpers import config_entry_oauth2_flow

from . import api
from .const import DOMAIN, SCOPE_VALUES


Expand All @@ -17,6 +22,8 @@ class ElectricKiwiOauth2FlowHandler(
):
"""Config flow to handle Electric Kiwi OAuth2 authentication."""

VERSION = 1
MINOR_VERSION = 2
DOMAIN = DOMAIN

@property
Expand All @@ -40,12 +47,30 @@ async def async_step_reauth_confirm(
) -> ConfigFlowResult:
"""Dialog that informs the user that reauth is required."""
if user_input is None:
return self.async_show_form(step_id="reauth_confirm")
return self.async_show_form(
step_id="reauth_confirm",
description_placeholders={CONF_NAME: self._get_reauth_entry().title},
)
return await self.async_step_user()

async def async_oauth_create_entry(self, data: dict) -> ConfigFlowResult:
"""Create an entry for Electric Kiwi."""
existing_entry = await self.async_set_unique_id(DOMAIN)
if existing_entry:
return self.async_update_reload_and_abort(existing_entry, data=data)
return await super().async_oauth_create_entry(data)
ek_api = ElectricKiwiApi(
api.ConfigFlowElectricKiwiAuth(self.hass, data["token"]["access_token"])
)

try:
session = await ek_api.get_active_session()
except ApiException:
return self.async_abort(reason="connection_error")

unique_id = str(session.data.customer_number)
await self.async_set_unique_id(unique_id)
if self.source == SOURCE_REAUTH:
self._abort_if_unique_id_mismatch(reason="wrong_account")
return self.async_update_reload_and_abort(
self._get_reauth_entry(), data=data
)

self._abort_if_unique_id_configured()
return self.async_create_entry(title=unique_id, data=data)
2 changes: 1 addition & 1 deletion homeassistant/components/electric_kiwi/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
OAUTH2_TOKEN = "https://welcome.electrickiwi.co.nz/oauth/token"
API_BASE_URL = "https://api.electrickiwi.co.nz"

SCOPE_VALUES = "read_connection_detail read_billing_frequency read_account_running_balance read_consumption_summary read_consumption_averages read_hop_intervals_config read_hop_connection save_hop_connection read_session"
SCOPE_VALUES = "read_customer_details read_connection_detail read_connection read_billing_address get_bill_address read_billing_frequency read_billing_details read_billing_bills read_billing_bill read_billing_bill_id read_billing_bill_file read_account_running_balance read_customer_account_summary read_consumption_summary download_consumption_file read_consumption_averages get_consumption_averages read_hop_intervals_config read_hop_intervals read_hop_connection read_hop_specific_connection save_hop_connection save_hop_specific_connection read_outage_contact get_outage_contact_info_for_icp read_session read_session_data_login"
18 changes: 9 additions & 9 deletions homeassistant/components/electric_kiwi/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from electrickiwi_api import ElectricKiwiApi
from electrickiwi_api.exceptions import ApiException, AuthException
from electrickiwi_api.model import AccountBalance, Hop, HopIntervals
from electrickiwi_api.model import AccountSummary, Hop, HopIntervals

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
Expand All @@ -34,7 +34,7 @@
type ElectricKiwiConfigEntry = ConfigEntry[ElectricKiwiRuntimeData]


class ElectricKiwiAccountDataCoordinator(DataUpdateCoordinator[AccountBalance]):
class ElectricKiwiAccountDataCoordinator(DataUpdateCoordinator[AccountSummary]):
"""ElectricKiwi Account Data object."""

def __init__(
Expand All @@ -51,13 +51,13 @@
name="Electric Kiwi Account Data",
update_interval=ACCOUNT_SCAN_INTERVAL,
)
self._ek_api = ek_api
self.ek_api = ek_api

async def _async_update_data(self) -> AccountBalance:
async def _async_update_data(self) -> AccountSummary:
"""Fetch data from Account balance API endpoint."""
try:
async with asyncio.timeout(60):
return await self._ek_api.get_account_balance()
return await self.ek_api.get_account_summary()
except AuthException as auth_err:
raise ConfigEntryAuthFailed from auth_err
except ApiException as api_err:
Expand Down Expand Up @@ -85,7 +85,7 @@
# Polling interval. Will only be polled if there are subscribers.
update_interval=HOP_SCAN_INTERVAL,
)
self._ek_api = ek_api
self.ek_api = ek_api
self.hop_intervals: HopIntervals | None = None

def get_hop_options(self) -> dict[str, int]:
Expand All @@ -100,7 +100,7 @@
async def async_update_hop(self, hop_interval: int) -> Hop:
"""Update selected hop and data."""
try:
self.async_set_updated_data(await self._ek_api.post_hop(hop_interval))
self.async_set_updated_data(await self.ek_api.post_hop(hop_interval))

Check warning on line 103 in homeassistant/components/electric_kiwi/coordinator.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/electric_kiwi/coordinator.py#L103

Added line #L103 was not covered by tests
except AuthException as auth_err:
raise ConfigEntryAuthFailed from auth_err
except ApiException as api_err:
Expand All @@ -118,7 +118,7 @@
try:
async with asyncio.timeout(60):
if self.hop_intervals is None:
hop_intervals: HopIntervals = await self._ek_api.get_hop_intervals()
hop_intervals: HopIntervals = await self.ek_api.get_hop_intervals()
hop_intervals.intervals = OrderedDict(
filter(
lambda pair: pair[1].active == 1,
Expand All @@ -127,7 +127,7 @@
)

self.hop_intervals = hop_intervals
return await self._ek_api.get_hop()
return await self.ek_api.get_hop()
except AuthException as auth_err:
raise ConfigEntryAuthFailed from auth_err
except ApiException as api_err:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/electric_kiwi/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/electric_kiwi",
"integration_type": "hub",
"iot_class": "cloud_polling",
"requirements": ["electrickiwi-api==0.8.5"]
"requirements": ["electrickiwi-api==0.9.12"]
}
4 changes: 2 additions & 2 deletions homeassistant/components/electric_kiwi/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ def __init__(
"""Initialise the HOP selection entity."""
super().__init__(coordinator)
self._attr_unique_id = (
f"{coordinator._ek_api.customer_number}" # noqa: SLF001
f"_{coordinator._ek_api.connection_id}_{description.key}" # noqa: SLF001
f"{coordinator.ek_api.customer_number}"
f"_{coordinator.ek_api.electricity.identifier}_{description.key}"
)
self.entity_description = description
self.values_dict = coordinator.get_hop_options()
Expand Down
24 changes: 15 additions & 9 deletions homeassistant/components/electric_kiwi/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from dataclasses import dataclass
from datetime import datetime, timedelta

from electrickiwi_api.model import AccountBalance, Hop
from electrickiwi_api.model import AccountSummary, Hop

from homeassistant.components.sensor import (
SensorDeviceClass,
Expand Down Expand Up @@ -39,7 +39,15 @@
class ElectricKiwiAccountSensorEntityDescription(SensorEntityDescription):
"""Describes Electric Kiwi sensor entity."""

value_func: Callable[[AccountBalance], float | datetime]
value_func: Callable[[AccountSummary], float | datetime]


def _get_hop_percentage(account_balance: AccountSummary) -> float:
"""Return the hop percentage from account summary."""
if power := account_balance.services.get("power"):
if connection := power.connections[0]:
return float(connection.hop_percentage)
return 0.0

Check warning on line 50 in homeassistant/components/electric_kiwi/sensor.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/electric_kiwi/sensor.py#L50

Added line #L50 was not covered by tests


ACCOUNT_SENSOR_TYPES: tuple[ElectricKiwiAccountSensorEntityDescription, ...] = (
Expand Down Expand Up @@ -72,9 +80,7 @@
translation_key="hop_power_savings",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_func=lambda account_balance: float(
account_balance.connections[0].hop_percentage
),
value_func=_get_hop_percentage,
),
)

Expand Down Expand Up @@ -165,8 +171,8 @@
super().__init__(coordinator)

self._attr_unique_id = (
f"{coordinator._ek_api.customer_number}" # noqa: SLF001
f"_{coordinator._ek_api.connection_id}_{description.key}" # noqa: SLF001
f"{coordinator.ek_api.customer_number}"
f"_{coordinator.ek_api.electricity.identifier}_{description.key}"
)
self.entity_description = description

Expand Down Expand Up @@ -194,8 +200,8 @@
super().__init__(coordinator)

self._attr_unique_id = (
f"{coordinator._ek_api.customer_number}" # noqa: SLF001
f"_{coordinator._ek_api.connection_id}_{description.key}" # noqa: SLF001
f"{coordinator.ek_api.customer_number}"
f"_{coordinator.ek_api.electricity.identifier}_{description.key}"
)
self.entity_description = description

Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/electric_kiwi/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]"
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
"connection_error": "[%key:common::config_flow::error::cannot_connect%]"
},
"create_entry": {
"default": "[%key:common::config_flow::create_entry::authenticated%]"
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion requirements_test_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions tests/components/electric_kiwi/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
"""Tests for the Electric Kiwi integration."""

from homeassistant.core import HomeAssistant

from tests.common import MockConfigEntry


async def init_integration(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Fixture for setting up the integration with args."""
entry.add_to_hass(hass)

await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
Loading
Loading