From b3a625593e2eba65e51283fe3f5fca4b5e619769 Mon Sep 17 00:00:00 2001 From: Tom Brien Date: Fri, 2 Jul 2021 10:15:05 +0100 Subject: [PATCH] Add update listener to Coinbase (#52404) Co-authored-by: Franck Nijhof --- homeassistant/components/coinbase/__init__.py | 26 +++++++++++++++++ homeassistant/components/coinbase/sensor.py | 16 +++++++---- tests/components/coinbase/const.py | 4 +-- tests/components/coinbase/test_config_flow.py | 28 ++++++++++++++++--- 4 files changed, 62 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/coinbase/__init__.py b/homeassistant/components/coinbase/__init__.py index eb4370a9534..08b97756dff 100644 --- a/homeassistant/components/coinbase/__init__.py +++ b/homeassistant/components/coinbase/__init__.py @@ -11,6 +11,7 @@ import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_API_TOKEN from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType from homeassistant.util import Throttle @@ -70,6 +71,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: create_and_update_instance, entry.data[CONF_API_KEY], entry.data[CONF_API_TOKEN] ) + entry.async_on_unload(entry.add_update_listener(update_listener)) + hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = instance @@ -96,6 +99,29 @@ def create_and_update_instance(api_key, api_token): return instance +async def update_listener(hass, config_entry): + """Handle options update.""" + + await hass.config_entries.async_reload(config_entry.entry_id) + + registry = entity_registry.async_get(hass) + entities = entity_registry.async_entries_for_config_entry( + registry, config_entry.entry_id + ) + + # Remove orphaned entities + for entity in entities: + currency = entity.unique_id.split("-")[-1] + if "xe" in entity.unique_id and currency not in config_entry.options.get( + CONF_EXCHANGE_RATES + ): + registry.async_remove(entity.entity_id) + elif "wallet" in entity.unique_id and currency not in config_entry.options.get( + CONF_CURRENCIES + ): + registry.async_remove(entity.entity_id) + + def get_accounts(client): """Handle paginated accounts.""" response = client.get_accounts() diff --git a/homeassistant/components/coinbase/sensor.py b/homeassistant/components/coinbase/sensor.py index 5febfe8a978..13981619051 100644 --- a/homeassistant/components/coinbase/sensor.py +++ b/homeassistant/components/coinbase/sensor.py @@ -11,6 +11,7 @@ from .const import ( API_ACCOUNT_ID, API_ACCOUNT_NAME, API_ACCOUNT_NATIVE_BALANCE, + API_RATES, CONF_CURRENCIES, CONF_EXCHANGE_RATES, DOMAIN, @@ -48,7 +49,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if CONF_CURRENCIES in config_entry.options: desired_currencies = config_entry.options[CONF_CURRENCIES] - exchange_native_currency = instance.exchange_rates.currency + exchange_native_currency = instance.exchange_rates[API_ACCOUNT_CURRENCY] for currency in desired_currencies: if currency not in provided_currencies: @@ -81,9 +82,12 @@ class AccountSensor(SensorEntity): self._coinbase_data = coinbase_data self._currency = currency for account in coinbase_data.accounts: - if account.currency == currency: + if account[API_ACCOUNT_CURRENCY] == currency: self._name = f"Coinbase {account[API_ACCOUNT_NAME]}" - self._id = f"coinbase-{account[API_ACCOUNT_ID]}" + self._id = ( + f"coinbase-{account[API_ACCOUNT_ID]}-wallet-" + f"{account[API_ACCOUNT_CURRENCY]}" + ) self._state = account[API_ACCOUNT_BALANCE][API_ACCOUNT_AMOUNT] self._unit_of_measurement = account[API_ACCOUNT_CURRENCY] self._native_balance = account[API_ACCOUNT_NATIVE_BALANCE][ @@ -131,7 +135,7 @@ class AccountSensor(SensorEntity): """Get the latest state of the sensor.""" self._coinbase_data.update() for account in self._coinbase_data.accounts: - if account.currency == self._currency: + if account[API_ACCOUNT_CURRENCY] == self._currency: self._state = account[API_ACCOUNT_BALANCE][API_ACCOUNT_AMOUNT] self._native_balance = account[API_ACCOUNT_NATIVE_BALANCE][ API_ACCOUNT_AMOUNT @@ -150,9 +154,9 @@ class ExchangeRateSensor(SensorEntity): self._coinbase_data = coinbase_data self.currency = exchange_currency self._name = f"{exchange_currency} Exchange Rate" - self._id = f"{coinbase_data.user_id}-xe-{exchange_currency}" + self._id = f"coinbase-{coinbase_data.user_id}-xe-{exchange_currency}" self._state = round( - 1 / float(self._coinbase_data.exchange_rates.rates[self.currency]), 2 + 1 / float(self._coinbase_data.exchange_rates[API_RATES][self.currency]), 2 ) self._unit_of_measurement = native_currency diff --git a/tests/components/coinbase/const.py b/tests/components/coinbase/const.py index 52505be514e..864ebc18701 100644 --- a/tests/components/coinbase/const.py +++ b/tests/components/coinbase/const.py @@ -11,14 +11,14 @@ BAD_EXCHANGE_RATE = "ETH" MOCK_ACCOUNTS_RESPONSE = [ { "balance": {"amount": "13.38", "currency": GOOD_CURRENCY_3}, - "currency": "BTC", + "currency": GOOD_CURRENCY_3, "id": "ABCDEF", "name": "BTC Wallet", "native_balance": {"amount": "15.02", "currency": GOOD_CURRENCY_2}, }, { "balance": {"amount": "0.00001", "currency": GOOD_CURRENCY}, - "currency": "BTC", + "currency": GOOD_CURRENCY, "id": "123456789", "name": "BTC Wallet", "native_balance": {"amount": "100.12", "currency": GOOD_CURRENCY_2}, diff --git a/tests/components/coinbase/test_config_flow.py b/tests/components/coinbase/test_config_flow.py index dc036d23a6f..4c7b6c13333 100644 --- a/tests/components/coinbase/test_config_flow.py +++ b/tests/components/coinbase/test_config_flow.py @@ -19,7 +19,14 @@ from .common import ( mock_get_exchange_rates, mocked_get_accounts, ) -from .const import BAD_CURRENCY, BAD_EXCHANGE_RATE, GOOD_CURRENCY, GOOD_EXCHNAGE_RATE +from .const import ( + BAD_CURRENCY, + BAD_EXCHANGE_RATE, + GOOD_CURRENCY, + GOOD_CURRENCY_2, + GOOD_EXCHNAGE_RATE, + GOOD_EXCHNAGE_RATE_2, +) from tests.common import MockConfigEntry @@ -139,6 +146,18 @@ async def test_form_catch_all_exception(hass): async def test_option_good_account_currency(hass): """Test we handle a good wallet currency option.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + entry_id="abcde12345", + title="Test User", + data={CONF_API_KEY: "123456", CONF_API_TOKEN: "AbCDeF"}, + options={ + CONF_CURRENCIES: [GOOD_CURRENCY_2], + CONF_EXCHANGE_RATES: [], + }, + ) + config_entry.add_to_hass(hass) + with patch( "coinbase.wallet.client.Client.get_current_user", return_value=mock_get_current_user(), @@ -148,7 +167,8 @@ async def test_option_good_account_currency(hass): "coinbase.wallet.client.Client.get_exchange_rates", return_value=mock_get_exchange_rates(), ): - config_entry = await init_mock_coinbase(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() result = await hass.config_entries.options.async_init(config_entry.entry_id) await hass.async_block_till_done() result2 = await hass.config_entries.options.async_configure( @@ -191,12 +211,12 @@ async def test_option_good_exchange_rate(hass): """Test we handle a good exchange rate option.""" config_entry = MockConfigEntry( domain=DOMAIN, - unique_id="abcde12345", + entry_id="abcde12345", title="Test User", data={CONF_API_KEY: "123456", CONF_API_TOKEN: "AbCDeF"}, options={ CONF_CURRENCIES: [], - CONF_EXCHANGE_RATES: [], + CONF_EXCHANGE_RATES: [GOOD_EXCHNAGE_RATE_2], }, ) config_entry.add_to_hass(hass)