Enforce strict typing for RainMachine (#53414)
This commit is contained in:
parent
5483ab0cda
commit
a6b34924be
9 changed files with 83 additions and 61 deletions
|
@ -74,6 +74,7 @@ homeassistant.components.openuv.*
|
|||
homeassistant.components.persistent_notification.*
|
||||
homeassistant.components.pi_hole.*
|
||||
homeassistant.components.proximity.*
|
||||
homeassistant.components.rainmachine.*
|
||||
homeassistant.components.recorder.purge
|
||||
homeassistant.components.recorder.repack
|
||||
homeassistant.components.recorder.statistics
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
"""Support for RainMachine devices."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
from functools import partial
|
||||
from typing import Any
|
||||
|
||||
from regenmaschine import Client
|
||||
from regenmaschine.controller import Controller
|
||||
|
@ -93,7 +96,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
entry.entry_id
|
||||
] = get_client_controller(client)
|
||||
|
||||
entry_updates = {}
|
||||
entry_updates: dict[str, Any] = {}
|
||||
if not entry.unique_id or is_ip_address(entry.unique_id):
|
||||
# If the config entry doesn't already have a unique ID, set one:
|
||||
entry_updates["unique_id"] = controller.mac
|
||||
|
@ -111,23 +114,24 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
|
||||
async def async_update(api_category: str) -> dict:
|
||||
"""Update the appropriate API data based on a category."""
|
||||
data: dict = {}
|
||||
|
||||
try:
|
||||
if api_category == DATA_PROGRAMS:
|
||||
return await controller.programs.all(include_inactive=True)
|
||||
|
||||
if api_category == DATA_PROVISION_SETTINGS:
|
||||
return await controller.provisioning.settings()
|
||||
|
||||
if api_category == DATA_RESTRICTIONS_CURRENT:
|
||||
return await controller.restrictions.current()
|
||||
|
||||
if api_category == DATA_RESTRICTIONS_UNIVERSAL:
|
||||
return await controller.restrictions.universal()
|
||||
|
||||
return await controller.zones.all(details=True, include_inactive=True)
|
||||
data = await controller.programs.all(include_inactive=True)
|
||||
elif api_category == DATA_PROVISION_SETTINGS:
|
||||
data = await controller.provisioning.settings()
|
||||
elif api_category == DATA_RESTRICTIONS_CURRENT:
|
||||
data = await controller.restrictions.current()
|
||||
elif api_category == DATA_RESTRICTIONS_UNIVERSAL:
|
||||
data = await controller.restrictions.universal()
|
||||
else:
|
||||
data = await controller.zones.all(details=True, include_inactive=True)
|
||||
except RainMachineError as err:
|
||||
raise UpdateFailed(err) from err
|
||||
|
||||
return data
|
||||
|
||||
controller_init_tasks = []
|
||||
for api_category in (
|
||||
DATA_PROGRAMS,
|
||||
|
@ -201,12 +205,12 @@ class RainMachineEntity(CoordinatorEntity):
|
|||
self._entity_type = entity_type
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self):
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Respond to a DataUpdateCoordinator update."""
|
||||
self.update_from_latest_data()
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Handle entity which will be added."""
|
||||
await super().async_added_to_hass()
|
||||
self.update_from_latest_data()
|
||||
|
|
|
@ -1,31 +1,33 @@
|
|||
"""Config flow to configure the RainMachine component."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from regenmaschine import Client
|
||||
from regenmaschine.controller import Controller
|
||||
from regenmaschine.errors import RainMachineError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_PORT, CONF_SSL
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||
|
||||
from .const import CONF_ZONE_RUN_TIME, DEFAULT_PORT, DEFAULT_ZONE_RUN, DOMAIN
|
||||
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_IP_ADDRESS): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): int,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def get_client_controller(client):
|
||||
@callback
|
||||
def get_client_controller(client: Client) -> Controller:
|
||||
"""Return the first local controller."""
|
||||
return next(iter(client.controllers.values()))
|
||||
|
||||
|
||||
async def async_get_controller(hass, ip_address, password, port, ssl):
|
||||
async def async_get_controller(
|
||||
hass: HomeAssistant, ip_address: str, password: str, port: int, ssl: bool
|
||||
) -> Controller | None:
|
||||
"""Auth and fetch the mac address from the controller."""
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
client = Client(session=websession)
|
||||
|
@ -42,21 +44,23 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize config flow."""
|
||||
self.discovered_ip_address = None
|
||||
discovered_ip_address: str | None = None
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
def async_get_options_flow(
|
||||
config_entry: ConfigEntry,
|
||||
) -> RainMachineOptionsFlowHandler:
|
||||
"""Define the config flow to handle options."""
|
||||
return RainMachineOptionsFlowHandler(config_entry)
|
||||
|
||||
async def async_step_homekit(self, discovery_info):
|
||||
async def async_step_homekit(self, discovery_info: DiscoveryInfoType) -> FlowResult:
|
||||
"""Handle a flow initialized by homekit discovery."""
|
||||
return await self.async_step_zeroconf(discovery_info)
|
||||
|
||||
async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType):
|
||||
async def async_step_zeroconf(
|
||||
self, discovery_info: DiscoveryInfoType
|
||||
) -> FlowResult:
|
||||
"""Handle discovery via zeroconf."""
|
||||
ip_address = discovery_info["host"]
|
||||
|
||||
|
@ -86,7 +90,7 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
return await self.async_step_user()
|
||||
|
||||
@callback
|
||||
def _async_generate_schema(self):
|
||||
def _async_generate_schema(self) -> vol.Schema:
|
||||
"""Generate schema."""
|
||||
return vol.Schema(
|
||||
{
|
||||
|
@ -96,7 +100,9 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
}
|
||||
)
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the start of the config flow."""
|
||||
errors = {}
|
||||
if user_input:
|
||||
|
@ -134,6 +140,7 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
|
||||
if self.discovered_ip_address:
|
||||
self.context["title_placeholders"] = {"ip": self.discovered_ip_address}
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=self._async_generate_schema(), errors=errors
|
||||
)
|
||||
|
@ -142,11 +149,13 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
class RainMachineOptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Handle a RainMachine options flow."""
|
||||
|
||||
def __init__(self, config_entry):
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
"""Initialize."""
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Manage the options."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "RainMachine",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/rainmachine",
|
||||
"requirements": ["regenmaschine==3.0.0"],
|
||||
"requirements": ["regenmaschine==3.1.5"],
|
||||
"codeowners": ["@bachya"],
|
||||
"iot_class": "local_polling",
|
||||
"homekit": {
|
||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
|
||||
from collections.abc import Coroutine
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from regenmaschine.controller import Controller
|
||||
from regenmaschine.errors import RequestError
|
||||
|
@ -165,7 +166,8 @@ async def async_setup_entry(
|
|||
]
|
||||
zones_coordinator = hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id][DATA_ZONES]
|
||||
|
||||
entities = []
|
||||
entities: list[RainMachineProgram | RainMachineZone] = []
|
||||
|
||||
for uid, program in programs_coordinator.data.items():
|
||||
entities.append(
|
||||
RainMachineProgram(
|
||||
|
@ -241,57 +243,57 @@ class RainMachineSwitch(RainMachineEntity, SwitchEntity):
|
|||
async_update_programs_and_zones(self.hass, self._entry)
|
||||
)
|
||||
|
||||
async def async_disable_program(self, *, program_id):
|
||||
async def async_disable_program(self, *, program_id: int) -> None:
|
||||
"""Disable a program."""
|
||||
await self._controller.programs.disable(program_id)
|
||||
await async_update_programs_and_zones(self.hass, self._entry)
|
||||
|
||||
async def async_disable_zone(self, *, zone_id):
|
||||
async def async_disable_zone(self, *, zone_id: int) -> None:
|
||||
"""Disable a zone."""
|
||||
await self._controller.zones.disable(zone_id)
|
||||
await async_update_programs_and_zones(self.hass, self._entry)
|
||||
|
||||
async def async_enable_program(self, *, program_id):
|
||||
async def async_enable_program(self, *, program_id: int) -> None:
|
||||
"""Enable a program."""
|
||||
await self._controller.programs.enable(program_id)
|
||||
await async_update_programs_and_zones(self.hass, self._entry)
|
||||
|
||||
async def async_enable_zone(self, *, zone_id):
|
||||
async def async_enable_zone(self, *, zone_id: int) -> None:
|
||||
"""Enable a zone."""
|
||||
await self._controller.zones.enable(zone_id)
|
||||
await async_update_programs_and_zones(self.hass, self._entry)
|
||||
|
||||
async def async_pause_watering(self, *, seconds):
|
||||
async def async_pause_watering(self, *, seconds: int) -> None:
|
||||
"""Pause watering for a set number of seconds."""
|
||||
await self._controller.watering.pause_all(seconds)
|
||||
await async_update_programs_and_zones(self.hass, self._entry)
|
||||
|
||||
async def async_start_program(self, *, program_id):
|
||||
async def async_start_program(self, *, program_id: int) -> None:
|
||||
"""Start a particular program."""
|
||||
await self._controller.programs.start(program_id)
|
||||
await async_update_programs_and_zones(self.hass, self._entry)
|
||||
|
||||
async def async_start_zone(self, *, zone_id, zone_run_time):
|
||||
async def async_start_zone(self, *, zone_id: int, zone_run_time: int) -> None:
|
||||
"""Start a particular zone for a certain amount of time."""
|
||||
await self._controller.zones.start(zone_id, zone_run_time)
|
||||
await async_update_programs_and_zones(self.hass, self._entry)
|
||||
|
||||
async def async_stop_all(self):
|
||||
async def async_stop_all(self) -> None:
|
||||
"""Stop all watering."""
|
||||
await self._controller.watering.stop_all()
|
||||
await async_update_programs_and_zones(self.hass, self._entry)
|
||||
|
||||
async def async_stop_program(self, *, program_id):
|
||||
async def async_stop_program(self, *, program_id: int) -> None:
|
||||
"""Stop a program."""
|
||||
await self._controller.programs.stop(program_id)
|
||||
await async_update_programs_and_zones(self.hass, self._entry)
|
||||
|
||||
async def async_stop_zone(self, *, zone_id):
|
||||
async def async_stop_zone(self, *, zone_id: int) -> None:
|
||||
"""Stop a zone."""
|
||||
await self._controller.zones.stop(zone_id)
|
||||
await async_update_programs_and_zones(self.hass, self._entry)
|
||||
|
||||
async def async_unpause_watering(self):
|
||||
async def async_unpause_watering(self) -> None:
|
||||
"""Unpause watering."""
|
||||
await self._controller.watering.unpause_all()
|
||||
await async_update_programs_and_zones(self.hass, self._entry)
|
||||
|
@ -311,13 +313,13 @@ class RainMachineProgram(RainMachineSwitch):
|
|||
"""Return a list of active zones associated with this program."""
|
||||
return [z for z in self._data["wateringTimes"] if z["active"]]
|
||||
|
||||
async def async_turn_off(self, **kwargs) -> None:
|
||||
async def async_turn_off(self, **kwargs: dict[str, Any]) -> None:
|
||||
"""Turn the program off."""
|
||||
await self._async_run_switch_coroutine(
|
||||
self._controller.programs.stop(self._uid)
|
||||
)
|
||||
|
||||
async def async_turn_on(self, **kwargs) -> None:
|
||||
async def async_turn_on(self, **kwargs: dict[str, Any]) -> None:
|
||||
"""Turn the program on."""
|
||||
await self._async_run_switch_coroutine(
|
||||
self._controller.programs.start(self._uid)
|
||||
|
@ -330,13 +332,12 @@ class RainMachineProgram(RainMachineSwitch):
|
|||
|
||||
self._attr_is_on = bool(self._data["status"])
|
||||
|
||||
next_run: str | None = None
|
||||
if self._data.get("nextRun") is not None:
|
||||
next_run = datetime.strptime(
|
||||
f"{self._data['nextRun']} {self._data['startTime']}",
|
||||
"%Y-%m-%d %H:%M",
|
||||
).isoformat()
|
||||
else:
|
||||
next_run = None
|
||||
|
||||
self._attr_extra_state_attributes.update(
|
||||
{
|
||||
|
@ -352,11 +353,11 @@ class RainMachineProgram(RainMachineSwitch):
|
|||
class RainMachineZone(RainMachineSwitch):
|
||||
"""A RainMachine zone."""
|
||||
|
||||
async def async_turn_off(self, **kwargs) -> None:
|
||||
async def async_turn_off(self, **kwargs: dict[str, Any]) -> None:
|
||||
"""Turn the zone off."""
|
||||
await self._async_run_switch_coroutine(self._controller.zones.stop(self._uid))
|
||||
|
||||
async def async_turn_on(self, **kwargs) -> None:
|
||||
async def async_turn_on(self, **kwargs: dict[str, Any]) -> None:
|
||||
"""Turn the zone on."""
|
||||
await self._async_run_switch_coroutine(
|
||||
self._controller.zones.start(
|
||||
|
|
14
mypy.ini
14
mypy.ini
|
@ -825,6 +825,17 @@ no_implicit_optional = true
|
|||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.rainmachine.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_decorators = true
|
||||
disallow_untyped_defs = true
|
||||
no_implicit_optional = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.recorder.purge]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
@ -1535,9 +1546,6 @@ ignore_errors = true
|
|||
[mypy-homeassistant.components.rachio.*]
|
||||
ignore_errors = true
|
||||
|
||||
[mypy-homeassistant.components.rainmachine.*]
|
||||
ignore_errors = true
|
||||
|
||||
[mypy-homeassistant.components.recollect_waste.*]
|
||||
ignore_errors = true
|
||||
|
||||
|
|
|
@ -2010,7 +2010,7 @@ raincloudy==0.0.7
|
|||
raspyrfm-client==1.2.8
|
||||
|
||||
# homeassistant.components.rainmachine
|
||||
regenmaschine==3.0.0
|
||||
regenmaschine==3.1.5
|
||||
|
||||
# homeassistant.components.python_script
|
||||
restrictedpython==5.1
|
||||
|
|
|
@ -1107,7 +1107,7 @@ pyzerproc==0.4.8
|
|||
rachiopy==1.0.3
|
||||
|
||||
# homeassistant.components.rainmachine
|
||||
regenmaschine==3.0.0
|
||||
regenmaschine==3.1.5
|
||||
|
||||
# homeassistant.components.python_script
|
||||
restrictedpython==5.1
|
||||
|
|
|
@ -137,7 +137,6 @@ IGNORED_MODULES: Final[list[str]] = [
|
|||
"homeassistant.components.profiler.*",
|
||||
"homeassistant.components.proxmoxve.*",
|
||||
"homeassistant.components.rachio.*",
|
||||
"homeassistant.components.rainmachine.*",
|
||||
"homeassistant.components.recollect_waste.*",
|
||||
"homeassistant.components.reddit.*",
|
||||
"homeassistant.components.ring.*",
|
||||
|
|
Loading…
Add table
Reference in a new issue