From d1d61aac03dfc888350bdaa3e3c4c3b1b613da54 Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Thu, 11 Nov 2021 01:26:48 -0800 Subject: [PATCH 01/15] Add native unit types for weather entities --- homeassistant/components/weather/__init__.py | 74 ++++++++++++++++++-- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 81d245c19bb829..b0636171032cb8 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -120,30 +120,72 @@ class WeatherEntity(Entity): _attr_wind_speed: float | None = None @property - def temperature(self) -> float | None: + def native_temperature(self) -> float | None: """Return the platform temperature.""" return self._attr_temperature @property - def temperature_unit(self) -> str: - """Return the unit of measurement.""" + def native_temperature_unit(self) -> str | None: + """Return the unit of measurement for temperature.""" return self._attr_temperature_unit + @final @property - def pressure(self) -> float | None: + def temperature(self) -> float | None: + """Return the platform temperature, after unit conversion.""" + temp = self.native_temperature + unit = self.native_temperature_unit + if temp is None or unit is None: + return temp + return self.hass.config.units.temperature(temp, unit) + + @final + @property + def temperature_unit(self) -> str: + """Return the unit of measurement for temperature, after unit conversion.""" + return self.hass.config.units.temperature_unit + + @property + def native_pressure(self) -> float | None: """Return the pressure.""" return self._attr_pressure + @property + def native_pressure_unit(self) -> str | None: + """Return the unit of measurement for pressure.""" + return None + + @final + @property + def pressure(self) -> float | None: + """Return the pressure, after unit conversion.""" + pressure = self.native_pressure + unit = self.native_pressure_unit + if pressure is None or unit is None: + return self.native_pressure + return self.hass.config.units.pressure(pressure, unit) + @property def humidity(self) -> float | None: """Return the humidity.""" return self._attr_humidity @property - def wind_speed(self) -> float | None: + def native_wind_speed(self) -> float | None: """Return the wind speed.""" return self._attr_wind_speed + @property + def native_wind_speed_unit(self) -> str | None: + """Return the unit of measurement for wind speed.""" + return None + + @final + @property + def wind_speed(self) -> float | None: + """Return the wind speed, after unit conversion.""" + return self.native_wind_speed + @property def wind_bearing(self) -> float | str | None: """Return the wind bearing.""" @@ -155,10 +197,30 @@ def ozone(self) -> float | None: return self._attr_ozone @property - def visibility(self) -> float | None: + def attribution(self) -> str | None: + """Return the attribution.""" + return self._attr_attribution + + @property + def native_visibility(self) -> float | None: """Return the visibility.""" return self._attr_visibility + @property + def native_visibility_unit(self) -> str | None: + """Return the unit of measurement for visibility.""" + return None + + @final + @property + def visibility(self) -> float | None: + """Return the visibility, after unit conversion.""" + visibility = self.native_visibility + unit = self.native_visibility_unit + if visibility is None or unit is None: + return self.native_visibility + return self.hass.config.units.length(visibility, unit) + @property def forecast(self) -> list[Forecast] | None: """Return the forecast.""" From d00c0e1a72cd0f4dc8ec1a4a2d523d1ef4ff0ca1 Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Thu, 11 Nov 2021 13:01:33 -0800 Subject: [PATCH 02/15] Update weatherentity and change precision in climacell test --- homeassistant/components/weather/__init__.py | 25 ++-- tests/components/climacell/test_weather.py | 120 +++++++++---------- 2 files changed, 73 insertions(+), 72 deletions(-) diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index b0636171032cb8..44b026e130fd55 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -112,12 +112,15 @@ class WeatherEntity(Entity): _attr_ozone: float | None = None _attr_precision: float _attr_pressure: float | None = None + _attr_pressure_unit: str | None = None _attr_state: None = None _attr_temperature_unit: str _attr_temperature: float | None _attr_visibility: float | None = None + _attr_visibility_unit: str | None = None _attr_wind_bearing: float | str | None = None _attr_wind_speed: float | None = None + _attr_wind_speed_unit: str | None = None @property def native_temperature(self) -> float | None: @@ -129,7 +132,6 @@ def native_temperature_unit(self) -> str | None: """Return the unit of measurement for temperature.""" return self._attr_temperature_unit - @final @property def temperature(self) -> float | None: """Return the platform temperature, after unit conversion.""" @@ -137,9 +139,8 @@ def temperature(self) -> float | None: unit = self.native_temperature_unit if temp is None or unit is None: return temp - return self.hass.config.units.temperature(temp, unit) + return show_temp(self.hass, temp, unit, self.precision) - @final @property def temperature_unit(self) -> str: """Return the unit of measurement for temperature, after unit conversion.""" @@ -153,9 +154,8 @@ def native_pressure(self) -> float | None: @property def native_pressure_unit(self) -> str | None: """Return the unit of measurement for pressure.""" - return None + return self._attr_pressure_unit - @final @property def pressure(self) -> float | None: """Return the pressure, after unit conversion.""" @@ -178,9 +178,8 @@ def native_wind_speed(self) -> float | None: @property def native_wind_speed_unit(self) -> str | None: """Return the unit of measurement for wind speed.""" - return None + return self._attr_wind_speed_unit - @final @property def wind_speed(self) -> float | None: """Return the wind speed, after unit conversion.""" @@ -209,9 +208,8 @@ def native_visibility(self) -> float | None: @property def native_visibility_unit(self) -> str | None: """Return the unit of measurement for visibility.""" - return None + return self._attr_visibility_unit - @final @property def visibility(self) -> float | None: """Return the visibility, after unit conversion.""" @@ -244,7 +242,10 @@ def state_attributes(self): data = {} if self.temperature is not None: data[ATTR_WEATHER_TEMPERATURE] = show_temp( - self.hass, self.temperature, self.temperature_unit, self.precision + self.hass, + self.temperature, + self.native_temperature_unit, + self.precision, ) if (humidity := self.humidity) is not None: @@ -272,14 +273,14 @@ def state_attributes(self): forecast_entry[ATTR_FORECAST_TEMP] = show_temp( self.hass, forecast_entry[ATTR_FORECAST_TEMP], - self.temperature_unit, + self.native_temperature_unit, self.precision, ) if ATTR_FORECAST_TEMP_LOW in forecast_entry: forecast_entry[ATTR_FORECAST_TEMP_LOW] = show_temp( self.hass, forecast_entry[ATTR_FORECAST_TEMP_LOW], - self.temperature_unit, + self.native_temperature_unit, self.precision, ) forecast.append(forecast_entry) diff --git a/tests/components/climacell/test_weather.py b/tests/components/climacell/test_weather.py index e3e15889e4474b..c92ec42dc82e4c 100644 --- a/tests/components/climacell/test_weather.py +++ b/tests/components/climacell/test_weather.py @@ -101,127 +101,127 @@ async def test_v3_weather( ATTR_FORECAST_TIME: "2021-03-07T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 7, - ATTR_FORECAST_TEMP_LOW: -5, + ATTR_FORECAST_TEMP: 7.2, + ATTR_FORECAST_TEMP_LOW: -4.7, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-08T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 10, - ATTR_FORECAST_TEMP_LOW: -4, + ATTR_FORECAST_TEMP: 9.7, + ATTR_FORECAST_TEMP_LOW: -4.0, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-09T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 19, - ATTR_FORECAST_TEMP_LOW: 0, + ATTR_FORECAST_TEMP: 19.4, + ATTR_FORECAST_TEMP_LOW: -0.3, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-10T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 18, - ATTR_FORECAST_TEMP_LOW: 3, + ATTR_FORECAST_TEMP: 18.5, + ATTR_FORECAST_TEMP_LOW: 3.0, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-11T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 5, - ATTR_FORECAST_TEMP: 20, - ATTR_FORECAST_TEMP_LOW: 9, + ATTR_FORECAST_TEMP: 19.7, + ATTR_FORECAST_TEMP_LOW: 9.3, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-12T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0.0457, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 25, - ATTR_FORECAST_TEMP: 20, - ATTR_FORECAST_TEMP_LOW: 12, + ATTR_FORECAST_TEMP: 19.9, + ATTR_FORECAST_TEMP_LOW: 12.1, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-13T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 25, - ATTR_FORECAST_TEMP: 16, - ATTR_FORECAST_TEMP_LOW: 7, + ATTR_FORECAST_TEMP: 15.8, + ATTR_FORECAST_TEMP_LOW: 7.5, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_RAINY, ATTR_FORECAST_TIME: "2021-03-14T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 1.0744, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 75, - ATTR_FORECAST_TEMP: 6, - ATTR_FORECAST_TEMP_LOW: 3, + ATTR_FORECAST_TEMP: 6.4, + ATTR_FORECAST_TEMP_LOW: 3.2, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_SNOWY, ATTR_FORECAST_TIME: "2021-03-15T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 7.3050, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 95, - ATTR_FORECAST_TEMP: 1, - ATTR_FORECAST_TEMP_LOW: 0, + ATTR_FORECAST_TEMP: 1.2, + ATTR_FORECAST_TEMP_LOW: 0.2, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-16T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0.0051, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 5, - ATTR_FORECAST_TEMP: 6, - ATTR_FORECAST_TEMP_LOW: -2, + ATTR_FORECAST_TEMP: 6.1, + ATTR_FORECAST_TEMP_LOW: -1.6, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-17T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 11, - ATTR_FORECAST_TEMP_LOW: 1, + ATTR_FORECAST_TEMP: 11.3, + ATTR_FORECAST_TEMP_LOW: 1.3, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-18T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 5, - ATTR_FORECAST_TEMP: 12, - ATTR_FORECAST_TEMP_LOW: 6, + ATTR_FORECAST_TEMP: 12.3, + ATTR_FORECAST_TEMP_LOW: 5.6, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-19T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0.1778, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 45, - ATTR_FORECAST_TEMP: 9, - ATTR_FORECAST_TEMP_LOW: 5, + ATTR_FORECAST_TEMP: 9.4, + ATTR_FORECAST_TEMP_LOW: 4.7, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_RAINY, ATTR_FORECAST_TIME: "2021-03-20T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 1.2319, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 55, - ATTR_FORECAST_TEMP: 5, - ATTR_FORECAST_TEMP_LOW: 3, + ATTR_FORECAST_TEMP: 5.0, + ATTR_FORECAST_TEMP_LOW: 3.1, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-21T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0.0432, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 20, - ATTR_FORECAST_TEMP: 7, - ATTR_FORECAST_TEMP_LOW: 1, + ATTR_FORECAST_TEMP: 6.8, + ATTR_FORECAST_TEMP_LOW: 0.9, }, ] assert weather_state.attributes[ATTR_FRIENDLY_NAME] == "ClimaCell - Daily" assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 24 assert weather_state.attributes[ATTR_WEATHER_OZONE] == 52.625 assert weather_state.attributes[ATTR_WEATHER_PRESSURE] == 1028.1246 - assert weather_state.attributes[ATTR_WEATHER_TEMPERATURE] == 7 + assert weather_state.attributes[ATTR_WEATHER_TEMPERATURE] == 6.6 assert weather_state.attributes[ATTR_WEATHER_VISIBILITY] == 9.9940 assert weather_state.attributes[ATTR_WEATHER_WIND_BEARING] == 320.31 assert weather_state.attributes[ATTR_WEATHER_WIND_SPEED] == 14.6289 @@ -244,8 +244,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-07T11:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 8, - ATTR_FORECAST_TEMP_LOW: -3, + ATTR_FORECAST_TEMP: 7.7, + ATTR_FORECAST_TEMP_LOW: -3.3, ATTR_FORECAST_WIND_BEARING: 239.6, ATTR_FORECAST_WIND_SPEED: 15.2727, }, @@ -254,8 +254,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-08T11:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 10, - ATTR_FORECAST_TEMP_LOW: -3, + ATTR_FORECAST_TEMP: 9.7, + ATTR_FORECAST_TEMP_LOW: -3.2, ATTR_FORECAST_WIND_BEARING: 262.82, ATTR_FORECAST_WIND_SPEED: 11.6517, }, @@ -264,8 +264,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-09T11:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 19, - ATTR_FORECAST_TEMP_LOW: 0, + ATTR_FORECAST_TEMP: 19.4, + ATTR_FORECAST_TEMP_LOW: -0.3, ATTR_FORECAST_WIND_BEARING: 229.3, ATTR_FORECAST_WIND_SPEED: 11.3459, }, @@ -274,8 +274,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-10T11:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 18, - ATTR_FORECAST_TEMP_LOW: 3, + ATTR_FORECAST_TEMP: 18.5, + ATTR_FORECAST_TEMP_LOW: 3.0, ATTR_FORECAST_WIND_BEARING: 149.91, ATTR_FORECAST_WIND_SPEED: 17.1234, }, @@ -284,8 +284,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-11T11:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 19, - ATTR_FORECAST_TEMP_LOW: 9, + ATTR_FORECAST_TEMP: 19.0, + ATTR_FORECAST_TEMP_LOW: 9.0, ATTR_FORECAST_WIND_BEARING: 210.45, ATTR_FORECAST_WIND_SPEED: 25.2506, }, @@ -294,8 +294,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-12T11:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0.1219, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 25, - ATTR_FORECAST_TEMP: 20, - ATTR_FORECAST_TEMP_LOW: 12, + ATTR_FORECAST_TEMP: 19.9, + ATTR_FORECAST_TEMP_LOW: 12.1, ATTR_FORECAST_WIND_BEARING: 217.98, ATTR_FORECAST_WIND_SPEED: 19.7949, }, @@ -304,8 +304,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-13T11:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 25, - ATTR_FORECAST_TEMP: 12, - ATTR_FORECAST_TEMP_LOW: 6, + ATTR_FORECAST_TEMP: 12.5, + ATTR_FORECAST_TEMP_LOW: 6.1, ATTR_FORECAST_WIND_BEARING: 58.79, ATTR_FORECAST_WIND_SPEED: 15.6428, }, @@ -314,8 +314,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-14T10:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 23.9573, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 95, - ATTR_FORECAST_TEMP: 6, - ATTR_FORECAST_TEMP_LOW: 1, + ATTR_FORECAST_TEMP: 6.1, + ATTR_FORECAST_TEMP_LOW: 0.8, ATTR_FORECAST_WIND_BEARING: 70.25, ATTR_FORECAST_WIND_SPEED: 26.1518, }, @@ -324,8 +324,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-15T10:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 1.4630, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 55, - ATTR_FORECAST_TEMP: 6, - ATTR_FORECAST_TEMP_LOW: -1, + ATTR_FORECAST_TEMP: 6.5, + ATTR_FORECAST_TEMP_LOW: -1.5, ATTR_FORECAST_WIND_BEARING: 84.47, ATTR_FORECAST_WIND_SPEED: 25.5725, }, @@ -334,8 +334,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-16T10:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 6, - ATTR_FORECAST_TEMP_LOW: -2, + ATTR_FORECAST_TEMP: 6.1, + ATTR_FORECAST_TEMP_LOW: -1.6, ATTR_FORECAST_WIND_BEARING: 103.85, ATTR_FORECAST_WIND_SPEED: 10.7987, }, @@ -344,8 +344,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-17T10:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 11, - ATTR_FORECAST_TEMP_LOW: 1, + ATTR_FORECAST_TEMP: 11.3, + ATTR_FORECAST_TEMP_LOW: 1.3, ATTR_FORECAST_WIND_BEARING: 145.41, ATTR_FORECAST_WIND_SPEED: 11.6999, }, @@ -354,8 +354,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-18T10:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 10, - ATTR_FORECAST_TEMP: 12, - ATTR_FORECAST_TEMP_LOW: 5, + ATTR_FORECAST_TEMP: 12.3, + ATTR_FORECAST_TEMP_LOW: 5.2, ATTR_FORECAST_WIND_BEARING: 62.99, ATTR_FORECAST_WIND_SPEED: 10.5895, }, @@ -364,8 +364,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-19T10:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 2.9261, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 55, - ATTR_FORECAST_TEMP: 9, - ATTR_FORECAST_TEMP_LOW: 4, + ATTR_FORECAST_TEMP: 9.4, + ATTR_FORECAST_TEMP_LOW: 4.1, ATTR_FORECAST_WIND_BEARING: 68.54, ATTR_FORECAST_WIND_SPEED: 22.3860, }, @@ -374,8 +374,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-20T10:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 1.2192, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 33.3, - ATTR_FORECAST_TEMP: 5, - ATTR_FORECAST_TEMP_LOW: 2, + ATTR_FORECAST_TEMP: 4.5, + ATTR_FORECAST_TEMP_LOW: 1.7, ATTR_FORECAST_WIND_BEARING: 56.98, ATTR_FORECAST_WIND_SPEED: 27.9221, }, @@ -384,7 +384,7 @@ async def test_v4_weather( assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 23 assert weather_state.attributes[ATTR_WEATHER_OZONE] == 46.53 assert weather_state.attributes[ATTR_WEATHER_PRESSURE] == 1027.7691 - assert weather_state.attributes[ATTR_WEATHER_TEMPERATURE] == 7 + assert weather_state.attributes[ATTR_WEATHER_TEMPERATURE] == 6.7 assert weather_state.attributes[ATTR_WEATHER_VISIBILITY] == 13.1162 assert weather_state.attributes[ATTR_WEATHER_WIND_BEARING] == 315.14 assert weather_state.attributes[ATTR_WEATHER_WIND_SPEED] == 15.0152 From 1b018e48a188d8479109e89a0eec20c8a52ef6f7 Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Thu, 11 Nov 2021 13:04:19 -0800 Subject: [PATCH 03/15] Move weather test to demo tests --- tests/components/{weather => demo}/test_weather.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/components/{weather => demo}/test_weather.py (98%) diff --git a/tests/components/weather/test_weather.py b/tests/components/demo/test_weather.py similarity index 98% rename from tests/components/weather/test_weather.py rename to tests/components/demo/test_weather.py index 3057532668a3a0..c4ae8fcd79c6c2 100644 --- a/tests/components/weather/test_weather.py +++ b/tests/components/demo/test_weather.py @@ -1,4 +1,4 @@ -"""The tests for the Weather component.""" +"""The tests for the demo weather component.""" from homeassistant.components import weather from homeassistant.components.weather import ( ATTR_FORECAST, From 12022b51a5bcbf72c4db8899d759201bd241ffdd Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Thu, 11 Nov 2021 13:39:46 -0800 Subject: [PATCH 04/15] Add weather test for temperature conversion --- homeassistant/components/weather/__init__.py | 5 +- tests/components/weather/test_init.py | 54 ++++++++++ .../custom_components/test/weather.py | 99 +++++++++++++++++++ 3 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 tests/components/weather/test_init.py create mode 100644 tests/testing_config/custom_components/test/weather.py diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 44b026e130fd55..bf8f71b7a0c879 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -139,7 +139,7 @@ def temperature(self) -> float | None: unit = self.native_temperature_unit if temp is None or unit is None: return temp - return show_temp(self.hass, temp, unit, self.precision) + return self.hass.config.units.temperature(temp, unit) @property def temperature_unit(self) -> str: @@ -243,7 +243,8 @@ def state_attributes(self): if self.temperature is not None: data[ATTR_WEATHER_TEMPERATURE] = show_temp( self.hass, - self.temperature, + # Use native_temperature if defined, use temperature for old integrations + self.native_temperature or self.temperature, self.native_temperature_unit, self.precision, ) diff --git a/tests/components/weather/test_init.py b/tests/components/weather/test_init.py new file mode 100644 index 00000000000000..c6a6b8136ca3e0 --- /dev/null +++ b/tests/components/weather/test_init.py @@ -0,0 +1,54 @@ +"""The test for weather entity.""" +import pytest +from pytest import approx + +from homeassistant.components.weather import ( + ATTR_CONDITION_SUNNY, + ATTR_WEATHER_TEMPERATURE, +) +from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.setup import async_setup_component +from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM + + +@pytest.mark.parametrize( + "unit_system,native_unit,state_unit,native_value,state_value", + [ + (IMPERIAL_SYSTEM, TEMP_FAHRENHEIT, TEMP_FAHRENHEIT, 100, 100), + (IMPERIAL_SYSTEM, TEMP_CELSIUS, TEMP_FAHRENHEIT, 37.8, 100), + (METRIC_SYSTEM, TEMP_FAHRENHEIT, TEMP_CELSIUS, 100, 37.8), + (METRIC_SYSTEM, TEMP_CELSIUS, TEMP_CELSIUS, 37.8, 37.8), + ], +) +async def test_temperature_conversion( + hass, + enable_custom_integrations, + unit_system, + native_unit, + state_unit, + native_value, + state_value, +): + """Test temperature conversion.""" + hass.config.units = unit_system + platform = getattr(hass.components, "test.weather") + platform.init(empty=True) + platform.ENTITIES.append( + platform.MockWeather( + name="Test", + native_temperature=native_value, + native_temperature_unit=native_unit, + condition=ATTR_CONDITION_SUNNY, + ) + ) + + entity0 = platform.ENTITIES[0] + assert await async_setup_component( + hass, "weather", {"weather": {"platform": "test"}} + ) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert float(state.attributes[ATTR_WEATHER_TEMPERATURE]) == approx( + float(state_value) + ) diff --git a/tests/testing_config/custom_components/test/weather.py b/tests/testing_config/custom_components/test/weather.py new file mode 100644 index 00000000000000..72691a1958de7b --- /dev/null +++ b/tests/testing_config/custom_components/test/weather.py @@ -0,0 +1,99 @@ +""" +Provide a mock sensor platform. + +Call init before using it in your tests to ensure clean test data. +""" +from __future__ import annotations + +from homeassistant.components.weather import Forecast, WeatherEntity + +from tests.common import MockEntity + +ENTITIES = [] + + +def init(empty=False): + """Initialize the platform with entities.""" + global ENTITIES + ENTITIES = [] if empty else [MockWeather()] + + +async def async_setup_platform( + hass, config, async_add_entities_callback, discovery_info=None +): + """Return mock entities.""" + async_add_entities_callback(ENTITIES) + + +class MockWeather(MockEntity, WeatherEntity): + """Mock weather class.""" + + @property + def native_temperature(self) -> float | None: + """Return the platform temperature.""" + return self._handle("native_temperature") + + @property + def native_temperature_unit(self) -> str | None: + """Return the unit of measurement for temperature.""" + return self._handle("native_temperature_unit") + + @property + def native_pressure(self) -> float | None: + """Return the pressure.""" + return self._handle("native_pressure") + + @property + def native_pressure_unit(self) -> str | None: + """Return the unit of measurement for pressure.""" + return self._handle("native_pressure_unit") + + @property + def humidity(self) -> float | None: + """Return the humidity.""" + return self._handle("humidity") + + @property + def native_wind_speed(self) -> float | None: + """Return the wind speed.""" + return self._handle("native_wind_speed") + + @property + def native_wind_speed_unit(self) -> str | None: + """Return the unit of measurement for wind speed.""" + return self._handle("native_wind_speed_unit") + + @property + def wind_bearing(self) -> float | str | None: + """Return the wind bearing.""" + return self._handle("wind_bearing") + + @property + def ozone(self) -> float | None: + """Return the ozone level.""" + return self._handle("ozone") + + @property + def attribution(self) -> str | None: + """Return the attribution.""" + return self._handle("attribution") + + @property + def native_visibility(self) -> float | None: + """Return the visibility.""" + return self._handle("native_visibility") + + @property + def native_visibility_unit(self) -> str | None: + """Return the unit of measurement for visibility.""" + return self._handle("native_visibility_unit") + + @property + def forecast(self) -> list[Forecast] | None: + """Return the forecast.""" + return self._handle("forecast") + + @property + def condition(self) -> str | None: + """Return the current condition.""" + return self._handle("condition") From 6cf7cd2c6a18fdac24131593391a12e6359072e8 Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Thu, 11 Nov 2021 14:04:12 -0800 Subject: [PATCH 05/15] Add more unit conversion tests --- tests/components/weather/test_init.py | 116 ++++++++++++++++++++++++-- 1 file changed, 107 insertions(+), 9 deletions(-) diff --git a/tests/components/weather/test_init.py b/tests/components/weather/test_init.py index c6a6b8136ca3e0..4eeb84c2b72241 100644 --- a/tests/components/weather/test_init.py +++ b/tests/components/weather/test_init.py @@ -4,20 +4,33 @@ from homeassistant.components.weather import ( ATTR_CONDITION_SUNNY, + ATTR_WEATHER_PRESSURE, ATTR_WEATHER_TEMPERATURE, + ATTR_WEATHER_VISIBILITY, +) +from homeassistant.const import ( + LENGTH_KILOMETERS, + LENGTH_MILES, + PRESSURE_INHG, + PRESSURE_PA, + PRESSURE_PSI, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, ) -from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.setup import async_setup_component +from homeassistant.util.distance import convert as convert_distance +from homeassistant.util.pressure import convert as convert_pressure +from homeassistant.util.temperature import convert as convert_temperature from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM @pytest.mark.parametrize( - "unit_system,native_unit,state_unit,native_value,state_value", + "unit_system,native_unit,native_value,state_unit", [ - (IMPERIAL_SYSTEM, TEMP_FAHRENHEIT, TEMP_FAHRENHEIT, 100, 100), - (IMPERIAL_SYSTEM, TEMP_CELSIUS, TEMP_FAHRENHEIT, 37.8, 100), - (METRIC_SYSTEM, TEMP_FAHRENHEIT, TEMP_CELSIUS, 100, 37.8), - (METRIC_SYSTEM, TEMP_CELSIUS, TEMP_CELSIUS, 37.8, 37.8), + (IMPERIAL_SYSTEM, TEMP_FAHRENHEIT, 100, TEMP_FAHRENHEIT), + (IMPERIAL_SYSTEM, TEMP_CELSIUS, 38, TEMP_FAHRENHEIT), + (METRIC_SYSTEM, TEMP_FAHRENHEIT, 100, TEMP_CELSIUS), + (METRIC_SYSTEM, TEMP_CELSIUS, 38, TEMP_CELSIUS), ], ) async def test_temperature_conversion( @@ -25,9 +38,8 @@ async def test_temperature_conversion( enable_custom_integrations, unit_system, native_unit, - state_unit, native_value, - state_value, + state_unit, ): """Test temperature conversion.""" hass.config.units = unit_system @@ -50,5 +62,91 @@ async def test_temperature_conversion( state = hass.states.get(entity0.entity_id) assert float(state.attributes[ATTR_WEATHER_TEMPERATURE]) == approx( - float(state_value) + convert_temperature(native_value, native_unit, state_unit), rel=0.1 + ) + + +@pytest.mark.parametrize( + "unit_system,native_unit,native_value,state_unit", + [ + (IMPERIAL_SYSTEM, PRESSURE_INHG, 30, PRESSURE_PSI), + (METRIC_SYSTEM, PRESSURE_INHG, 30, PRESSURE_PA), + ], +) +async def test_pressure_conversion( + hass, + enable_custom_integrations, + unit_system, + native_unit, + native_value, + state_unit, +): + """Test temperature conversion.""" + hass.config.units = unit_system + platform = getattr(hass.components, "test.weather") + platform.init(empty=True) + platform.ENTITIES.append( + platform.MockWeather( + name="Test", + native_temperature=None, + native_temperature_unit=None, + native_pressure=native_value, + native_pressure_unit=native_unit, + condition=ATTR_CONDITION_SUNNY, + ) + ) + + entity0 = platform.ENTITIES[0] + assert await async_setup_component( + hass, "weather", {"weather": {"platform": "test"}} + ) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == approx( + convert_pressure(native_value, native_unit, state_unit) + ) + + +@pytest.mark.parametrize( + "unit_system,native_unit,native_value,state_unit", + [ + (IMPERIAL_SYSTEM, LENGTH_MILES, 30, LENGTH_MILES), + (IMPERIAL_SYSTEM, LENGTH_KILOMETERS, 30, LENGTH_MILES), + (METRIC_SYSTEM, LENGTH_KILOMETERS, 30, LENGTH_KILOMETERS), + (METRIC_SYSTEM, LENGTH_MILES, 30, LENGTH_KILOMETERS), + ], +) +async def test_visibility_conversion( + hass, + enable_custom_integrations, + unit_system, + native_unit, + native_value, + state_unit, +): + """Test temperature conversion.""" + hass.config.units = unit_system + platform = getattr(hass.components, "test.weather") + platform.init(empty=True) + platform.ENTITIES.append( + platform.MockWeather( + name="Test", + native_temperature=None, + native_temperature_unit=None, + native_visibility=native_value, + native_visibility_unit=native_unit, + condition=ATTR_CONDITION_SUNNY, + ) + ) + + entity0 = platform.ENTITIES[0] + assert await async_setup_component( + hass, "weather", {"weather": {"platform": "test"}} + ) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == approx( + convert_distance(native_value, native_unit, state_unit) ) From e63952425172ae42aff9b04faa54abe93caa73d0 Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Thu, 11 Nov 2021 14:22:37 -0800 Subject: [PATCH 06/15] Remove extra native_ methods --- homeassistant/components/weather/__init__.py | 66 +++++-------------- tests/components/weather/test_init.py | 20 +++--- .../custom_components/test/weather.py | 32 ++++----- 3 files changed, 42 insertions(+), 76 deletions(-) diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index bf8f71b7a0c879..4f73f7d05be9ab 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -123,68 +123,40 @@ class WeatherEntity(Entity): _attr_wind_speed_unit: str | None = None @property - def native_temperature(self) -> float | None: + def temperature(self) -> float | None: """Return the platform temperature.""" return self._attr_temperature @property - def native_temperature_unit(self) -> str | None: + def temperature_unit(self) -> str | None: """Return the unit of measurement for temperature.""" return self._attr_temperature_unit @property - def temperature(self) -> float | None: - """Return the platform temperature, after unit conversion.""" - temp = self.native_temperature - unit = self.native_temperature_unit - if temp is None or unit is None: - return temp - return self.hass.config.units.temperature(temp, unit) - - @property - def temperature_unit(self) -> str: - """Return the unit of measurement for temperature, after unit conversion.""" - return self.hass.config.units.temperature_unit - - @property - def native_pressure(self) -> float | None: + def pressure(self) -> float | None: """Return the pressure.""" return self._attr_pressure @property - def native_pressure_unit(self) -> str | None: + def pressure_unit(self) -> str | None: """Return the unit of measurement for pressure.""" return self._attr_pressure_unit - @property - def pressure(self) -> float | None: - """Return the pressure, after unit conversion.""" - pressure = self.native_pressure - unit = self.native_pressure_unit - if pressure is None or unit is None: - return self.native_pressure - return self.hass.config.units.pressure(pressure, unit) - @property def humidity(self) -> float | None: """Return the humidity.""" return self._attr_humidity @property - def native_wind_speed(self) -> float | None: + def wind_speed(self) -> float | None: """Return the wind speed.""" return self._attr_wind_speed @property - def native_wind_speed_unit(self) -> str | None: + def wind_speed_unit(self) -> str | None: """Return the unit of measurement for wind speed.""" return self._attr_wind_speed_unit - @property - def wind_speed(self) -> float | None: - """Return the wind speed, after unit conversion.""" - return self.native_wind_speed - @property def wind_bearing(self) -> float | str | None: """Return the wind bearing.""" @@ -201,24 +173,15 @@ def attribution(self) -> str | None: return self._attr_attribution @property - def native_visibility(self) -> float | None: + def visibility(self) -> float | None: """Return the visibility.""" return self._attr_visibility @property - def native_visibility_unit(self) -> str | None: + def visibility_unit(self) -> str | None: """Return the unit of measurement for visibility.""" return self._attr_visibility_unit - @property - def visibility(self) -> float | None: - """Return the visibility, after unit conversion.""" - visibility = self.native_visibility - unit = self.native_visibility_unit - if visibility is None or unit is None: - return self.native_visibility - return self.hass.config.units.length(visibility, unit) - @property def forecast(self) -> list[Forecast] | None: """Return the forecast.""" @@ -243,9 +206,8 @@ def state_attributes(self): if self.temperature is not None: data[ATTR_WEATHER_TEMPERATURE] = show_temp( self.hass, - # Use native_temperature if defined, use temperature for old integrations - self.native_temperature or self.temperature, - self.native_temperature_unit, + self.temperature, + self.temperature_unit, self.precision, ) @@ -256,6 +218,8 @@ def state_attributes(self): data[ATTR_WEATHER_OZONE] = ozone if (pressure := self.pressure) is not None: + if (unit := self.pressure_unit) is not None: + pressure = self.hass.config.units.pressure(pressure, unit) data[ATTR_WEATHER_PRESSURE] = pressure if (wind_bearing := self.wind_bearing) is not None: @@ -265,6 +229,8 @@ def state_attributes(self): data[ATTR_WEATHER_WIND_SPEED] = wind_speed if (visibility := self.visibility) is not None: + if (unit := self.visibility_unit) is not None: + visibility = self.hass.config.units.length(visibility, unit) data[ATTR_WEATHER_VISIBILITY] = visibility if self.forecast is not None: @@ -274,14 +240,14 @@ def state_attributes(self): forecast_entry[ATTR_FORECAST_TEMP] = show_temp( self.hass, forecast_entry[ATTR_FORECAST_TEMP], - self.native_temperature_unit, + self.temperature_unit, self.precision, ) if ATTR_FORECAST_TEMP_LOW in forecast_entry: forecast_entry[ATTR_FORECAST_TEMP_LOW] = show_temp( self.hass, forecast_entry[ATTR_FORECAST_TEMP_LOW], - self.native_temperature_unit, + self.temperature_unit, self.precision, ) forecast.append(forecast_entry) diff --git a/tests/components/weather/test_init.py b/tests/components/weather/test_init.py index 4eeb84c2b72241..8769c2e89ec543 100644 --- a/tests/components/weather/test_init.py +++ b/tests/components/weather/test_init.py @@ -48,8 +48,8 @@ async def test_temperature_conversion( platform.ENTITIES.append( platform.MockWeather( name="Test", - native_temperature=native_value, - native_temperature_unit=native_unit, + temperature=native_value, + temperature_unit=native_unit, condition=ATTR_CONDITION_SUNNY, ) ) @@ -88,10 +88,10 @@ async def test_pressure_conversion( platform.ENTITIES.append( platform.MockWeather( name="Test", - native_temperature=None, - native_temperature_unit=None, - native_pressure=native_value, - native_pressure_unit=native_unit, + temperature=None, + temperature_unit=None, + pressure=native_value, + pressure_unit=native_unit, condition=ATTR_CONDITION_SUNNY, ) ) @@ -132,10 +132,10 @@ async def test_visibility_conversion( platform.ENTITIES.append( platform.MockWeather( name="Test", - native_temperature=None, - native_temperature_unit=None, - native_visibility=native_value, - native_visibility_unit=native_unit, + temperature=None, + temperature_unit=None, + visibility=native_value, + visibility_unit=native_unit, condition=ATTR_CONDITION_SUNNY, ) ) diff --git a/tests/testing_config/custom_components/test/weather.py b/tests/testing_config/custom_components/test/weather.py index 72691a1958de7b..ec8cda69a888c4 100644 --- a/tests/testing_config/custom_components/test/weather.py +++ b/tests/testing_config/custom_components/test/weather.py @@ -29,24 +29,24 @@ class MockWeather(MockEntity, WeatherEntity): """Mock weather class.""" @property - def native_temperature(self) -> float | None: + def temperature(self) -> float | None: """Return the platform temperature.""" - return self._handle("native_temperature") + return self._handle("temperature") @property - def native_temperature_unit(self) -> str | None: + def temperature_unit(self) -> str | None: """Return the unit of measurement for temperature.""" - return self._handle("native_temperature_unit") + return self._handle("temperature_unit") @property - def native_pressure(self) -> float | None: + def pressure(self) -> float | None: """Return the pressure.""" - return self._handle("native_pressure") + return self._handle("pressure") @property - def native_pressure_unit(self) -> str | None: + def pressure_unit(self) -> str | None: """Return the unit of measurement for pressure.""" - return self._handle("native_pressure_unit") + return self._handle("pressure_unit") @property def humidity(self) -> float | None: @@ -54,14 +54,14 @@ def humidity(self) -> float | None: return self._handle("humidity") @property - def native_wind_speed(self) -> float | None: + def wind_speed(self) -> float | None: """Return the wind speed.""" - return self._handle("native_wind_speed") + return self._handle("wind_speed") @property - def native_wind_speed_unit(self) -> str | None: + def wind_speed_unit(self) -> str | None: """Return the unit of measurement for wind speed.""" - return self._handle("native_wind_speed_unit") + return self._handle("wind_speed_unit") @property def wind_bearing(self) -> float | str | None: @@ -79,14 +79,14 @@ def attribution(self) -> str | None: return self._handle("attribution") @property - def native_visibility(self) -> float | None: + def visibility(self) -> float | None: """Return the visibility.""" - return self._handle("native_visibility") + return self._handle("visibility") @property - def native_visibility_unit(self) -> str | None: + def visibility_unit(self) -> str | None: """Return the unit of measurement for visibility.""" - return self._handle("native_visibility_unit") + return self._handle("visibility_unit") @property def forecast(self) -> list[Forecast] | None: From 9e79fe79c4d047323dfdf64e2c500196172e8fa9 Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Thu, 11 Nov 2021 14:30:39 -0800 Subject: [PATCH 07/15] Remove extra properties and save precision change for another PR --- homeassistant/components/weather/__init__.py | 7 +- tests/components/climacell/test_weather.py | 120 +++++++++--------- .../custom_components/test/weather.py | 5 - 3 files changed, 61 insertions(+), 71 deletions(-) diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 4f73f7d05be9ab..0f639054f1a016 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -128,7 +128,7 @@ def temperature(self) -> float | None: return self._attr_temperature @property - def temperature_unit(self) -> str | None: + def temperature_unit(self) -> str: """Return the unit of measurement for temperature.""" return self._attr_temperature_unit @@ -167,11 +167,6 @@ def ozone(self) -> float | None: """Return the ozone level.""" return self._attr_ozone - @property - def attribution(self) -> str | None: - """Return the attribution.""" - return self._attr_attribution - @property def visibility(self) -> float | None: """Return the visibility.""" diff --git a/tests/components/climacell/test_weather.py b/tests/components/climacell/test_weather.py index c92ec42dc82e4c..e3e15889e4474b 100644 --- a/tests/components/climacell/test_weather.py +++ b/tests/components/climacell/test_weather.py @@ -101,127 +101,127 @@ async def test_v3_weather( ATTR_FORECAST_TIME: "2021-03-07T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 7.2, - ATTR_FORECAST_TEMP_LOW: -4.7, + ATTR_FORECAST_TEMP: 7, + ATTR_FORECAST_TEMP_LOW: -5, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-08T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 9.7, - ATTR_FORECAST_TEMP_LOW: -4.0, + ATTR_FORECAST_TEMP: 10, + ATTR_FORECAST_TEMP_LOW: -4, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-09T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 19.4, - ATTR_FORECAST_TEMP_LOW: -0.3, + ATTR_FORECAST_TEMP: 19, + ATTR_FORECAST_TEMP_LOW: 0, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-10T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 18.5, - ATTR_FORECAST_TEMP_LOW: 3.0, + ATTR_FORECAST_TEMP: 18, + ATTR_FORECAST_TEMP_LOW: 3, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-11T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 5, - ATTR_FORECAST_TEMP: 19.7, - ATTR_FORECAST_TEMP_LOW: 9.3, + ATTR_FORECAST_TEMP: 20, + ATTR_FORECAST_TEMP_LOW: 9, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-12T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0.0457, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 25, - ATTR_FORECAST_TEMP: 19.9, - ATTR_FORECAST_TEMP_LOW: 12.1, + ATTR_FORECAST_TEMP: 20, + ATTR_FORECAST_TEMP_LOW: 12, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-13T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 25, - ATTR_FORECAST_TEMP: 15.8, - ATTR_FORECAST_TEMP_LOW: 7.5, + ATTR_FORECAST_TEMP: 16, + ATTR_FORECAST_TEMP_LOW: 7, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_RAINY, ATTR_FORECAST_TIME: "2021-03-14T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 1.0744, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 75, - ATTR_FORECAST_TEMP: 6.4, - ATTR_FORECAST_TEMP_LOW: 3.2, + ATTR_FORECAST_TEMP: 6, + ATTR_FORECAST_TEMP_LOW: 3, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_SNOWY, ATTR_FORECAST_TIME: "2021-03-15T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 7.3050, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 95, - ATTR_FORECAST_TEMP: 1.2, - ATTR_FORECAST_TEMP_LOW: 0.2, + ATTR_FORECAST_TEMP: 1, + ATTR_FORECAST_TEMP_LOW: 0, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-16T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0.0051, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 5, - ATTR_FORECAST_TEMP: 6.1, - ATTR_FORECAST_TEMP_LOW: -1.6, + ATTR_FORECAST_TEMP: 6, + ATTR_FORECAST_TEMP_LOW: -2, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-17T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 11.3, - ATTR_FORECAST_TEMP_LOW: 1.3, + ATTR_FORECAST_TEMP: 11, + ATTR_FORECAST_TEMP_LOW: 1, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-18T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 5, - ATTR_FORECAST_TEMP: 12.3, - ATTR_FORECAST_TEMP_LOW: 5.6, + ATTR_FORECAST_TEMP: 12, + ATTR_FORECAST_TEMP_LOW: 6, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-19T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0.1778, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 45, - ATTR_FORECAST_TEMP: 9.4, - ATTR_FORECAST_TEMP_LOW: 4.7, + ATTR_FORECAST_TEMP: 9, + ATTR_FORECAST_TEMP_LOW: 5, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_RAINY, ATTR_FORECAST_TIME: "2021-03-20T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 1.2319, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 55, - ATTR_FORECAST_TEMP: 5.0, - ATTR_FORECAST_TEMP_LOW: 3.1, + ATTR_FORECAST_TEMP: 5, + ATTR_FORECAST_TEMP_LOW: 3, }, { ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY, ATTR_FORECAST_TIME: "2021-03-21T00:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0.0432, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 20, - ATTR_FORECAST_TEMP: 6.8, - ATTR_FORECAST_TEMP_LOW: 0.9, + ATTR_FORECAST_TEMP: 7, + ATTR_FORECAST_TEMP_LOW: 1, }, ] assert weather_state.attributes[ATTR_FRIENDLY_NAME] == "ClimaCell - Daily" assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 24 assert weather_state.attributes[ATTR_WEATHER_OZONE] == 52.625 assert weather_state.attributes[ATTR_WEATHER_PRESSURE] == 1028.1246 - assert weather_state.attributes[ATTR_WEATHER_TEMPERATURE] == 6.6 + assert weather_state.attributes[ATTR_WEATHER_TEMPERATURE] == 7 assert weather_state.attributes[ATTR_WEATHER_VISIBILITY] == 9.9940 assert weather_state.attributes[ATTR_WEATHER_WIND_BEARING] == 320.31 assert weather_state.attributes[ATTR_WEATHER_WIND_SPEED] == 14.6289 @@ -244,8 +244,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-07T11:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 7.7, - ATTR_FORECAST_TEMP_LOW: -3.3, + ATTR_FORECAST_TEMP: 8, + ATTR_FORECAST_TEMP_LOW: -3, ATTR_FORECAST_WIND_BEARING: 239.6, ATTR_FORECAST_WIND_SPEED: 15.2727, }, @@ -254,8 +254,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-08T11:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 9.7, - ATTR_FORECAST_TEMP_LOW: -3.2, + ATTR_FORECAST_TEMP: 10, + ATTR_FORECAST_TEMP_LOW: -3, ATTR_FORECAST_WIND_BEARING: 262.82, ATTR_FORECAST_WIND_SPEED: 11.6517, }, @@ -264,8 +264,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-09T11:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 19.4, - ATTR_FORECAST_TEMP_LOW: -0.3, + ATTR_FORECAST_TEMP: 19, + ATTR_FORECAST_TEMP_LOW: 0, ATTR_FORECAST_WIND_BEARING: 229.3, ATTR_FORECAST_WIND_SPEED: 11.3459, }, @@ -274,8 +274,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-10T11:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 18.5, - ATTR_FORECAST_TEMP_LOW: 3.0, + ATTR_FORECAST_TEMP: 18, + ATTR_FORECAST_TEMP_LOW: 3, ATTR_FORECAST_WIND_BEARING: 149.91, ATTR_FORECAST_WIND_SPEED: 17.1234, }, @@ -284,8 +284,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-11T11:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 19.0, - ATTR_FORECAST_TEMP_LOW: 9.0, + ATTR_FORECAST_TEMP: 19, + ATTR_FORECAST_TEMP_LOW: 9, ATTR_FORECAST_WIND_BEARING: 210.45, ATTR_FORECAST_WIND_SPEED: 25.2506, }, @@ -294,8 +294,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-12T11:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0.1219, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 25, - ATTR_FORECAST_TEMP: 19.9, - ATTR_FORECAST_TEMP_LOW: 12.1, + ATTR_FORECAST_TEMP: 20, + ATTR_FORECAST_TEMP_LOW: 12, ATTR_FORECAST_WIND_BEARING: 217.98, ATTR_FORECAST_WIND_SPEED: 19.7949, }, @@ -304,8 +304,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-13T11:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 25, - ATTR_FORECAST_TEMP: 12.5, - ATTR_FORECAST_TEMP_LOW: 6.1, + ATTR_FORECAST_TEMP: 12, + ATTR_FORECAST_TEMP_LOW: 6, ATTR_FORECAST_WIND_BEARING: 58.79, ATTR_FORECAST_WIND_SPEED: 15.6428, }, @@ -314,8 +314,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-14T10:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 23.9573, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 95, - ATTR_FORECAST_TEMP: 6.1, - ATTR_FORECAST_TEMP_LOW: 0.8, + ATTR_FORECAST_TEMP: 6, + ATTR_FORECAST_TEMP_LOW: 1, ATTR_FORECAST_WIND_BEARING: 70.25, ATTR_FORECAST_WIND_SPEED: 26.1518, }, @@ -324,8 +324,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-15T10:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 1.4630, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 55, - ATTR_FORECAST_TEMP: 6.5, - ATTR_FORECAST_TEMP_LOW: -1.5, + ATTR_FORECAST_TEMP: 6, + ATTR_FORECAST_TEMP_LOW: -1, ATTR_FORECAST_WIND_BEARING: 84.47, ATTR_FORECAST_WIND_SPEED: 25.5725, }, @@ -334,8 +334,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-16T10:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 6.1, - ATTR_FORECAST_TEMP_LOW: -1.6, + ATTR_FORECAST_TEMP: 6, + ATTR_FORECAST_TEMP_LOW: -2, ATTR_FORECAST_WIND_BEARING: 103.85, ATTR_FORECAST_WIND_SPEED: 10.7987, }, @@ -344,8 +344,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-17T10:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 11.3, - ATTR_FORECAST_TEMP_LOW: 1.3, + ATTR_FORECAST_TEMP: 11, + ATTR_FORECAST_TEMP_LOW: 1, ATTR_FORECAST_WIND_BEARING: 145.41, ATTR_FORECAST_WIND_SPEED: 11.6999, }, @@ -354,8 +354,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-18T10:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 0, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 10, - ATTR_FORECAST_TEMP: 12.3, - ATTR_FORECAST_TEMP_LOW: 5.2, + ATTR_FORECAST_TEMP: 12, + ATTR_FORECAST_TEMP_LOW: 5, ATTR_FORECAST_WIND_BEARING: 62.99, ATTR_FORECAST_WIND_SPEED: 10.5895, }, @@ -364,8 +364,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-19T10:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 2.9261, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 55, - ATTR_FORECAST_TEMP: 9.4, - ATTR_FORECAST_TEMP_LOW: 4.1, + ATTR_FORECAST_TEMP: 9, + ATTR_FORECAST_TEMP_LOW: 4, ATTR_FORECAST_WIND_BEARING: 68.54, ATTR_FORECAST_WIND_SPEED: 22.3860, }, @@ -374,8 +374,8 @@ async def test_v4_weather( ATTR_FORECAST_TIME: "2021-03-20T10:00:00+00:00", ATTR_FORECAST_PRECIPITATION: 1.2192, ATTR_FORECAST_PRECIPITATION_PROBABILITY: 33.3, - ATTR_FORECAST_TEMP: 4.5, - ATTR_FORECAST_TEMP_LOW: 1.7, + ATTR_FORECAST_TEMP: 5, + ATTR_FORECAST_TEMP_LOW: 2, ATTR_FORECAST_WIND_BEARING: 56.98, ATTR_FORECAST_WIND_SPEED: 27.9221, }, @@ -384,7 +384,7 @@ async def test_v4_weather( assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 23 assert weather_state.attributes[ATTR_WEATHER_OZONE] == 46.53 assert weather_state.attributes[ATTR_WEATHER_PRESSURE] == 1027.7691 - assert weather_state.attributes[ATTR_WEATHER_TEMPERATURE] == 6.7 + assert weather_state.attributes[ATTR_WEATHER_TEMPERATURE] == 7 assert weather_state.attributes[ATTR_WEATHER_VISIBILITY] == 13.1162 assert weather_state.attributes[ATTR_WEATHER_WIND_BEARING] == 315.14 assert weather_state.attributes[ATTR_WEATHER_WIND_SPEED] == 15.0152 diff --git a/tests/testing_config/custom_components/test/weather.py b/tests/testing_config/custom_components/test/weather.py index ec8cda69a888c4..2f285c779e033d 100644 --- a/tests/testing_config/custom_components/test/weather.py +++ b/tests/testing_config/custom_components/test/weather.py @@ -73,11 +73,6 @@ def ozone(self) -> float | None: """Return the ozone level.""" return self._handle("ozone") - @property - def attribution(self) -> str | None: - """Return the attribution.""" - return self._handle("attribution") - @property def visibility(self) -> float | None: """Return the visibility.""" From c121f459ef65084a381020751d7bcee3242123a6 Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Thu, 11 Nov 2021 15:32:01 -0800 Subject: [PATCH 08/15] Remove visibility_unit from metoffice component The vibility values given by metoffice are formatted into strings, which means they can't automatically be converted. --- homeassistant/components/metoffice/weather.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/homeassistant/components/metoffice/weather.py b/homeassistant/components/metoffice/weather.py index b02539f0e314c9..8c52a1b26e7491 100644 --- a/homeassistant/components/metoffice/weather.py +++ b/homeassistant/components/metoffice/weather.py @@ -8,7 +8,7 @@ ATTR_FORECAST_WIND_SPEED, WeatherEntity, ) -from homeassistant.const import LENGTH_KILOMETERS, TEMP_CELSIUS +from homeassistant.const import TEMP_CELSIUS from homeassistant.core import HomeAssistant from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -123,11 +123,6 @@ def visibility(self): _visibility = f"{visibility_class} - {visibility_distance}" return _visibility - @property - def visibility_unit(self): - """Return the unit of measurement.""" - return LENGTH_KILOMETERS - @property def pressure(self): """Return the mean sea-level pressure.""" From e46de67f31431b98ac2915c8796bec4da6274c25 Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Fri, 12 Nov 2021 00:48:51 -0800 Subject: [PATCH 09/15] Improve docstrings and convert pressures in forecast --- homeassistant/components/weather/__init__.py | 29 ++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 0f639054f1a016..bd3f360c119043 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -124,37 +124,37 @@ class WeatherEntity(Entity): @property def temperature(self) -> float | None: - """Return the platform temperature.""" + """Return the platform temperature in native units (i.e. not converted).""" return self._attr_temperature @property def temperature_unit(self) -> str: - """Return the unit of measurement for temperature.""" + """Return the native unit of measurement for temperature.""" return self._attr_temperature_unit @property def pressure(self) -> float | None: - """Return the pressure.""" + """Return the pressure in native units.""" return self._attr_pressure @property def pressure_unit(self) -> str | None: - """Return the unit of measurement for pressure.""" + """Return the native unit of measurement for pressure.""" return self._attr_pressure_unit @property def humidity(self) -> float | None: - """Return the humidity.""" + """Return the humidity in.""" return self._attr_humidity @property def wind_speed(self) -> float | None: - """Return the wind speed.""" + """Return the wind speed in native units.""" return self._attr_wind_speed @property def wind_speed_unit(self) -> str | None: - """Return the unit of measurement for wind speed.""" + """Return the native unit of measurement for wind speed.""" return self._attr_wind_speed_unit @property @@ -169,12 +169,12 @@ def ozone(self) -> float | None: @property def visibility(self) -> float | None: - """Return the visibility.""" + """Return the visibility in native units.""" return self._attr_visibility @property def visibility_unit(self) -> str | None: - """Return the unit of measurement for visibility.""" + """Return the native unit of measurement for visibility.""" return self._attr_visibility_unit @property @@ -184,7 +184,7 @@ def forecast(self) -> list[Forecast] | None: @property def precision(self) -> float: - """Return the precision of the temperature value.""" + """Return the precision of the temperature value, after unit conversion.""" if hasattr(self, "_attr_precision"): return self._attr_precision return ( @@ -196,7 +196,7 @@ def precision(self) -> float: @final @property def state_attributes(self): - """Return the state attributes.""" + """Return the state attributes, converted from native units to user-configured units.""" data = {} if self.temperature is not None: data[ATTR_WEATHER_TEMPERATURE] = show_temp( @@ -245,6 +245,13 @@ def state_attributes(self): self.temperature_unit, self.precision, ) + if ATTR_FORECAST_PRESSURE in forecast_entry: + if (unit := self.pressure_unit) is not None: + pressure = self.hass.config.units.pressure( + forecast_entry[ATTR_FORECAST_PRESSURE], unit + ) + forecast_entry[ATTR_FORECAST_PRESSURE] = pressure + forecast.append(forecast_entry) data[ATTR_FORECAST] = forecast From c598874e9c16dffae66e20dedb8ca662c5fdbccd Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Fri, 19 Nov 2021 00:50:42 -0800 Subject: [PATCH 10/15] Add precipitation and wind speed units --- homeassistant/components/weather/__init__.py | 22 +++ tests/components/weather/test_init.py | 139 +++++++++++++++++- .../custom_components/test/weather.py | 5 + 3 files changed, 161 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index bd3f360c119043..6926db526274c8 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -118,6 +118,7 @@ class WeatherEntity(Entity): _attr_temperature: float | None _attr_visibility: float | None = None _attr_visibility_unit: str | None = None + _attr_precipitation_unit: str | None = None _attr_wind_bearing: float | str | None = None _attr_wind_speed: float | None = None _attr_wind_speed_unit: str | None = None @@ -182,6 +183,11 @@ def forecast(self) -> list[Forecast] | None: """Return the forecast.""" return self._attr_forecast + @property + def precipitation_unit(self) -> str | None: + """Return the native unit of measurement for accumulated precipitation.""" + return self._attr_precipitation_unit + @property def precision(self) -> float: """Return the precision of the temperature value, after unit conversion.""" @@ -221,6 +227,8 @@ def state_attributes(self): data[ATTR_WEATHER_WIND_BEARING] = wind_bearing if (wind_speed := self.wind_speed) is not None: + if (unit := self.wind_speed_unit) is not None: + wind_speed = self.hass.config.units.wind_speed(wind_speed, unit) data[ATTR_WEATHER_WIND_SPEED] = wind_speed if (visibility := self.visibility) is not None: @@ -251,6 +259,20 @@ def state_attributes(self): forecast_entry[ATTR_FORECAST_PRESSURE], unit ) forecast_entry[ATTR_FORECAST_PRESSURE] = pressure + if ATTR_FORECAST_WIND_SPEED in forecast_entry: + if (unit := self.wind_speed_unit) is not None: + wind_speed = self.hass.config.units.wind_speed( + forecast_entry[ATTR_FORECAST_WIND_SPEED], unit + ) + forecast_entry[ATTR_FORECAST_WIND_SPEED] = wind_speed + if ATTR_FORECAST_PRECIPITATION in forecast_entry: + if (unit := self.precipitation_unit) is not None: + precipitation = ( + self.hass.config.units.accumulated_precipitation( + forecast_entry[ATTR_FORECAST_PRECIPITATION], unit + ) + ) + forecast_entry[ATTR_FORECAST_PRECIPITATION] = precipitation forecast.append(forecast_entry) diff --git a/tests/components/weather/test_init.py b/tests/components/weather/test_init.py index 8769c2e89ec543..4a9598f37c1ccd 100644 --- a/tests/components/weather/test_init.py +++ b/tests/components/weather/test_init.py @@ -4,22 +4,34 @@ from homeassistant.components.weather import ( ATTR_CONDITION_SUNNY, + ATTR_FORECAST, + ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_PRESSURE, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, + ATTR_FORECAST_WIND_SPEED, ATTR_WEATHER_PRESSURE, ATTR_WEATHER_TEMPERATURE, ATTR_WEATHER_VISIBILITY, + ATTR_WEATHER_WIND_SPEED, ) from homeassistant.const import ( + LENGTH_INCHES, LENGTH_KILOMETERS, LENGTH_MILES, + LENGTH_MILLIMETERS, PRESSURE_INHG, PRESSURE_PA, PRESSURE_PSI, + SPEED_METERS_PER_SECOND, + SPEED_MILES_PER_HOUR, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) from homeassistant.setup import async_setup_component from homeassistant.util.distance import convert as convert_distance from homeassistant.util.pressure import convert as convert_pressure +from homeassistant.util.speed import convert as convert_speed from homeassistant.util.temperature import convert as convert_temperature from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM @@ -51,6 +63,12 @@ async def test_temperature_conversion( temperature=native_value, temperature_unit=native_unit, condition=ATTR_CONDITION_SUNNY, + forecast=[ + { + ATTR_FORECAST_TEMP: native_value, + ATTR_FORECAST_TEMP_LOW: native_value, + } + ], ) ) @@ -61,9 +79,14 @@ async def test_temperature_conversion( await hass.async_block_till_done() state = hass.states.get(entity0.entity_id) + forecast = state.attributes[ATTR_FORECAST][0] + + expected = convert_temperature(native_value, native_unit, state_unit) assert float(state.attributes[ATTR_WEATHER_TEMPERATURE]) == approx( - convert_temperature(native_value, native_unit, state_unit), rel=0.1 + expected, rel=0.1 ) + assert float(forecast[ATTR_FORECAST_TEMP]) == approx(expected, rel=0.1) + assert float(forecast[ATTR_FORECAST_TEMP_LOW]) == approx(expected, rel=0.1) @pytest.mark.parametrize( @@ -81,7 +104,7 @@ async def test_pressure_conversion( native_value, state_unit, ): - """Test temperature conversion.""" + """Test pressure conversion.""" hass.config.units = unit_system platform = getattr(hass.components, "test.weather") platform.init(empty=True) @@ -93,6 +116,12 @@ async def test_pressure_conversion( pressure=native_value, pressure_unit=native_unit, condition=ATTR_CONDITION_SUNNY, + forecast=[ + { + ATTR_FORECAST_TEMP: None, + ATTR_FORECAST_PRESSURE: native_value, + } + ], ) ) @@ -103,10 +132,62 @@ async def test_pressure_conversion( await hass.async_block_till_done() state = hass.states.get(entity0.entity_id) - assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == approx( - convert_pressure(native_value, native_unit, state_unit) + forecast = state.attributes[ATTR_FORECAST][0] + + expected = convert_pressure(native_value, native_unit, state_unit) + assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == approx(expected) + assert float(forecast[ATTR_FORECAST_PRESSURE]) == approx(expected) + + +@pytest.mark.parametrize( + "unit_system,native_unit,native_value,state_unit", + [ + (IMPERIAL_SYSTEM, SPEED_METERS_PER_SECOND, 30, SPEED_MILES_PER_HOUR), + (METRIC_SYSTEM, SPEED_MILES_PER_HOUR, 30, SPEED_METERS_PER_SECOND), + ], +) +async def test_wind_speed_conversion( + hass, + enable_custom_integrations, + unit_system, + native_unit, + native_value, + state_unit, +): + """Test wind speed conversion.""" + hass.config.units = unit_system + platform = getattr(hass.components, "test.weather") + platform.init(empty=True) + platform.ENTITIES.append( + platform.MockWeather( + name="Test", + temperature=None, + temperature_unit=None, + wind_speed=native_value, + wind_speed_unit=native_unit, + condition=ATTR_CONDITION_SUNNY, + forecast=[ + { + ATTR_FORECAST_TEMP: None, + ATTR_FORECAST_WIND_SPEED: native_value, + } + ], + ) ) + entity0 = platform.ENTITIES[0] + assert await async_setup_component( + hass, "weather", {"weather": {"platform": "test"}} + ) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + forecast = state.attributes[ATTR_FORECAST][0] + + expected = convert_speed(native_value, native_unit, state_unit) + assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == approx(expected) + assert float(forecast[ATTR_FORECAST_WIND_SPEED]) == approx(expected) + @pytest.mark.parametrize( "unit_system,native_unit,native_value,state_unit", @@ -125,7 +206,7 @@ async def test_visibility_conversion( native_value, state_unit, ): - """Test temperature conversion.""" + """Test visibility conversion.""" hass.config.units = unit_system platform = getattr(hass.components, "test.weather") platform.init(empty=True) @@ -150,3 +231,51 @@ async def test_visibility_conversion( assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == approx( convert_distance(native_value, native_unit, state_unit) ) + + +@pytest.mark.parametrize( + "unit_system,native_unit,native_value,state_unit", + [ + (IMPERIAL_SYSTEM, LENGTH_MILLIMETERS, 30, LENGTH_INCHES), + (METRIC_SYSTEM, LENGTH_INCHES, 30, LENGTH_MILLIMETERS), + ], +) +async def test_precipitation_conversion( + hass, + enable_custom_integrations, + unit_system, + native_unit, + native_value, + state_unit, +): + """Test precipitation conversion.""" + hass.config.units = unit_system + platform = getattr(hass.components, "test.weather") + platform.init(empty=True) + platform.ENTITIES.append( + platform.MockWeather( + name="Test", + temperature=None, + temperature_unit=None, + precipitation_unit=native_unit, + condition=ATTR_CONDITION_SUNNY, + forecast=[ + { + ATTR_FORECAST_TEMP: None, + ATTR_FORECAST_PRECIPITATION: native_value, + } + ], + ) + ) + + entity0 = platform.ENTITIES[0] + assert await async_setup_component( + hass, "weather", {"weather": {"platform": "test"}} + ) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + forecast = state.attributes[ATTR_FORECAST][0] + + expected = convert_distance(native_value, native_unit, state_unit) + assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == approx(expected) diff --git a/tests/testing_config/custom_components/test/weather.py b/tests/testing_config/custom_components/test/weather.py index 2f285c779e033d..2d457dfe517215 100644 --- a/tests/testing_config/custom_components/test/weather.py +++ b/tests/testing_config/custom_components/test/weather.py @@ -88,6 +88,11 @@ def forecast(self) -> list[Forecast] | None: """Return the forecast.""" return self._handle("forecast") + @property + def precipitation_unit(self) -> str | None: + """Return the native unit of measurement for accumulated precipitation.""" + return self._handle("precipitation_unit") + @property def condition(self) -> str | None: """Return the current condition.""" From 17687a0c85dcbc20484fe0cb76e1792ad61ab30a Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Fri, 19 Nov 2021 01:41:15 -0800 Subject: [PATCH 11/15] Clean up tests --- tests/components/weather/test_init.py | 216 +++++------------- .../custom_components/test/weather.py | 29 ++- 2 files changed, 79 insertions(+), 166 deletions(-) diff --git a/tests/components/weather/test_init.py b/tests/components/weather/test_init.py index 4a9598f37c1ccd..57839fa4036cb4 100644 --- a/tests/components/weather/test_init.py +++ b/tests/components/weather/test_init.py @@ -16,16 +16,10 @@ ATTR_WEATHER_WIND_SPEED, ) from homeassistant.const import ( - LENGTH_INCHES, - LENGTH_KILOMETERS, LENGTH_MILES, LENGTH_MILLIMETERS, PRESSURE_INHG, - PRESSURE_PA, - PRESSURE_PSI, SPEED_METERS_PER_SECOND, - SPEED_MILES_PER_HOUR, - TEMP_CELSIUS, TEMP_FAHRENHEIT, ) from homeassistant.setup import async_setup_component @@ -36,39 +30,14 @@ from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM -@pytest.mark.parametrize( - "unit_system,native_unit,native_value,state_unit", - [ - (IMPERIAL_SYSTEM, TEMP_FAHRENHEIT, 100, TEMP_FAHRENHEIT), - (IMPERIAL_SYSTEM, TEMP_CELSIUS, 38, TEMP_FAHRENHEIT), - (METRIC_SYSTEM, TEMP_FAHRENHEIT, 100, TEMP_CELSIUS), - (METRIC_SYSTEM, TEMP_CELSIUS, 38, TEMP_CELSIUS), - ], -) -async def test_temperature_conversion( - hass, - enable_custom_integrations, - unit_system, - native_unit, - native_value, - state_unit, -): - """Test temperature conversion.""" - hass.config.units = unit_system +async def create_entity(hass, **kwargs): + """Create the weather entity to run tests on.""" + kwargs = {"temperature": None, "temperature_unit": None, **kwargs} platform = getattr(hass.components, "test.weather") platform.init(empty=True) platform.ENTITIES.append( - platform.MockWeather( - name="Test", - temperature=native_value, - temperature_unit=native_unit, - condition=ATTR_CONDITION_SUNNY, - forecast=[ - { - ATTR_FORECAST_TEMP: native_value, - ATTR_FORECAST_TEMP_LOW: native_value, - } - ], + platform.MockWeatherMockForecast( + name="Test", condition=ATTR_CONDITION_SUNNY, **kwargs ) ) @@ -77,11 +46,30 @@ async def test_temperature_conversion( hass, "weather", {"weather": {"platform": "test"}} ) await hass.async_block_till_done() + return entity0 + + +@pytest.mark.parametrize("unit_system", [IMPERIAL_SYSTEM, METRIC_SYSTEM]) +async def test_temperature_conversion( + hass, + enable_custom_integrations, + unit_system, +): + """Test temperature conversion.""" + hass.config.units = unit_system + native_value = 38 + native_unit = TEMP_FAHRENHEIT + + entity0 = await create_entity( + hass, temperature=native_value, temperature_unit=native_unit + ) state = hass.states.get(entity0.entity_id) forecast = state.attributes[ATTR_FORECAST][0] - expected = convert_temperature(native_value, native_unit, state_unit) + expected = convert_temperature( + native_value, native_unit, unit_system.temperature_unit + ) assert float(state.attributes[ATTR_WEATHER_TEMPERATURE]) == approx( expected, rel=0.1 ) @@ -89,193 +77,91 @@ async def test_temperature_conversion( assert float(forecast[ATTR_FORECAST_TEMP_LOW]) == approx(expected, rel=0.1) -@pytest.mark.parametrize( - "unit_system,native_unit,native_value,state_unit", - [ - (IMPERIAL_SYSTEM, PRESSURE_INHG, 30, PRESSURE_PSI), - (METRIC_SYSTEM, PRESSURE_INHG, 30, PRESSURE_PA), - ], -) +@pytest.mark.parametrize("unit_system", [IMPERIAL_SYSTEM, METRIC_SYSTEM]) async def test_pressure_conversion( hass, enable_custom_integrations, unit_system, - native_unit, - native_value, - state_unit, ): """Test pressure conversion.""" hass.config.units = unit_system - platform = getattr(hass.components, "test.weather") - platform.init(empty=True) - platform.ENTITIES.append( - platform.MockWeather( - name="Test", - temperature=None, - temperature_unit=None, - pressure=native_value, - pressure_unit=native_unit, - condition=ATTR_CONDITION_SUNNY, - forecast=[ - { - ATTR_FORECAST_TEMP: None, - ATTR_FORECAST_PRESSURE: native_value, - } - ], - ) - ) + native_value = 30 + native_unit = PRESSURE_INHG - entity0 = platform.ENTITIES[0] - assert await async_setup_component( - hass, "weather", {"weather": {"platform": "test"}} + entity0 = await create_entity( + hass, pressure=native_value, pressure_unit=native_unit ) - await hass.async_block_till_done() - state = hass.states.get(entity0.entity_id) forecast = state.attributes[ATTR_FORECAST][0] - expected = convert_pressure(native_value, native_unit, state_unit) + expected = convert_pressure(native_value, native_unit, unit_system.pressure_unit) assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == approx(expected) assert float(forecast[ATTR_FORECAST_PRESSURE]) == approx(expected) -@pytest.mark.parametrize( - "unit_system,native_unit,native_value,state_unit", - [ - (IMPERIAL_SYSTEM, SPEED_METERS_PER_SECOND, 30, SPEED_MILES_PER_HOUR), - (METRIC_SYSTEM, SPEED_MILES_PER_HOUR, 30, SPEED_METERS_PER_SECOND), - ], -) +@pytest.mark.parametrize("unit_system", [IMPERIAL_SYSTEM, METRIC_SYSTEM]) async def test_wind_speed_conversion( hass, enable_custom_integrations, unit_system, - native_unit, - native_value, - state_unit, ): """Test wind speed conversion.""" hass.config.units = unit_system - platform = getattr(hass.components, "test.weather") - platform.init(empty=True) - platform.ENTITIES.append( - platform.MockWeather( - name="Test", - temperature=None, - temperature_unit=None, - wind_speed=native_value, - wind_speed_unit=native_unit, - condition=ATTR_CONDITION_SUNNY, - forecast=[ - { - ATTR_FORECAST_TEMP: None, - ATTR_FORECAST_WIND_SPEED: native_value, - } - ], - ) - ) + native_value = 10 + native_unit = SPEED_METERS_PER_SECOND - entity0 = platform.ENTITIES[0] - assert await async_setup_component( - hass, "weather", {"weather": {"platform": "test"}} + entity0 = await create_entity( + hass, wind_speed=native_value, wind_speed_unit=native_unit ) - await hass.async_block_till_done() state = hass.states.get(entity0.entity_id) forecast = state.attributes[ATTR_FORECAST][0] - expected = convert_speed(native_value, native_unit, state_unit) + expected = convert_speed(native_value, native_unit, unit_system.wind_speed_unit) assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == approx(expected) assert float(forecast[ATTR_FORECAST_WIND_SPEED]) == approx(expected) -@pytest.mark.parametrize( - "unit_system,native_unit,native_value,state_unit", - [ - (IMPERIAL_SYSTEM, LENGTH_MILES, 30, LENGTH_MILES), - (IMPERIAL_SYSTEM, LENGTH_KILOMETERS, 30, LENGTH_MILES), - (METRIC_SYSTEM, LENGTH_KILOMETERS, 30, LENGTH_KILOMETERS), - (METRIC_SYSTEM, LENGTH_MILES, 30, LENGTH_KILOMETERS), - ], -) +@pytest.mark.parametrize("unit_system", [IMPERIAL_SYSTEM, METRIC_SYSTEM]) async def test_visibility_conversion( hass, enable_custom_integrations, unit_system, - native_unit, - native_value, - state_unit, ): """Test visibility conversion.""" hass.config.units = unit_system - platform = getattr(hass.components, "test.weather") - platform.init(empty=True) - platform.ENTITIES.append( - platform.MockWeather( - name="Test", - temperature=None, - temperature_unit=None, - visibility=native_value, - visibility_unit=native_unit, - condition=ATTR_CONDITION_SUNNY, - ) - ) + native_value = 10 + native_unit = LENGTH_MILES - entity0 = platform.ENTITIES[0] - assert await async_setup_component( - hass, "weather", {"weather": {"platform": "test"}} + entity0 = await create_entity( + hass, visibility=native_value, visibility_unit=native_unit ) - await hass.async_block_till_done() state = hass.states.get(entity0.entity_id) assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == approx( - convert_distance(native_value, native_unit, state_unit) + convert_distance(native_value, native_unit, unit_system.length_unit) ) -@pytest.mark.parametrize( - "unit_system,native_unit,native_value,state_unit", - [ - (IMPERIAL_SYSTEM, LENGTH_MILLIMETERS, 30, LENGTH_INCHES), - (METRIC_SYSTEM, LENGTH_INCHES, 30, LENGTH_MILLIMETERS), - ], -) +@pytest.mark.parametrize("unit_system", [IMPERIAL_SYSTEM, METRIC_SYSTEM]) async def test_precipitation_conversion( hass, enable_custom_integrations, unit_system, - native_unit, - native_value, - state_unit, ): """Test precipitation conversion.""" hass.config.units = unit_system - platform = getattr(hass.components, "test.weather") - platform.init(empty=True) - platform.ENTITIES.append( - platform.MockWeather( - name="Test", - temperature=None, - temperature_unit=None, - precipitation_unit=native_unit, - condition=ATTR_CONDITION_SUNNY, - forecast=[ - { - ATTR_FORECAST_TEMP: None, - ATTR_FORECAST_PRECIPITATION: native_value, - } - ], - ) - ) + native_value = 30 + native_unit = LENGTH_MILLIMETERS - entity0 = platform.ENTITIES[0] - assert await async_setup_component( - hass, "weather", {"weather": {"platform": "test"}} + entity0 = await create_entity( + hass, precipitation=native_value, precipitation_unit=native_unit ) - await hass.async_block_till_done() state = hass.states.get(entity0.entity_id) forecast = state.attributes[ATTR_FORECAST][0] - expected = convert_distance(native_value, native_unit, state_unit) + expected = convert_distance( + native_value, native_unit, unit_system.accumulated_precipitation_unit + ) assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == approx(expected) diff --git a/tests/testing_config/custom_components/test/weather.py b/tests/testing_config/custom_components/test/weather.py index 2d457dfe517215..1e52546557c6df 100644 --- a/tests/testing_config/custom_components/test/weather.py +++ b/tests/testing_config/custom_components/test/weather.py @@ -5,7 +5,16 @@ """ from __future__ import annotations -from homeassistant.components.weather import Forecast, WeatherEntity +from homeassistant.components.weather import ( + ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_PRESSURE, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, + ATTR_FORECAST_WIND_BEARING, + ATTR_FORECAST_WIND_SPEED, + Forecast, + WeatherEntity, +) from tests.common import MockEntity @@ -97,3 +106,21 @@ def precipitation_unit(self) -> str | None: def condition(self) -> str | None: """Return the current condition.""" return self._handle("condition") + + +class MockWeatherMockForecast(MockWeather): + """Mock weather class with mocked forecast.""" + + @property + def forecast(self) -> list[Forecast] | None: + """Return the forecast.""" + return [ + { + ATTR_FORECAST_TEMP: self.temperature, + ATTR_FORECAST_TEMP_LOW: self.temperature, + ATTR_FORECAST_PRESSURE: self.pressure, + ATTR_FORECAST_WIND_SPEED: self.wind_speed, + ATTR_FORECAST_WIND_BEARING: self.wind_bearing, + ATTR_FORECAST_PRECIPITATION: self._values.get("precipitation"), + } + ] From 4931bd82f04b6e59fc1e46424021f35cf30683db Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Sun, 21 Nov 2021 01:08:28 -0800 Subject: [PATCH 12/15] Round converted weather values --- homeassistant/components/weather/__init__.py | 27 +++++++++++++------- tests/components/weather/test_init.py | 15 ++++++----- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 6926db526274c8..8bab6b68c0d1dd 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -220,7 +220,7 @@ def state_attributes(self): if (pressure := self.pressure) is not None: if (unit := self.pressure_unit) is not None: - pressure = self.hass.config.units.pressure(pressure, unit) + pressure = round(self.hass.config.units.pressure(pressure, unit), 4) data[ATTR_WEATHER_PRESSURE] = pressure if (wind_bearing := self.wind_bearing) is not None: @@ -228,12 +228,14 @@ def state_attributes(self): if (wind_speed := self.wind_speed) is not None: if (unit := self.wind_speed_unit) is not None: - wind_speed = self.hass.config.units.wind_speed(wind_speed, unit) + wind_speed = round( + self.hass.config.units.wind_speed(wind_speed, unit), 4 + ) data[ATTR_WEATHER_WIND_SPEED] = wind_speed if (visibility := self.visibility) is not None: if (unit := self.visibility_unit) is not None: - visibility = self.hass.config.units.length(visibility, unit) + visibility = round(self.hass.config.units.length(visibility, unit), 4) data[ATTR_WEATHER_VISIBILITY] = visibility if self.forecast is not None: @@ -255,22 +257,29 @@ def state_attributes(self): ) if ATTR_FORECAST_PRESSURE in forecast_entry: if (unit := self.pressure_unit) is not None: - pressure = self.hass.config.units.pressure( - forecast_entry[ATTR_FORECAST_PRESSURE], unit + pressure = round( + self.hass.config.units.pressure( + forecast_entry[ATTR_FORECAST_PRESSURE], unit + ), + 4, ) forecast_entry[ATTR_FORECAST_PRESSURE] = pressure if ATTR_FORECAST_WIND_SPEED in forecast_entry: if (unit := self.wind_speed_unit) is not None: - wind_speed = self.hass.config.units.wind_speed( - forecast_entry[ATTR_FORECAST_WIND_SPEED], unit + wind_speed = round( + self.hass.config.units.wind_speed( + forecast_entry[ATTR_FORECAST_WIND_SPEED], unit + ), + 4, ) forecast_entry[ATTR_FORECAST_WIND_SPEED] = wind_speed if ATTR_FORECAST_PRECIPITATION in forecast_entry: if (unit := self.precipitation_unit) is not None: - precipitation = ( + precipitation = round( self.hass.config.units.accumulated_precipitation( forecast_entry[ATTR_FORECAST_PRECIPITATION], unit - ) + ), + 4, ) forecast_entry[ATTR_FORECAST_PRECIPITATION] = precipitation diff --git a/tests/components/weather/test_init.py b/tests/components/weather/test_init.py index 57839fa4036cb4..8e6a180e75aaaa 100644 --- a/tests/components/weather/test_init.py +++ b/tests/components/weather/test_init.py @@ -95,8 +95,8 @@ async def test_pressure_conversion( forecast = state.attributes[ATTR_FORECAST][0] expected = convert_pressure(native_value, native_unit, unit_system.pressure_unit) - assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == approx(expected) - assert float(forecast[ATTR_FORECAST_PRESSURE]) == approx(expected) + assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == approx(expected, rel=1e-3) + assert float(forecast[ATTR_FORECAST_PRESSURE]) == approx(expected, rel=1e-3) @pytest.mark.parametrize("unit_system", [IMPERIAL_SYSTEM, METRIC_SYSTEM]) @@ -118,8 +118,10 @@ async def test_wind_speed_conversion( forecast = state.attributes[ATTR_FORECAST][0] expected = convert_speed(native_value, native_unit, unit_system.wind_speed_unit) - assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == approx(expected) - assert float(forecast[ATTR_FORECAST_WIND_SPEED]) == approx(expected) + assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == approx( + expected, rel=1e-3 + ) + assert float(forecast[ATTR_FORECAST_WIND_SPEED]) == approx(expected, rel=1e-3) @pytest.mark.parametrize("unit_system", [IMPERIAL_SYSTEM, METRIC_SYSTEM]) @@ -138,8 +140,9 @@ async def test_visibility_conversion( ) state = hass.states.get(entity0.entity_id) + expected = convert_distance(native_value, native_unit, unit_system.length_unit) assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == approx( - convert_distance(native_value, native_unit, unit_system.length_unit) + expected, rel=1e-3 ) @@ -164,4 +167,4 @@ async def test_precipitation_conversion( expected = convert_distance( native_value, native_unit, unit_system.accumulated_precipitation_unit ) - assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == approx(expected) + assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == approx(expected, rel=1e-3) From 0bc202d054b80a616ab313fe322f829a49fc7dbd Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Sun, 21 Nov 2021 02:19:11 -0800 Subject: [PATCH 13/15] Round weather values to 2 decimal places --- homeassistant/components/weather/__init__.py | 6 +++--- tests/components/weather/test_init.py | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 8bab6b68c0d1dd..a7516b228ec345 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -261,7 +261,7 @@ def state_attributes(self): self.hass.config.units.pressure( forecast_entry[ATTR_FORECAST_PRESSURE], unit ), - 4, + 2, ) forecast_entry[ATTR_FORECAST_PRESSURE] = pressure if ATTR_FORECAST_WIND_SPEED in forecast_entry: @@ -270,7 +270,7 @@ def state_attributes(self): self.hass.config.units.wind_speed( forecast_entry[ATTR_FORECAST_WIND_SPEED], unit ), - 4, + 2, ) forecast_entry[ATTR_FORECAST_WIND_SPEED] = wind_speed if ATTR_FORECAST_PRECIPITATION in forecast_entry: @@ -279,7 +279,7 @@ def state_attributes(self): self.hass.config.units.accumulated_precipitation( forecast_entry[ATTR_FORECAST_PRECIPITATION], unit ), - 4, + 2, ) forecast_entry[ATTR_FORECAST_PRECIPITATION] = precipitation diff --git a/tests/components/weather/test_init.py b/tests/components/weather/test_init.py index 8e6a180e75aaaa..4125e94749ada2 100644 --- a/tests/components/weather/test_init.py +++ b/tests/components/weather/test_init.py @@ -95,8 +95,8 @@ async def test_pressure_conversion( forecast = state.attributes[ATTR_FORECAST][0] expected = convert_pressure(native_value, native_unit, unit_system.pressure_unit) - assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == approx(expected, rel=1e-3) - assert float(forecast[ATTR_FORECAST_PRESSURE]) == approx(expected, rel=1e-3) + assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == approx(expected, rel=1e-2) + assert float(forecast[ATTR_FORECAST_PRESSURE]) == approx(expected, rel=1e-2) @pytest.mark.parametrize("unit_system", [IMPERIAL_SYSTEM, METRIC_SYSTEM]) @@ -119,9 +119,9 @@ async def test_wind_speed_conversion( expected = convert_speed(native_value, native_unit, unit_system.wind_speed_unit) assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == approx( - expected, rel=1e-3 + expected, rel=1e-2 ) - assert float(forecast[ATTR_FORECAST_WIND_SPEED]) == approx(expected, rel=1e-3) + assert float(forecast[ATTR_FORECAST_WIND_SPEED]) == approx(expected, rel=1e-2) @pytest.mark.parametrize("unit_system", [IMPERIAL_SYSTEM, METRIC_SYSTEM]) @@ -142,7 +142,7 @@ async def test_visibility_conversion( state = hass.states.get(entity0.entity_id) expected = convert_distance(native_value, native_unit, unit_system.length_unit) assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == approx( - expected, rel=1e-3 + expected, rel=1e-2 ) @@ -167,4 +167,4 @@ async def test_precipitation_conversion( expected = convert_distance( native_value, native_unit, unit_system.accumulated_precipitation_unit ) - assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == approx(expected, rel=1e-3) + assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == approx(expected, rel=1e-2) From 0167530c063366923bff7d67c9e9c3ab5a04a5d5 Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Mon, 22 Nov 2021 12:09:01 -0800 Subject: [PATCH 14/15] Move number of rounding decimal places to constant --- homeassistant/components/weather/__init__.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index a7516b228ec345..831601e9b4594f 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -61,6 +61,8 @@ SCAN_INTERVAL = timedelta(seconds=30) +ROUNDING_PLACES = 2 + class Forecast(TypedDict, total=False): """Typed weather forecast dict.""" @@ -220,7 +222,9 @@ def state_attributes(self): if (pressure := self.pressure) is not None: if (unit := self.pressure_unit) is not None: - pressure = round(self.hass.config.units.pressure(pressure, unit), 4) + pressure = round( + self.hass.config.units.pressure(pressure, unit), ROUNDING_PLACES + ) data[ATTR_WEATHER_PRESSURE] = pressure if (wind_bearing := self.wind_bearing) is not None: @@ -229,13 +233,15 @@ def state_attributes(self): if (wind_speed := self.wind_speed) is not None: if (unit := self.wind_speed_unit) is not None: wind_speed = round( - self.hass.config.units.wind_speed(wind_speed, unit), 4 + self.hass.config.units.wind_speed(wind_speed, unit), ROUNDING_PLACES ) data[ATTR_WEATHER_WIND_SPEED] = wind_speed if (visibility := self.visibility) is not None: if (unit := self.visibility_unit) is not None: - visibility = round(self.hass.config.units.length(visibility, unit), 4) + visibility = round( + self.hass.config.units.length(visibility, unit), ROUNDING_PLACES + ) data[ATTR_WEATHER_VISIBILITY] = visibility if self.forecast is not None: @@ -261,7 +267,7 @@ def state_attributes(self): self.hass.config.units.pressure( forecast_entry[ATTR_FORECAST_PRESSURE], unit ), - 2, + ROUNDING_PLACES, ) forecast_entry[ATTR_FORECAST_PRESSURE] = pressure if ATTR_FORECAST_WIND_SPEED in forecast_entry: @@ -270,7 +276,7 @@ def state_attributes(self): self.hass.config.units.wind_speed( forecast_entry[ATTR_FORECAST_WIND_SPEED], unit ), - 2, + ROUNDING_PLACES, ) forecast_entry[ATTR_FORECAST_WIND_SPEED] = wind_speed if ATTR_FORECAST_PRECIPITATION in forecast_entry: @@ -279,7 +285,7 @@ def state_attributes(self): self.hass.config.units.accumulated_precipitation( forecast_entry[ATTR_FORECAST_PRECIPITATION], unit ), - 2, + ROUNDING_PLACES, ) forecast_entry[ATTR_FORECAST_PRECIPITATION] = precipitation From 52c1e57c3eb00a7f1784e1286bfd17dcbf646820 Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Sun, 28 Nov 2021 17:21:36 -0800 Subject: [PATCH 15/15] Docstring and styles --- homeassistant/components/weather/__init__.py | 19 ++++++++++--------- .../custom_components/test/weather.py | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 831601e9b4594f..b9fa7e2ae39141 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -61,7 +61,7 @@ SCAN_INTERVAL = timedelta(seconds=30) -ROUNDING_PLACES = 2 +ROUNDING_PRECISION = 2 class Forecast(TypedDict, total=False): @@ -147,7 +147,7 @@ def pressure_unit(self) -> str | None: @property def humidity(self) -> float | None: - """Return the humidity in.""" + """Return the humidity in native units.""" return self._attr_humidity @property @@ -182,7 +182,7 @@ def visibility_unit(self) -> str | None: @property def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" + """Return the forecast in native units.""" return self._attr_forecast @property @@ -223,7 +223,7 @@ def state_attributes(self): if (pressure := self.pressure) is not None: if (unit := self.pressure_unit) is not None: pressure = round( - self.hass.config.units.pressure(pressure, unit), ROUNDING_PLACES + self.hass.config.units.pressure(pressure, unit), ROUNDING_PRECISION ) data[ATTR_WEATHER_PRESSURE] = pressure @@ -233,14 +233,15 @@ def state_attributes(self): if (wind_speed := self.wind_speed) is not None: if (unit := self.wind_speed_unit) is not None: wind_speed = round( - self.hass.config.units.wind_speed(wind_speed, unit), ROUNDING_PLACES + self.hass.config.units.wind_speed(wind_speed, unit), + ROUNDING_PRECISION, ) data[ATTR_WEATHER_WIND_SPEED] = wind_speed if (visibility := self.visibility) is not None: if (unit := self.visibility_unit) is not None: visibility = round( - self.hass.config.units.length(visibility, unit), ROUNDING_PLACES + self.hass.config.units.length(visibility, unit), ROUNDING_PRECISION ) data[ATTR_WEATHER_VISIBILITY] = visibility @@ -267,7 +268,7 @@ def state_attributes(self): self.hass.config.units.pressure( forecast_entry[ATTR_FORECAST_PRESSURE], unit ), - ROUNDING_PLACES, + ROUNDING_PRECISION, ) forecast_entry[ATTR_FORECAST_PRESSURE] = pressure if ATTR_FORECAST_WIND_SPEED in forecast_entry: @@ -276,7 +277,7 @@ def state_attributes(self): self.hass.config.units.wind_speed( forecast_entry[ATTR_FORECAST_WIND_SPEED], unit ), - ROUNDING_PLACES, + ROUNDING_PRECISION, ) forecast_entry[ATTR_FORECAST_WIND_SPEED] = wind_speed if ATTR_FORECAST_PRECIPITATION in forecast_entry: @@ -285,7 +286,7 @@ def state_attributes(self): self.hass.config.units.accumulated_precipitation( forecast_entry[ATTR_FORECAST_PRECIPITATION], unit ), - ROUNDING_PLACES, + ROUNDING_PRECISION, ) forecast_entry[ATTR_FORECAST_PRECIPITATION] = precipitation diff --git a/tests/testing_config/custom_components/test/weather.py b/tests/testing_config/custom_components/test/weather.py index 1e52546557c6df..224d64955488fe 100644 --- a/tests/testing_config/custom_components/test/weather.py +++ b/tests/testing_config/custom_components/test/weather.py @@ -1,5 +1,5 @@ """ -Provide a mock sensor platform. +Provide a mock weather platform. Call init before using it in your tests to ensure clean test data. """