diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1a2e4d15482..1676b60a802 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: hooks: - id: codespell args: - - --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,iif,ines,ist,lightsensor,mut,nd,pres,referer,rime,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba,haa,pullrequests + - --ignore-words-list=hass,alot,bre,datas,dof,dur,ether,farenheit,hist,iff,iif,ines,ist,lightsensor,mut,nd,pres,referer,rime,ser,serie,sur,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba,haa,pullrequests - --skip="./.*,*.csv,*.json" - --quiet-level=2 exclude_types: [csv, json] diff --git a/homeassistant/components/homeassistant/strings.json b/homeassistant/components/homeassistant/strings.json index 317e9d1dfcd..2db00081eaa 100644 --- a/homeassistant/components/homeassistant/strings.json +++ b/homeassistant/components/homeassistant/strings.json @@ -1,4 +1,10 @@ { + "issues": { + "historic_currency": { + "title": "The configured currency is no longer in use", + "description": "The currency {currency} is no longer in use, please reconfigure the currency configuration." + } + }, "system_health": { "info": { "arch": "CPU Architecture", diff --git a/homeassistant/config.py b/homeassistant/config.py index e56dff4e491..8e06c2c47a2 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections import OrderedDict from collections.abc import Callable, Sequence +from contextlib import suppress import logging import os from pathlib import Path @@ -47,12 +48,19 @@ from .const import ( LEGACY_CONF_WHITELIST_EXTERNAL_DIRS, __version__, ) -from .core import DOMAIN as CONF_CORE, ConfigSource, HomeAssistant, callback +from .core import ( + DOMAIN as CONF_CORE, + ConfigSource, + HomeAssistant, + async_get_hass, + callback, +) from .exceptions import HomeAssistantError from .helpers import ( config_per_platform, config_validation as cv, extract_domain_configs, + issue_registry as ir, ) from .helpers.entity_values import EntityValues from .helpers.typing import ConfigType @@ -199,6 +207,27 @@ CUSTOMIZE_CONFIG_SCHEMA = vol.Schema( } ) + +def _validate_currency(data: Any) -> Any: + hass = async_get_hass() + try: + return cv.currency(data) + except vol.InInvalid: + with suppress(vol.InInvalid): + currency = cv.historic_currency(data) + ir.async_create_issue( + hass, + "homeassistant", + "historic_currency", + is_fixable=False, + severity=ir.IssueSeverity.WARNING, + translation_key="historic_currency", + translation_placeholders={"currency": currency}, + ) + return currency + raise + + CORE_CONFIG_SCHEMA = vol.All( CUSTOMIZE_CONFIG_SCHEMA.extend( { @@ -250,10 +279,10 @@ CORE_CONFIG_SCHEMA = vol.All( ], _no_duplicate_auth_mfa_module, ), - # pylint: disable=no-value-for-parameter + # pylint: disable-next=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, + vol.Optional(CONF_CURRENCY): _validate_currency, } ), _filter_bad_internal_external_urls, diff --git a/homeassistant/generated/currencies.py b/homeassistant/generated/currencies.py new file mode 100644 index 00000000000..7a5a6a31bb5 --- /dev/null +++ b/homeassistant/generated/currencies.py @@ -0,0 +1,290 @@ +"""Automatically generated by currencies.py. + +To update, run python3 -m script.currencies +""" + +ACTIVE_CURRENCIES = { + "AED", + "AFN", + "ALL", + "AMD", + "ANG", + "AOA", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BTN", + "BWP", + "BYN", + "BZD", + "CAD", + "CDF", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CUC", + "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", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MRU", + "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", + "SLE", + "SLL", + "SOS", + "SRD", + "SSP", + "STN", + "SVC", + "SYP", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "UYU", + "UZS", + "VED", + "VES", + "VND", + "VUV", + "WST", + "XAF", + "XCD", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMW", + "ZWL", +} + +HISTORIC_CURRENCIES = { + "ADP", + "AFA", + "ALK", + "AOK", + "AON", + "AOR", + "ARA", + "ARP", + "ARY", + "ATS", + "AYM", + "AZM", + "BAD", + "BEC", + "BEF", + "BEL", + "BGJ", + "BGK", + "BGL", + "BOP", + "BRB", + "BRC", + "BRE", + "BRN", + "BRR", + "BUK", + "BYB", + "BYR", + "CHC", + "CSD", + "CSJ", + "CSK", + "CYP", + "DDM", + "DEM", + "ECS", + "ECV", + "EEK", + "ESA", + "ESB", + "ESP", + "FIM", + "FRF", + "GEK", + "GHC", + "GHP", + "GNE", + "GNS", + "GQE", + "GRD", + "GWE", + "GWP", + "HRD", + "IEP", + "ILP", + "ILR", + "ISJ", + "ITL", + "LAJ", + "LSM", + "LTL", + "LTT", + "LUC", + "LUF", + "LUL", + "LVL", + "LVR", + "MGF", + "MLF", + "MRO", + "MTL", + "MTP", + "MVQ", + "MXP", + "MZE", + "MZM", + "NIC", + "NLG", + "PEH", + "PEI", + "PES", + "PLZ", + "PTE", + "RHD", + "ROK", + "ROL", + "RUR", + "SDD", + "SDP", + "SIT", + "SKK", + "SRG", + "STD", + "SUR", + "TJR", + "TMM", + "TPE", + "TRL", + "UAK", + "UGS", + "UGW", + "USS", + "UYN", + "UYP", + "VEB", + "VEF", + "VNC", + "XEU", + "XFO", + "YDD", + "YUD", + "YUM", + "YUN", + "ZAL", + "ZMK", + "ZRN", + "ZRZ", + "ZWC", + "ZWD", + "ZWN", + "ZWR", +} diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 35191d77042..48f26c2b768 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -88,6 +88,7 @@ from homeassistant.const import ( ) from homeassistant.core import split_entity_id, valid_entity_id from homeassistant.exceptions import TemplateError +from homeassistant.generated import currencies from homeassistant.util import raise_if_invalid_path, slugify as util_slugify import homeassistant.util.dt as dt_util @@ -1654,167 +1655,10 @@ ACTION_TYPE_SCHEMAS: dict[str, Callable[[Any], dict]] = { } -# 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", - "BYN", - "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", - "ZMW", - "ZWL", - }, - msg="invalid ISO 4217 formatted currency", + currencies.ACTIVE_CURRENCIES, msg="invalid ISO 4217 formatted currency" +) + +historic_currency = vol.In( + currencies.HISTORIC_CURRENCIES, msg="invalid ISO 4217 formatted historic currency" ) diff --git a/script/currencies.py b/script/currencies.py new file mode 100644 index 00000000000..2e538ff7c97 --- /dev/null +++ b/script/currencies.py @@ -0,0 +1,55 @@ +"""Helper script to update currency list from the official source.""" +import pathlib + +import black +from bs4 import BeautifulSoup +import requests + +BASE = """ +\"\"\"Automatically generated by currencies.py. + +To update, run python3 -m script.currencies +\"\"\" + +ACTIVE_CURRENCIES = {{ {} }} + +HISTORIC_CURRENCIES = {{ {} }} +""".strip() + +req = requests.get( + "https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/lists/list-one.xml" +) +soup = BeautifulSoup(req.content, "xml") +active_currencies = sorted( + { + x.Ccy.contents[0] + for x in soup.ISO_4217.CcyTbl.children + if x.name == "CcyNtry" + and x.Ccy + and x.CcyMnrUnts.contents[0] != "N.A." + and "IsFund" not in x.CcyNm.attrs + and x.Ccy.contents[0] != "UYW" + } +) + +req = requests.get( + "https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/lists/list-three.xml" +) +soup = BeautifulSoup(req.content, "xml") +historic_currencies = sorted( + { + x.Ccy.contents[0] + for x in soup.ISO_4217.HstrcCcyTbl.children + if x.name == "HstrcCcyNtry" + and x.Ccy + and "IsFund" not in x.CcyNm.attrs + and x.Ccy.contents[0] not in active_currencies + } +) + +pathlib.Path("homeassistant/generated/currencies.py").write_text( + black.format_str( + BASE.format(repr(active_currencies)[1:-1], repr(historic_currencies)[1:-1]), + mode=black.Mode(), + ) +) diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index a5d2223a3d2..da9fa2cc68d 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -1332,3 +1332,15 @@ def test_currency(): for value in ("EUR", "USD"): assert schema(value) + + +def test_historic_currency(): + """Test historic currency validator.""" + schema = vol.Schema(cv.historic_currency) + + for value in (None, "BTC", "EUR"): + with pytest.raises(vol.MultipleInvalid): + schema(value) + + for value in ("DEM", "NLG"): + assert schema(value) diff --git a/tests/test_config.py b/tests/test_config.py index 0a125d8f121..75ad227a641 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -28,7 +28,7 @@ from homeassistant.const import ( __version__, ) from homeassistant.core import ConfigSource, HomeAssistant, HomeAssistantError -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, issue_registry as ir import homeassistant.helpers.check_config as check_config from homeassistant.helpers.entity import Entity from homeassistant.loader import async_get_integration @@ -445,7 +445,7 @@ async def test_loading_configuration_from_storage_with_yaml_only(hass, hass_stor assert hass.config.config_source is ConfigSource.STORAGE -async def test_igration_and_updating_configuration(hass, hass_storage): +async def test_migration_and_updating_configuration(hass, hass_storage): """Test updating configuration stores the new configuration.""" core_data = { "data": { @@ -1205,3 +1205,17 @@ def test_identify_config_schema(domain, schema, expected): config_util._identify_config_schema(Mock(DOMAIN=domain, CONFIG_SCHEMA=schema)) == expected ) + + +def test_core_config_schema_historic_currency(hass): + """Test core config schema.""" + config_util.CORE_CONFIG_SCHEMA( + { + "currency": "LTT", + } + ) + + issue_registry = ir.async_get(hass) + issue = issue_registry.async_get_issue("homeassistant", "historic_currency") + assert issue + assert issue.translation_placeholders == {"currency": "LTT"}