Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type annotations for MET #58804

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 22 additions & 14 deletions homeassistant/components/met/__init__.py
@@ -1,10 +1,15 @@
"""The met component."""
from __future__ import annotations

from datetime import timedelta
import logging
from random import randrange
from types import MappingProxyType
from typing import Any, Callable

import metno

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_ELEVATION,
CONF_LATITUDE,
Expand All @@ -13,6 +18,7 @@
LENGTH_FEET,
LENGTH_METERS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.distance import convert as convert_distance
Expand All @@ -32,7 +38,7 @@
_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass, config_entry):
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up Met as config entry."""
# Don't setup if tracking home location and latitude or longitude isn't set.
# Also, filters out our onboarding default location.
Expand Down Expand Up @@ -62,7 +68,7 @@ async def async_setup_entry(hass, config_entry):
return True


async def async_unload_entry(hass, config_entry):
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
Expand All @@ -77,9 +83,9 @@ async def async_unload_entry(hass, config_entry):
class MetDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Met data."""

def __init__(self, hass, config_entry):
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Initialize global Met data updater."""
self._unsub_track_home = None
self._unsub_track_home: Callable | None = None
self.weather = MetWeatherData(
hass, config_entry.data, hass.config.units.is_metric
)
Expand All @@ -89,19 +95,19 @@ def __init__(self, hass, config_entry):

super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)

async def _async_update_data(self):
async def _async_update_data(self) -> MetWeatherData:
"""Fetch data from Met."""
try:
return await self.weather.fetch_data()
except Exception as err:
raise UpdateFailed(f"Update failed: {err}") from err

def track_home(self):
def track_home(self) -> None:
"""Start tracking changes to HA home setting."""
if self._unsub_track_home:
return

async def _async_update_weather_data(_event=None):
async def _async_update_weather_data(_event: str | None = None) -> None:
"""Update weather data."""
if self.weather.set_coordinates():
await self.async_refresh()
Expand All @@ -110,7 +116,7 @@ async def _async_update_weather_data(_event=None):
EVENT_CORE_CONFIG_UPDATE, _async_update_weather_data
)

def untrack_home(self):
def untrack_home(self) -> None:
"""Stop tracking changes to HA home setting."""
if self._unsub_track_home:
self._unsub_track_home()
Expand All @@ -120,18 +126,20 @@ def untrack_home(self):
class MetWeatherData:
"""Keep data for Met.no weather entities."""

def __init__(self, hass, config, is_metric):
def __init__(
self, hass: HomeAssistant, config: MappingProxyType[str, Any], is_metric: bool
) -> None:
"""Initialise the weather entity data."""
self.hass = hass
self._config = config
self._is_metric = is_metric
self._weather_data = None
self.current_weather_data = {}
self._weather_data: metno.MetWeatherData
self.current_weather_data: dict = {}
self.daily_forecast = None
self.hourly_forecast = None
self._coordinates = None
self._coordinates: dict[str, str] | None = None

def set_coordinates(self):
def set_coordinates(self) -> bool:
"""Weather data inialization - set the coordinates."""
if self._config.get(CONF_TRACK_HOME, False):
latitude = self.hass.config.latitude
Expand Down Expand Up @@ -161,7 +169,7 @@ def set_coordinates(self):
)
return True

async def fetch_data(self):
async def fetch_data(self) -> MetWeatherData:
"""Fetch data from API - (current weather and forecast)."""
await self._weather_data.fetching_data()
self.current_weather_data = self._weather_data.get_current_weather()
Expand Down
80 changes: 54 additions & 26 deletions homeassistant/components/met/weather.py
@@ -1,5 +1,9 @@
"""Support for Met.no weather service."""
from __future__ import annotations

import logging
from types import MappingProxyType
from typing import Any

import voluptuous as vol

Expand All @@ -13,9 +17,10 @@
ATTR_WEATHER_WIND_BEARING,
ATTR_WEATHER_WIND_SPEED,
PLATFORM_SCHEMA,
Forecast,
WeatherEntity,
)
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
CONF_ELEVATION,
CONF_LATITUDE,
Expand All @@ -29,9 +34,16 @@
PRESSURE_INHG,
TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
T,
)
from homeassistant.util.distance import convert as convert_distance
from homeassistant.util.pressure import convert as convert_pressure

Expand Down Expand Up @@ -67,7 +79,12 @@
)


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Met.no weather platform."""
_LOGGER.warning("Loading Met.no via platform config is deprecated")

Expand All @@ -84,7 +101,11 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
)


async def async_setup_entry(hass, config_entry, async_add_entities):
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Add a weather entity from a config_entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities(
Expand All @@ -110,20 +131,26 @@ def format_condition(condition: str) -> str:
class MetWeather(CoordinatorEntity, WeatherEntity):
"""Implementation of a Met.no weather condition."""

def __init__(self, coordinator, config, is_metric, hourly):
def __init__(
self,
coordinator: DataUpdateCoordinator[T],
config: MappingProxyType[str, Any],
is_metric: bool,
hourly: bool,
) -> None:
"""Initialise the platform with a data instance and site."""
super().__init__(coordinator)
self._config = config
self._is_metric = is_metric
self._hourly = hourly

@property
def track_home(self):
def track_home(self) -> (Any | bool):
"""Return if we are tracking home."""
return self._config.get(CONF_TRACK_HOME, False)

@property
def unique_id(self):
def unique_id(self) -> str:
"""Return unique ID."""
name_appendix = ""
if self._hourly:
Expand All @@ -134,7 +161,7 @@ def unique_id(self):
return f"{self._config[CONF_LATITUDE]}-{self._config[CONF_LONGITUDE]}{name_appendix}"

@property
def name(self):
def name(self) -> str:
"""Return the name of the sensor."""
name = self._config.get(CONF_NAME)
name_appendix = ""
Expand All @@ -155,25 +182,25 @@ def entity_registry_enabled_default(self) -> bool:
return not self._hourly

@property
def condition(self):
def condition(self) -> str | None:
"""Return the current condition."""
condition = self.coordinator.data.current_weather_data.get("condition")
return format_condition(condition)

@property
def temperature(self):
def temperature(self) -> float | None:
"""Return the temperature."""
return self.coordinator.data.current_weather_data.get(
ATTR_MAP[ATTR_WEATHER_TEMPERATURE]
)

@property
def temperature_unit(self):
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
return TEMP_CELSIUS

@property
def pressure(self):
def pressure(self) -> float | None:
"""Return the pressure."""
pressure_hpa = self.coordinator.data.current_weather_data.get(
ATTR_MAP[ATTR_WEATHER_PRESSURE]
Expand All @@ -184,14 +211,14 @@ def pressure(self):
return round(convert_pressure(pressure_hpa, PRESSURE_HPA, PRESSURE_INHG), 2)

@property
def humidity(self):
def humidity(self) -> float | None:
"""Return the humidity."""
return self.coordinator.data.current_weather_data.get(
ATTR_MAP[ATTR_WEATHER_HUMIDITY]
)

@property
def wind_speed(self):
def wind_speed(self) -> float | None:
"""Return the wind speed."""
speed_km_h = self.coordinator.data.current_weather_data.get(
ATTR_MAP[ATTR_WEATHER_WIND_SPEED]
Expand All @@ -203,26 +230,26 @@ def wind_speed(self):
return int(round(speed_mi_h))

@property
def wind_bearing(self):
def wind_bearing(self) -> float | str | None:
"""Return the wind direction."""
return self.coordinator.data.current_weather_data.get(
ATTR_MAP[ATTR_WEATHER_WIND_BEARING]
)

@property
def attribution(self):
def attribution(self) -> str:
"""Return the attribution."""
return ATTRIBUTION

@property
def forecast(self):
def forecast(self) -> list[Forecast] | None:
"""Return the forecast array."""
if self._hourly:
met_forecast = self.coordinator.data.hourly_forecast
else:
met_forecast = self.coordinator.data.daily_forecast
required_keys = {ATTR_FORECAST_TEMP, ATTR_FORECAST_TIME}
ha_forecast = []
ha_forecast: list[Forecast] = []
for met_item in met_forecast:
if not set(met_item).issuperset(required_keys):
continue
Expand All @@ -232,26 +259,27 @@ def forecast(self):
if met_item.get(v) is not None
}
if not self._is_metric and ATTR_FORECAST_PRECIPITATION in ha_item:
precip_inches = convert_distance(
ha_item[ATTR_FORECAST_PRECIPITATION],
LENGTH_MILLIMETERS,
LENGTH_INCHES,
)
if ha_item[ATTR_FORECAST_PRECIPITATION] is not None:
precip_inches = convert_distance(
ha_item[ATTR_FORECAST_PRECIPITATION],
LENGTH_MILLIMETERS,
LENGTH_INCHES,
)
ha_item[ATTR_FORECAST_PRECIPITATION] = round(precip_inches, 2)
if ha_item.get(ATTR_FORECAST_CONDITION):
ha_item[ATTR_FORECAST_CONDITION] = format_condition(
ha_item[ATTR_FORECAST_CONDITION]
)
ha_forecast.append(ha_item)
ha_forecast.append(ha_item) # type: ignore[arg-type]
return ha_forecast

@property
def device_info(self):
def device_info(self) -> DeviceInfo:
"""Device info."""
return DeviceInfo(
default_name="Forecast",
entry_type="service",
identifiers={(DOMAIN,)},
identifiers={(DOMAIN,)}, # type: ignore[arg-type]
manufacturer="Met.no",
model="Forecast",
)