From 1968b95829bbcfdf04edb0db6a2c1e511907b07b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 28 Jul 2021 08:55:58 +0200 Subject: [PATCH] Add currency core configuration (#53541) Co-authored-by: Paulus Schoutsen --- homeassistant/components/api/__init__.py | 2 + homeassistant/components/config/core.py | 1 + homeassistant/config.py | 4 + homeassistant/core.py | 7 + homeassistant/helpers/config_validation.py | 164 +++++++++++++++++++++ tests/components/config/test_core.py | 5 +- tests/helpers/test_config_validation.py | 15 ++ tests/test_config.py | 12 +- tests/test_core.py | 2 + 9 files changed, 210 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index a91d8540286..0a11cf04651 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -43,6 +43,7 @@ from homeassistant.helpers.system_info import async_get_system_info _LOGGER = logging.getLogger(__name__) ATTR_BASE_URL = "base_url" +ATTR_CURRENCY = "currency" ATTR_EXTERNAL_URL = "external_url" ATTR_INTERNAL_URL = "internal_url" ATTR_LOCATION_NAME = "location_name" @@ -195,6 +196,7 @@ class APIDiscoveryView(HomeAssistantView): # always needs authentication ATTR_REQUIRES_API_PASSWORD: True, ATTR_VERSION: __version__, + ATTR_CURRENCY: None, } with suppress(NoURLAvailableError): diff --git a/homeassistant/components/config/core.py b/homeassistant/components/config/core.py index 3b38ab49c4b..d9029dc497f 100644 --- a/homeassistant/components/config/core.py +++ b/homeassistant/components/config/core.py @@ -46,6 +46,7 @@ class CheckConfigView(HomeAssistantView): vol.Optional("time_zone"): cv.time_zone, vol.Optional("external_url"): vol.Any(cv.url, None), vol.Optional("internal_url"): vol.Any(cv.url, None), + vol.Optional("currency"): cv.currency, } ) async def websocket_update_config(hass, connection, msg): diff --git a/homeassistant/config.py b/homeassistant/config.py index 9e128fc6d23..8641a80ddef 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -28,6 +28,7 @@ from homeassistant.const import ( CONF_ALLOWLIST_EXTERNAL_URLS, CONF_AUTH_MFA_MODULES, CONF_AUTH_PROVIDERS, + CONF_CURRENCY, CONF_CUSTOMIZE, CONF_CUSTOMIZE_DOMAIN, CONF_CUSTOMIZE_GLOB, @@ -238,6 +239,7 @@ CORE_CONFIG_SCHEMA = CUSTOMIZE_CONFIG_SCHEMA.extend( # pylint: disable=no-value-for-parameter vol.Optional(CONF_MEDIA_DIRS): cv.schema_with_slug_keys(vol.IsDir()), vol.Optional(CONF_LEGACY_TEMPLATES): cv.boolean, + vol.Optional(CONF_CURRENCY): cv.currency, } ) @@ -520,6 +522,7 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: dict) -> Non CONF_UNIT_SYSTEM, CONF_EXTERNAL_URL, CONF_INTERNAL_URL, + CONF_CURRENCY, ) ): hac.config_source = SOURCE_YAML @@ -533,6 +536,7 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: dict) -> Non (CONF_EXTERNAL_URL, "external_url"), (CONF_MEDIA_DIRS, "media_dirs"), (CONF_LEGACY_TEMPLATES, "legacy_templates"), + (CONF_CURRENCY, "currency"), ): if key in config: setattr(hac, attr, config[key]) diff --git a/homeassistant/core.py b/homeassistant/core.py index aba4483f192..e2418321592 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1545,6 +1545,7 @@ class Config: self.units: UnitSystem = METRIC_SYSTEM self.internal_url: str | None = None self.external_url: str | None = None + self.currency: str = "EUR" self.config_source: str = "default" @@ -1650,6 +1651,7 @@ class Config: "state": self.hass.state.value, "external_url": self.external_url, "internal_url": self.internal_url, + "currency": self.currency, } def set_time_zone(self, time_zone_str: str) -> None: @@ -1676,6 +1678,7 @@ class Config: # pylint: disable=dangerous-default-value # _UNDEFs not modified external_url: str | dict | None = _UNDEF, internal_url: str | dict | None = _UNDEF, + currency: str | None = None, ) -> None: """Update the configuration from a dictionary.""" self.config_source = source @@ -1698,6 +1701,8 @@ class Config: self.external_url = cast(Optional[str], external_url) if internal_url is not _UNDEF: self.internal_url = cast(Optional[str], internal_url) + if currency is not None: + self.currency = currency async def async_update(self, **kwargs: Any) -> None: """Update the configuration from a dictionary.""" @@ -1723,6 +1728,7 @@ class Config: time_zone=data.get("time_zone"), external_url=data.get("external_url", _UNDEF), internal_url=data.get("internal_url", _UNDEF), + currency=data.get("currency"), ) async def async_store(self) -> None: @@ -1736,6 +1742,7 @@ class Config: "time_zone": self.time_zone, "external_url": self.external_url, "internal_url": self.internal_url, + "currency": self.currency, } store = self.hass.helpers.storage.Store( diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index e195c1ded31..66d1c01d6d3 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1269,3 +1269,167 @@ ACTION_TYPE_SCHEMAS: dict[str, Callable[[Any], dict]] = { SCRIPT_ACTION_WAIT_FOR_TRIGGER: _SCRIPT_WAIT_FOR_TRIGGER_SCHEMA, SCRIPT_ACTION_VARIABLES: _SCRIPT_SET_SCHEMA, } + + +# Validate currencies adopted by countries +currency = vol.In( + { + "AED", + "AFN", + "ALL", + "AMD", + "ANG", + "AOA", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BTN", + "BWP", + "BYR", + "BZD", + "CAD", + "CDF", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CUP", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EGP", + "ERN", + "ETB", + "EUR", + "FJD", + "FKP", + "GBP", + "GEL", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HTG", + "HUF", + "IDR", + "ILS", + "INR", + "IQD", + "IRR", + "ISK", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KPW", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LTL", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MRO", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SDG", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SRD", + "SSP", + "STD", + "SYP", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "UYU", + "UZS", + "VEF", + "VND", + "VUV", + "WST", + "XAF", + "XCD", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMK", + "ZWL", + }, + msg="invalid ISO 4217 formatted currency", +) diff --git a/tests/components/config/test_core.py b/tests/components/config/test_core.py index 54fd6a76565..9c86a3f2d1b 100644 --- a/tests/components/config/test_core.py +++ b/tests/components/config/test_core.py @@ -1,4 +1,4 @@ -"""Test hassbian config.""" +"""Test core config.""" from unittest.mock import patch import pytest @@ -60,6 +60,7 @@ async def test_websocket_core_update(hass, client): assert hass.config.time_zone != "America/New_York" assert hass.config.external_url != "https://www.example.com" assert hass.config.internal_url != "http://example.com" + assert hass.config.currency == "EUR" with patch("homeassistant.util.dt.set_default_time_zone") as mock_set_tz: await client.send_json( @@ -74,6 +75,7 @@ async def test_websocket_core_update(hass, client): "time_zone": "America/New_York", "external_url": "https://www.example.com", "internal_url": "http://example.local", + "currency": "USD", } ) @@ -89,6 +91,7 @@ async def test_websocket_core_update(hass, client): assert hass.config.units.name == CONF_UNIT_SYSTEM_IMPERIAL assert hass.config.external_url == "https://www.example.com" assert hass.config.internal_url == "http://example.local" + assert hass.config.currency == "USD" assert len(mock_set_tz.mock_calls) == 1 assert mock_set_tz.mock_calls[0][1][0] == dt_util.get_time_zone("America/New_York") diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 02303825bbd..c5e9f5880c4 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -1085,3 +1085,18 @@ def test_whitespace(): for value in (" ", " "): assert schema(value) + + +def test_currency(): + """Test currency validator.""" + schema = vol.Schema(cv.currency) + + for value in ( + None, + "BTC", + ): + with pytest.raises(vol.MultipleInvalid): + schema(value) + + for value in ("EUR", "USD"): + assert schema(value) diff --git a/tests/test_config.py b/tests/test_config.py index 87496c566e3..c1eb1ab7540 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -193,6 +193,7 @@ def test_core_config_schema(): {"longitude": -181}, {"external_url": "not an url"}, {"internal_url": "not an url"}, + {"currency", 100}, {"customize": "bla"}, {"customize": {"light.sensor": 100}}, {"customize": {"entity_id": []}}, @@ -208,6 +209,7 @@ def test_core_config_schema(): "external_url": "https://www.example.com", "internal_url": "http://example.local", CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + "currency": "USD", "customize": {"sensor.temperature": {"hidden": True}}, } ) @@ -360,6 +362,7 @@ async def test_loading_configuration_from_storage(hass, hass_storage): "unit_system": "metric", "external_url": "https://www.example.com", "internal_url": "http://example.local", + "currency": "EUR", }, "key": "core.config", "version": 1, @@ -376,6 +379,7 @@ async def test_loading_configuration_from_storage(hass, hass_storage): assert hass.config.time_zone == "Europe/Copenhagen" assert hass.config.external_url == "https://www.example.com" assert hass.config.internal_url == "http://example.local" + assert hass.config.currency == "EUR" assert len(hass.config.allowlist_external_dirs) == 3 assert "/etc" in hass.config.allowlist_external_dirs assert hass.config.config_source == SOURCE_STORAGE @@ -423,6 +427,7 @@ async def test_updating_configuration(hass, hass_storage): "unit_system": "metric", "external_url": "https://www.example.com", "internal_url": "http://example.local", + "currency": "BTC", }, "key": "core.config", "version": 1, @@ -431,12 +436,14 @@ async def test_updating_configuration(hass, hass_storage): await config_util.async_process_ha_core_config( hass, {"allowlist_external_dirs": "/etc"} ) - await hass.config.async_update(latitude=50) + await hass.config.async_update(latitude=50, currency="USD") new_core_data = copy.deepcopy(core_data) new_core_data["data"]["latitude"] = 50 + new_core_data["data"]["currency"] = "USD" assert hass_storage["core.config"] == new_core_data assert hass.config.latitude == 50 + assert hass.config.currency == "USD" async def test_override_stored_configuration(hass, hass_storage): @@ -484,6 +491,7 @@ async def test_loading_configuration(hass): "internal_url": "http://example.local", "media_dirs": {"mymedia": "/usr"}, "legacy_templates": True, + "currency": "EUR", }, ) @@ -501,6 +509,7 @@ async def test_loading_configuration(hass): assert hass.config.media_dirs == {"mymedia": "/usr"} assert hass.config.config_source == config_util.SOURCE_YAML assert hass.config.legacy_templates is True + assert hass.config.currency == "EUR" async def test_loading_configuration_temperature_unit(hass): @@ -528,6 +537,7 @@ async def test_loading_configuration_temperature_unit(hass): assert hass.config.external_url == "https://www.example.com" assert hass.config.internal_url == "http://example.local" assert hass.config.config_source == config_util.SOURCE_YAML + assert hass.config.currency == "EUR" async def test_loading_configuration_default_media_dirs_docker(hass): diff --git a/tests/test_core.py b/tests/test_core.py index 5f17625a3de..77ec07e6a63 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -912,6 +912,7 @@ def test_config_defaults(): assert config.media_dirs == {} assert config.safe_mode is False assert config.legacy_templates is False + assert config.currency == "EUR" def test_config_path_with_file(): @@ -952,6 +953,7 @@ def test_config_as_dict(): "state": "RUNNING", "external_url": None, "internal_url": None, + "currency": "EUR", } assert expected == config.as_dict()