Fix smhi typing (#50690)

This commit is contained in:
Martin Hjelmare 2021-05-15 21:38:12 +02:00 committed by GitHub
parent cad41cd4ed
commit 5da64d01e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 58 additions and 40 deletions

View File

@ -1,10 +1,15 @@
"""Config flow to configure SMHI component.""" """Config flow to configure SMHI component."""
from __future__ import annotations
from typing import Any
from smhi.smhi_lib import Smhi, SmhiForecastException from smhi.smhi_lib import Smhi, SmhiForecastException
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.util import slugify from homeassistant.util import slugify
@ -13,7 +18,7 @@ from .const import DOMAIN, HOME_LOCATION_NAME
@callback @callback
def smhi_locations(hass: HomeAssistant): def smhi_locations(hass: HomeAssistant) -> set[str]:
"""Return configurations of SMHI component.""" """Return configurations of SMHI component."""
return { return {
(slugify(entry.data[CONF_NAME])) (slugify(entry.data[CONF_NAME]))
@ -28,9 +33,11 @@ class SmhiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize SMHI forecast configuration flow.""" """Initialize SMHI forecast configuration flow."""
self._errors = {} self._errors: dict[str, str] = {}
async def async_step_user(self, user_input=None): async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle a flow initialized by the user.""" """Handle a flow initialized by the user."""
self._errors = {} self._errors = {}
@ -79,8 +86,11 @@ class SmhiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return name in smhi_locations(self.hass) return name in smhi_locations(self.hass)
async def _show_config_form( async def _show_config_form(
self, name: str = None, latitude: str = None, longitude: str = None self,
): name: str | None = None,
latitude: float | None = None,
longitude: float | None = None,
) -> FlowResult:
"""Show the configuration form to edit location data.""" """Show the configuration form to edit location data."""
return self.async_show_form( return self.async_show_form(
step_id="user", step_id="user",
@ -94,7 +104,7 @@ class SmhiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
errors=self._errors, errors=self._errors,
) )
async def _check_location(self, longitude: str, latitude: str) -> bool: async def _check_location(self, longitude: float, latitude: float) -> bool:
"""Return true if location is ok.""" """Return true if location is ok."""
try: try:
session = aiohttp_client.async_get_clientsession(self.hass) session = aiohttp_client.async_get_clientsession(self.hass)

View File

@ -1,9 +1,11 @@
"""Constants in smhi component.""" """Constants in smhi component."""
from typing import Final
from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN
ATTR_SMHI_CLOUDINESS = "cloudiness" ATTR_SMHI_CLOUDINESS: Final = "cloudiness"
ATTR_SMHI_WIND_GUST_SPEED = "wind_gust_speed" ATTR_SMHI_WIND_GUST_SPEED: Final = "wind_gust_speed"
ATTR_SMHI_THUNDER_PROBABILITY = "thunder_probability" ATTR_SMHI_THUNDER_PROBABILITY: Final = "thunder_probability"
DOMAIN = "smhi" DOMAIN = "smhi"

View File

@ -2,13 +2,14 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from datetime import timedelta from datetime import datetime, timedelta
import logging import logging
from typing import Any, Final, TypedDict
import aiohttp import aiohttp
import async_timeout import async_timeout
from smhi import Smhi from smhi import Smhi
from smhi.smhi_lib import SmhiForecastException from smhi.smhi_lib import SmhiForecast, SmhiForecastException
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_CONDITION_CLOUDY, ATTR_CONDITION_CLOUDY,
@ -36,6 +37,8 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_call_later
from homeassistant.util import Throttle, slugify from homeassistant.util import Throttle, slugify
from .const import ( from .const import (
@ -48,7 +51,7 @@ from .const import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# Used to map condition from API results # Used to map condition from API results
CONDITION_CLASSES = { CONDITION_CLASSES: Final[dict[str, list[int]]] = {
ATTR_CONDITION_CLOUDY: [5, 6], ATTR_CONDITION_CLOUDY: [5, 6],
ATTR_CONDITION_FOG: [7], ATTR_CONDITION_FOG: [7],
ATTR_CONDITION_HAIL: [], ATTR_CONDITION_HAIL: [],
@ -72,8 +75,10 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=31)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, config_entry: ConfigEntry, config_entries hass: HomeAssistant,
) -> bool: config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Add a weather entity from map location.""" """Add a weather entity from map location."""
location = config_entry.data location = config_entry.data
name = slugify(location[CONF_NAME]) name = slugify(location[CONF_NAME])
@ -88,8 +93,7 @@ async def async_setup_entry(
) )
entity.entity_id = ENTITY_ID_SENSOR_FORMAT.format(name) entity.entity_id = ENTITY_ID_SENSOR_FORMAT.format(name)
config_entries([entity], True) async_add_entities([entity], True)
return True
class SmhiWeather(WeatherEntity): class SmhiWeather(WeatherEntity):
@ -100,14 +104,14 @@ class SmhiWeather(WeatherEntity):
name: str, name: str,
latitude: str, latitude: str,
longitude: str, longitude: str,
session: aiohttp.ClientSession = None, session: aiohttp.ClientSession,
) -> None: ) -> None:
"""Initialize the SMHI weather entity.""" """Initialize the SMHI weather entity."""
self._name = name self._name = name
self._latitude = latitude self._latitude = latitude
self._longitude = longitude self._longitude = longitude
self._forecasts = None self._forecasts: list[SmhiForecast] | None = None
self._fail_count = 0 self._fail_count = 0
self._smhi_api = Smhi(self._longitude, self._latitude, session=session) self._smhi_api = Smhi(self._longitude, self._latitude, session=session)
@ -128,17 +132,15 @@ class SmhiWeather(WeatherEntity):
_LOGGER.error("Failed to connect to SMHI API, retry in 5 minutes") _LOGGER.error("Failed to connect to SMHI API, retry in 5 minutes")
self._fail_count += 1 self._fail_count += 1
if self._fail_count < 3: if self._fail_count < 3:
self.hass.helpers.event.async_call_later( async_call_later(self.hass, RETRY_TIMEOUT, self.retry_update)
RETRY_TIMEOUT, self.retry_update
)
async def retry_update(self, _): async def retry_update(self, _: datetime) -> None:
"""Retry refresh weather forecast.""" """Retry refresh weather forecast."""
await self.async_update( # pylint: disable=unexpected-keyword-arg await self.async_update( # pylint: disable=unexpected-keyword-arg
no_throttle=True no_throttle=True
) )
async def get_weather_forecast(self) -> []: async def get_weather_forecast(self) -> list[SmhiForecast]:
"""Return the current forecasts from SMHI API.""" """Return the current forecasts from SMHI API."""
return await self._smhi_api.async_get_forecast() return await self._smhi_api.async_get_forecast()
@ -148,7 +150,7 @@ class SmhiWeather(WeatherEntity):
return self._name return self._name
@property @property
def temperature(self) -> int: def temperature(self) -> int | None:
"""Return the temperature.""" """Return the temperature."""
if self._forecasts is not None: if self._forecasts is not None:
return self._forecasts[0].temperature return self._forecasts[0].temperature
@ -160,14 +162,14 @@ class SmhiWeather(WeatherEntity):
return TEMP_CELSIUS return TEMP_CELSIUS
@property @property
def humidity(self) -> int: def humidity(self) -> int | None:
"""Return the humidity.""" """Return the humidity."""
if self._forecasts is not None: if self._forecasts is not None:
return self._forecasts[0].humidity return self._forecasts[0].humidity
return None return None
@property @property
def wind_speed(self) -> float: def wind_speed(self) -> float | None:
"""Return the wind speed.""" """Return the wind speed."""
if self._forecasts is not None: if self._forecasts is not None:
# Convert from m/s to km/h # Convert from m/s to km/h
@ -175,7 +177,7 @@ class SmhiWeather(WeatherEntity):
return None return None
@property @property
def wind_gust_speed(self) -> float: def wind_gust_speed(self) -> float | None:
"""Return the wind gust speed.""" """Return the wind gust speed."""
if self._forecasts is not None: if self._forecasts is not None:
# Convert from m/s to km/h # Convert from m/s to km/h
@ -183,42 +185,42 @@ class SmhiWeather(WeatherEntity):
return None return None
@property @property
def wind_bearing(self) -> int: def wind_bearing(self) -> int | None:
"""Return the wind bearing.""" """Return the wind bearing."""
if self._forecasts is not None: if self._forecasts is not None:
return self._forecasts[0].wind_direction return self._forecasts[0].wind_direction
return None return None
@property @property
def visibility(self) -> float: def visibility(self) -> float | None:
"""Return the visibility.""" """Return the visibility."""
if self._forecasts is not None: if self._forecasts is not None:
return self._forecasts[0].horizontal_visibility return self._forecasts[0].horizontal_visibility
return None return None
@property @property
def pressure(self) -> int: def pressure(self) -> int | None:
"""Return the pressure.""" """Return the pressure."""
if self._forecasts is not None: if self._forecasts is not None:
return self._forecasts[0].pressure return self._forecasts[0].pressure
return None return None
@property @property
def cloudiness(self) -> int: def cloudiness(self) -> int | None:
"""Return the cloudiness.""" """Return the cloudiness."""
if self._forecasts is not None: if self._forecasts is not None:
return self._forecasts[0].cloudiness return self._forecasts[0].cloudiness
return None return None
@property @property
def thunder_probability(self) -> int: def thunder_probability(self) -> int | None:
"""Return the chance of thunder, unit Percent.""" """Return the chance of thunder, unit Percent."""
if self._forecasts is not None: if self._forecasts is not None:
return self._forecasts[0].thunder return self._forecasts[0].thunder
return None return None
@property @property
def condition(self) -> str: def condition(self) -> str | None:
"""Return the weather condition.""" """Return the weather condition."""
if self._forecasts is None: if self._forecasts is None:
return None return None
@ -233,7 +235,7 @@ class SmhiWeather(WeatherEntity):
return "Swedish weather institute (SMHI)" return "Swedish weather institute (SMHI)"
@property @property
def forecast(self) -> list: def forecast(self) -> list[dict[str, Any]] | None:
"""Return the forecast.""" """Return the forecast."""
if self._forecasts is None or len(self._forecasts) < 2: if self._forecasts is None or len(self._forecasts) < 2:
return None return None
@ -258,9 +260,9 @@ class SmhiWeather(WeatherEntity):
return data return data
@property @property
def extra_state_attributes(self) -> dict: def extra_state_attributes(self) -> ExtraAttributes:
"""Return SMHI specific attributes.""" """Return SMHI specific attributes."""
extra_attributes = {} extra_attributes: ExtraAttributes = {}
if self.cloudiness is not None: if self.cloudiness is not None:
extra_attributes[ATTR_SMHI_CLOUDINESS] = self.cloudiness extra_attributes[ATTR_SMHI_CLOUDINESS] = self.cloudiness
if self.wind_gust_speed is not None: if self.wind_gust_speed is not None:
@ -268,3 +270,11 @@ class SmhiWeather(WeatherEntity):
if self.thunder_probability is not None: if self.thunder_probability is not None:
extra_attributes[ATTR_SMHI_THUNDER_PROBABILITY] = self.thunder_probability extra_attributes[ATTR_SMHI_THUNDER_PROBABILITY] = self.thunder_probability
return extra_attributes return extra_attributes
class ExtraAttributes(TypedDict, total=False):
"""Represent the extra state attribute types."""
cloudiness: int
thunder_probability: int
wind_gust_speed: float

View File

@ -1160,9 +1160,6 @@ ignore_errors = true
[mypy-homeassistant.components.smarty.*] [mypy-homeassistant.components.smarty.*]
ignore_errors = true ignore_errors = true
[mypy-homeassistant.components.smhi.*]
ignore_errors = true
[mypy-homeassistant.components.solaredge.*] [mypy-homeassistant.components.solaredge.*]
ignore_errors = true ignore_errors = true

View File

@ -188,7 +188,6 @@ IGNORED_MODULES: Final[list[str]] = [
"homeassistant.components.smartthings.*", "homeassistant.components.smartthings.*",
"homeassistant.components.smarttub.*", "homeassistant.components.smarttub.*",
"homeassistant.components.smarty.*", "homeassistant.components.smarty.*",
"homeassistant.components.smhi.*",
"homeassistant.components.solaredge.*", "homeassistant.components.solaredge.*",
"homeassistant.components.solarlog.*", "homeassistant.components.solarlog.*",
"homeassistant.components.somfy.*", "homeassistant.components.somfy.*",