diff --git a/homeassistant/components/config/core.py b/homeassistant/components/config/core.py index 3f665e475f0..d6d97b5caca 100644 --- a/homeassistant/components/config/core.py +++ b/homeassistant/components/config/core.py @@ -5,10 +5,9 @@ import voluptuous as vol from homeassistant.components import websocket_api from homeassistant.components.http import HomeAssistantView from homeassistant.config import async_check_ha_config_file -from homeassistant.const import CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.util import location +from homeassistant.util import location, unit_system async def async_setup(hass): @@ -41,7 +40,7 @@ class CheckConfigView(HomeAssistantView): vol.Optional("latitude"): cv.latitude, vol.Optional("longitude"): cv.longitude, vol.Optional("elevation"): int, - vol.Optional("unit_system"): cv.unit_system, + vol.Optional("unit_system"): unit_system.validate_unit_system, vol.Optional("location_name"): str, vol.Optional("time_zone"): cv.time_zone, vol.Optional("external_url"): vol.Any(cv.url_no_path, None), @@ -77,10 +76,14 @@ async def websocket_detect_config(hass, connection, msg): connection.send_result(msg["id"], info) return + # We don't want any integrations to use the name of the unit system + # so we are using the private attribute here if location_info.use_metric: - info["unit_system"] = CONF_UNIT_SYSTEM_METRIC + # pylint: disable-next=protected-access + info["unit_system"] = unit_system._CONF_UNIT_SYSTEM_METRIC else: - info["unit_system"] = CONF_UNIT_SYSTEM_IMPERIAL + # pylint: disable-next=protected-access + info["unit_system"] = unit_system._CONF_UNIT_SYSTEM_IMPERIAL if location_info.latitude: info["latitude"] = location_info.latitude diff --git a/homeassistant/config.py b/homeassistant/config.py index e71b0dcd726..ab3ad0eb5c1 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -44,7 +44,6 @@ from .const import ( CONF_TIME_ZONE, CONF_TYPE, CONF_UNIT_SYSTEM, - CONF_UNIT_SYSTEM_IMPERIAL, LEGACY_CONF_WHITELIST_EXTERNAL_DIRS, __version__, ) @@ -60,7 +59,7 @@ from .helpers.typing import ConfigType from .loader import Integration, IntegrationNotFound from .requirements import RequirementsNotFound, async_get_integration_with_requirements from .util.package import is_docker_env -from .util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM +from .util.unit_system import get_unit_system, validate_unit_system from .util.yaml import SECRET_YAML, Secrets, load_yaml _LOGGER = logging.getLogger(__name__) @@ -204,7 +203,7 @@ CORE_CONFIG_SCHEMA = vol.All( CONF_LONGITUDE: cv.longitude, CONF_ELEVATION: vol.Coerce(int), vol.Remove(CONF_TEMPERATURE_UNIT): cv.temperature_unit, - CONF_UNIT_SYSTEM: cv.unit_system, + CONF_UNIT_SYSTEM: validate_unit_system, CONF_TIME_ZONE: cv.time_zone, vol.Optional(CONF_INTERNAL_URL): cv.url, vol.Optional(CONF_EXTERNAL_URL): cv.url, @@ -602,10 +601,7 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: dict) -> Non hass.data[DATA_CUSTOMIZE] = EntityValues(cust_exact, cust_domain, cust_glob) if CONF_UNIT_SYSTEM in config: - if config[CONF_UNIT_SYSTEM] == CONF_UNIT_SYSTEM_IMPERIAL: - hac.units = IMPERIAL_SYSTEM - else: - hac.units = METRIC_SYSTEM + hac.units = get_unit_system(config[CONF_UNIT_SYSTEM]) def _log_pkg_error(package: str, component: str, config: dict, message: str) -> None: diff --git a/homeassistant/const.py b/homeassistant/const.py index c89eca63623..99af9974ea0 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -396,7 +396,9 @@ ATTR_ICON: Final = "icon" ATTR_UNIT_OF_MEASUREMENT: Final = "unit_of_measurement" CONF_UNIT_SYSTEM_METRIC: Final = "metric" +"""Deprecated: please use a local constant.""" CONF_UNIT_SYSTEM_IMPERIAL: Final = "imperial" +"""Deprecated: please use a local constant.""" # Electrical attributes ATTR_VOLTAGE: Final = "voltage" diff --git a/homeassistant/core.py b/homeassistant/core.py index 5a8ec07c1b9..872b4298c5d 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -49,7 +49,6 @@ from .const import ( ATTR_FRIENDLY_NAME, ATTR_SERVICE, ATTR_SERVICE_DATA, - CONF_UNIT_SYSTEM_IMPERIAL, EVENT_CALL_SERVICE, EVENT_CORE_CONFIG_UPDATE, EVENT_HOMEASSISTANT_CLOSE, @@ -82,7 +81,7 @@ from .util.async_ import ( ) from .util.read_only_dict import ReadOnlyDict from .util.timeout import TimeoutManager -from .util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem +from .util.unit_system import METRIC_SYSTEM, UnitSystem, get_unit_system # Typing imports that create a circular dependency if TYPE_CHECKING: @@ -1940,9 +1939,9 @@ class Config: if elevation is not None: self.elevation = elevation if unit_system is not None: - if unit_system == CONF_UNIT_SYSTEM_IMPERIAL: - self.units = IMPERIAL_SYSTEM - else: + try: + self.units = get_unit_system(unit_system) + except ValueError: self.units = METRIC_SYSTEM if location_name is not None: self.location_name = location_name @@ -2015,7 +2014,7 @@ class Config: "latitude": self.latitude, "longitude": self.longitude, "elevation": self.elevation, - # We don't want any components to use the name of the unit system + # We don't want any integrations to use the name of the unit system # so we are using the private attribute here "unit_system": self.units._name, # pylint: disable=protected-access "location_name": self.location_name, diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index f6e77ef0018..35191d77042 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -71,8 +71,6 @@ from homeassistant.const import ( CONF_TARGET, CONF_THEN, CONF_TIMEOUT, - CONF_UNIT_SYSTEM_IMPERIAL, - CONF_UNIT_SYSTEM_METRIC, CONF_UNTIL, CONF_VALUE_TEMPLATE, CONF_VARIABLES, @@ -588,11 +586,6 @@ def temperature_unit(value: Any) -> str: raise vol.Invalid("invalid temperature unit (expected C or F)") -unit_system = vol.All( - vol.Lower, vol.Any(CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL) -) - - def template(value: Any | None) -> template_helper.Template: """Validate a jinja2 template.""" if value is None: diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py index 5c1f6d647a1..8dad108ccbe 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -2,11 +2,12 @@ from __future__ import annotations from numbers import Number +from typing import Final + +import voluptuous as vol from homeassistant.const import ( ACCUMULATED_PRECIPITATION, - CONF_UNIT_SYSTEM_IMPERIAL, - CONF_UNIT_SYSTEM_METRIC, LENGTH, LENGTH_INCHES, LENGTH_KILOMETERS, @@ -41,6 +42,9 @@ from .unit_conversion import ( VolumeConverter, ) +_CONF_UNIT_SYSTEM_IMPERIAL: Final = "imperial" +_CONF_UNIT_SYSTEM_METRIC: Final = "metric" + LENGTH_UNITS = DistanceConverter.VALID_UNITS MASS_UNITS: set[str] = {MASS_POUNDS, MASS_OUNCES, MASS_KILOGRAMS, MASS_GRAMS} @@ -207,8 +211,21 @@ class UnitSystem: } +def get_unit_system(key: str) -> UnitSystem: + """Get unit system based on key.""" + if key == _CONF_UNIT_SYSTEM_IMPERIAL: + return IMPERIAL_SYSTEM + if key == _CONF_UNIT_SYSTEM_METRIC: + return METRIC_SYSTEM + raise ValueError(f"`{key}` is not a valid unit system key") + + +validate_unit_system = vol.All( + vol.Lower, vol.Any(_CONF_UNIT_SYSTEM_METRIC, _CONF_UNIT_SYSTEM_IMPERIAL) +) + METRIC_SYSTEM = UnitSystem( - CONF_UNIT_SYSTEM_METRIC, + _CONF_UNIT_SYSTEM_METRIC, TEMP_CELSIUS, LENGTH_KILOMETERS, SPEED_METERS_PER_SECOND, @@ -219,7 +236,7 @@ METRIC_SYSTEM = UnitSystem( ) IMPERIAL_SYSTEM = UnitSystem( - CONF_UNIT_SYSTEM_IMPERIAL, + _CONF_UNIT_SYSTEM_IMPERIAL, TEMP_FAHRENHEIT, LENGTH_MILES, SPEED_MILES_PER_HOUR, diff --git a/pylint/plugins/hass_imports.py b/pylint/plugins/hass_imports.py index 45deecfc02e..3d2f747ca7b 100644 --- a/pylint/plugins/hass_imports.py +++ b/pylint/plugins/hass_imports.py @@ -267,6 +267,10 @@ _OBSOLETE_IMPORT: dict[str, list[ObsoleteImportMatch]] = { reason="replaced by EntityCategory enum", constant=re.compile(r"^(ENTITY_CATEGORY_(\w*))|(ENTITY_CATEGORIES)$"), ), + ObsoleteImportMatch( + reason="replaced by local constants", + constant=re.compile(r"^(CONF_UNIT_SYSTEM_(\w*))$"), + ), ], "homeassistant.core": [ ObsoleteImportMatch( diff --git a/tests/util/test_unit_system.py b/tests/util/test_unit_system.py index 8c7a9cf2fc5..3019d6d0ff6 100644 --- a/tests/util/test_unit_system.py +++ b/tests/util/test_unit_system.py @@ -3,8 +3,6 @@ import pytest from homeassistant.const import ( ACCUMULATED_PRECIPITATION, - CONF_UNIT_SYSTEM_IMPERIAL, - CONF_UNIT_SYSTEM_METRIC, LENGTH, LENGTH_KILOMETERS, LENGTH_METERS, @@ -21,7 +19,14 @@ from homeassistant.const import ( WIND_SPEED, ) from homeassistant.exceptions import HomeAssistantError -from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem +from homeassistant.util.unit_system import ( + _CONF_UNIT_SYSTEM_IMPERIAL, + _CONF_UNIT_SYSTEM_METRIC, + IMPERIAL_SYSTEM, + METRIC_SYSTEM, + UnitSystem, + get_unit_system, +) SYSTEM_NAME = "TEST" INVALID_UNIT = "INVALID" @@ -317,8 +322,8 @@ def test_is_metric( @pytest.mark.parametrize( "unit_system, expected_name", [ - (METRIC_SYSTEM, CONF_UNIT_SYSTEM_METRIC), - (IMPERIAL_SYSTEM, CONF_UNIT_SYSTEM_IMPERIAL), + (METRIC_SYSTEM, _CONF_UNIT_SYSTEM_METRIC), + (IMPERIAL_SYSTEM, _CONF_UNIT_SYSTEM_IMPERIAL), ], ) def test_deprecated_name( @@ -330,3 +335,22 @@ def test_deprecated_name( "Detected code that accesses the `name` property of the unit system." in caplog.text ) + + +@pytest.mark.parametrize( + "key, expected_system", + [ + (_CONF_UNIT_SYSTEM_METRIC, METRIC_SYSTEM), + (_CONF_UNIT_SYSTEM_IMPERIAL, IMPERIAL_SYSTEM), + ], +) +def test_get_unit_system(key: str, expected_system: UnitSystem) -> None: + """Test get_unit_system.""" + assert get_unit_system(key) is expected_system + + +@pytest.mark.parametrize("key", [None, "", "invalid_custom"]) +def test_get_unit_system_invalid(key: str) -> None: + """Test get_unit_system with an invalid key.""" + with pytest.raises(ValueError, match=f"`{key}` is not a valid unit system key"): + _ = get_unit_system(key)