Skip to content

Commit bbb29ab

Browse files
Code quality improvements smhi (home-assistant#64312)
1 parent 7e40707 commit bbb29ab

File tree

5 files changed

+63
-131
lines changed

5 files changed

+63
-131
lines changed

.strict-typing

+1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ homeassistant.components.senseme.*
154154
homeassistant.components.shelly.*
155155
homeassistant.components.simplisafe.*
156156
homeassistant.components.slack.*
157+
homeassistant.components.smhi.*
157158
homeassistant.components.sonos.media_player
158159
homeassistant.components.ssdp.*
159160
homeassistant.components.stookalert.*

CODEOWNERS

+2
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,8 @@ tests/components/smartthings/* @andrewsayre
847847
homeassistant/components/smarttub/* @mdz
848848
tests/components/smarttub/* @mdz
849849
homeassistant/components/smarty/* @z0mbieprocess
850+
homeassistant/components/smhi/* @gjohansson-ST
851+
tests/components/smhi/* @gjohansson-ST
850852
homeassistant/components/sms/* @ocalvo
851853
homeassistant/components/smtp/* @fabaff
852854
tests/components/smtp/* @fabaff

homeassistant/components/smhi/manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
"config_flow": true,
55
"documentation": "https://www.home-assistant.io/integrations/smhi",
66
"requirements": ["smhi-pkg==1.0.15"],
7-
"codeowners": [],
7+
"codeowners": ["@gjohansson-ST"],
88
"iot_class": "cloud_polling"
99
}

homeassistant/components/smhi/weather.py

+48-130
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import asyncio
55
from datetime import datetime, timedelta
66
import logging
7-
from typing import Final, TypedDict
7+
from typing import Final
88

99
import aiohttp
1010
import async_timeout
@@ -35,7 +35,14 @@
3535
WeatherEntity,
3636
)
3737
from homeassistant.config_entries import ConfigEntry
38-
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS
38+
from homeassistant.const import (
39+
CONF_LATITUDE,
40+
CONF_LONGITUDE,
41+
CONF_NAME,
42+
LENGTH_KILOMETERS,
43+
LENGTH_MILLIMETERS,
44+
TEMP_CELSIUS,
45+
)
3946
from homeassistant.core import HomeAssistant
4047
from homeassistant.helpers import aiohttp_client
4148
from homeassistant.helpers.device_registry import DeviceEntryType
@@ -72,6 +79,7 @@
7279
ATTR_CONDITION_EXCEPTIONAL: [],
7380
}
7481

82+
TIMEOUT = 10
7583
# 5 minutes between retrying connect to API again
7684
RETRY_TIMEOUT = 5 * 60
7785

@@ -103,6 +111,11 @@ async def async_setup_entry(
103111
class SmhiWeather(WeatherEntity):
104112
"""Representation of a weather entity."""
105113

114+
_attr_attribution = "Swedish weather institute (SMHI)"
115+
_attr_temperature_unit = TEMP_CELSIUS
116+
_attr_visibility_unit = LENGTH_KILOMETERS
117+
_attr_precipitation_unit = LENGTH_MILLIMETERS
118+
106119
def __init__(
107120
self,
108121
name: str,
@@ -112,140 +125,65 @@ def __init__(
112125
) -> None:
113126
"""Initialize the SMHI weather entity."""
114127

115-
self._name = name
116-
self._latitude = latitude
117-
self._longitude = longitude
128+
self._attr_name = name
129+
self._attr_unique_id = f"{latitude}, {longitude}"
118130
self._forecasts: list[SmhiForecast] | None = None
119131
self._fail_count = 0
120-
self._smhi_api = Smhi(self._longitude, self._latitude, session=session)
132+
self._smhi_api = Smhi(longitude, latitude, session=session)
121133
self._attr_device_info = DeviceInfo(
122134
entry_type=DeviceEntryType.SERVICE,
123-
identifiers={(DOMAIN, f"{self._latitude}, {self._longitude}")},
135+
identifiers={(DOMAIN, f"{latitude}, {longitude}")},
124136
manufacturer="SMHI",
125137
model="v2",
126-
name=self._name,
138+
name=name,
127139
configuration_url="http://opendata.smhi.se/apidocs/metfcst/parameters.html",
128140
)
129-
130-
@property
131-
def unique_id(self) -> str:
132-
"""Return a unique id."""
133-
return f"{self._latitude}, {self._longitude}"
141+
self._attr_condition = None
142+
self._attr_temperature = None
134143

135144
@Throttle(MIN_TIME_BETWEEN_UPDATES)
136145
async def async_update(self) -> None:
137146
"""Refresh the forecast data from SMHI weather API."""
138147
try:
139-
async with async_timeout.timeout(10):
140-
self._forecasts = await self.get_weather_forecast()
148+
async with async_timeout.timeout(TIMEOUT):
149+
self._forecasts = await self._smhi_api.async_get_forecast()
141150
self._fail_count = 0
142-
143151
except (asyncio.TimeoutError, SmhiForecastException):
144152
_LOGGER.error("Failed to connect to SMHI API, retry in 5 minutes")
145153
self._fail_count += 1
146154
if self._fail_count < 3:
147155
async_call_later(self.hass, RETRY_TIMEOUT, self.retry_update)
156+
return
157+
158+
if self._forecasts:
159+
self._attr_temperature = self._forecasts[0].temperature
160+
self._attr_humidity = self._forecasts[0].humidity
161+
# Convert from m/s to km/h
162+
self._attr_wind_speed = round(self._forecasts[0].wind_speed * 18 / 5)
163+
self._attr_wind_bearing = self._forecasts[0].wind_direction
164+
self._attr_visibility = self._forecasts[0].horizontal_visibility
165+
self._attr_pressure = self._forecasts[0].pressure
166+
self._attr_condition = next(
167+
(
168+
k
169+
for k, v in CONDITION_CLASSES.items()
170+
if self._forecasts[0].symbol in v
171+
),
172+
None,
173+
)
174+
self._attr_extra_state_attributes = {
175+
ATTR_SMHI_CLOUDINESS: self._forecasts[0].cloudiness,
176+
# Convert from m/s to km/h
177+
ATTR_SMHI_WIND_GUST_SPEED: round(self._forecasts[0].wind_gust * 18 / 5),
178+
ATTR_SMHI_THUNDER_PROBABILITY: self._forecasts[0].thunder,
179+
}
148180

149181
async def retry_update(self, _: datetime) -> None:
150182
"""Retry refresh weather forecast."""
151183
await self.async_update( # pylint: disable=unexpected-keyword-arg
152184
no_throttle=True
153185
)
154186

155-
async def get_weather_forecast(self) -> list[SmhiForecast]:
156-
"""Return the current forecasts from SMHI API."""
157-
return await self._smhi_api.async_get_forecast()
158-
159-
@property
160-
def name(self) -> str:
161-
"""Return the name of the sensor."""
162-
return self._name
163-
164-
@property
165-
def temperature(self) -> int | None:
166-
"""Return the temperature."""
167-
if self._forecasts is not None:
168-
return self._forecasts[0].temperature
169-
return None
170-
171-
@property
172-
def temperature_unit(self) -> str:
173-
"""Return the unit of measurement."""
174-
return TEMP_CELSIUS
175-
176-
@property
177-
def humidity(self) -> int | None:
178-
"""Return the humidity."""
179-
if self._forecasts is not None:
180-
return self._forecasts[0].humidity
181-
return None
182-
183-
@property
184-
def wind_speed(self) -> float | None:
185-
"""Return the wind speed."""
186-
if self._forecasts is not None:
187-
# Convert from m/s to km/h
188-
return round(self._forecasts[0].wind_speed * 18 / 5)
189-
return None
190-
191-
@property
192-
def wind_gust_speed(self) -> float | None:
193-
"""Return the wind gust speed."""
194-
if self._forecasts is not None:
195-
# Convert from m/s to km/h
196-
return round(self._forecasts[0].wind_gust * 18 / 5)
197-
return None
198-
199-
@property
200-
def wind_bearing(self) -> int | None:
201-
"""Return the wind bearing."""
202-
if self._forecasts is not None:
203-
return self._forecasts[0].wind_direction
204-
return None
205-
206-
@property
207-
def visibility(self) -> float | None:
208-
"""Return the visibility."""
209-
if self._forecasts is not None:
210-
return self._forecasts[0].horizontal_visibility
211-
return None
212-
213-
@property
214-
def pressure(self) -> int | None:
215-
"""Return the pressure."""
216-
if self._forecasts is not None:
217-
return self._forecasts[0].pressure
218-
return None
219-
220-
@property
221-
def cloudiness(self) -> int | None:
222-
"""Return the cloudiness."""
223-
if self._forecasts is not None:
224-
return self._forecasts[0].cloudiness
225-
return None
226-
227-
@property
228-
def thunder_probability(self) -> int | None:
229-
"""Return the chance of thunder, unit Percent."""
230-
if self._forecasts is not None:
231-
return self._forecasts[0].thunder
232-
return None
233-
234-
@property
235-
def condition(self) -> str | None:
236-
"""Return the weather condition."""
237-
if self._forecasts is None:
238-
return None
239-
return next(
240-
(k for k, v in CONDITION_CLASSES.items() if self._forecasts[0].symbol in v),
241-
None,
242-
)
243-
244-
@property
245-
def attribution(self) -> str:
246-
"""Return the attribution."""
247-
return "Swedish weather institute (SMHI)"
248-
249187
@property
250188
def forecast(self) -> list[Forecast] | None:
251189
"""Return the forecast."""
@@ -270,23 +208,3 @@ def forecast(self) -> list[Forecast] | None:
270208
)
271209

272210
return data
273-
274-
@property
275-
def extra_state_attributes(self) -> ExtraAttributes:
276-
"""Return SMHI specific attributes."""
277-
extra_attributes: ExtraAttributes = {}
278-
if self.cloudiness is not None:
279-
extra_attributes[ATTR_SMHI_CLOUDINESS] = self.cloudiness
280-
if self.wind_gust_speed is not None:
281-
extra_attributes[ATTR_SMHI_WIND_GUST_SPEED] = self.wind_gust_speed
282-
if self.thunder_probability is not None:
283-
extra_attributes[ATTR_SMHI_THUNDER_PROBABILITY] = self.thunder_probability
284-
return extra_attributes
285-
286-
287-
class ExtraAttributes(TypedDict, total=False):
288-
"""Represent the extra state attribute types."""
289-
290-
cloudiness: int
291-
thunder_probability: int
292-
wind_gust_speed: float

mypy.ini

+11
Original file line numberDiff line numberDiff line change
@@ -1506,6 +1506,17 @@ no_implicit_optional = true
15061506
warn_return_any = true
15071507
warn_unreachable = true
15081508

1509+
[mypy-homeassistant.components.smhi.*]
1510+
check_untyped_defs = true
1511+
disallow_incomplete_defs = true
1512+
disallow_subclassing_any = true
1513+
disallow_untyped_calls = true
1514+
disallow_untyped_decorators = true
1515+
disallow_untyped_defs = true
1516+
no_implicit_optional = true
1517+
warn_return_any = true
1518+
warn_unreachable = true
1519+
15091520
[mypy-homeassistant.components.sonos.media_player]
15101521
check_untyped_defs = true
15111522
disallow_incomplete_defs = true

0 commit comments

Comments
 (0)