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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

beta: Enable mypy #423

Merged
merged 1 commit into from
Dec 27, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 19 additions & 18 deletions custom_components/keymaster/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
from __future__ import annotations

import asyncio
from collections.abc import Mapping
from collections.abc import MutableMapping
from datetime import datetime, timedelta
import functools
import logging
from typing import Any

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
Expand Down Expand Up @@ -54,8 +55,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b

updated_config = config_entry.data.copy()

updated_config[CONF_SLOTS] = int(updated_config.get(CONF_SLOTS))
updated_config[CONF_START] = int(updated_config.get(CONF_START))
# updated_config[CONF_SLOTS] = int(updated_config[CONF_SLOTS])
# updated_config[CONF_START] = int(updated_config[CONF_START])

if config_entry.data.get(CONF_PARENT) in {None, "(none)"}:
updated_config[CONF_PARENT] = None
Expand All @@ -77,10 +78,10 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
)
elif isinstance(
updated_config.get(CONF_NOTIFY_SCRIPT_NAME), str
) and updated_config.get(CONF_NOTIFY_SCRIPT_NAME).startswith("script."):
updated_config[CONF_NOTIFY_SCRIPT_NAME] = updated_config.get(
) and updated_config[CONF_NOTIFY_SCRIPT_NAME].startswith("script."):
updated_config[CONF_NOTIFY_SCRIPT_NAME] = updated_config[
CONF_NOTIFY_SCRIPT_NAME
).split(".", maxsplit=1)[1]
].split(".", maxsplit=1)[1]

if updated_config.get(CONF_DOOR_SENSOR_ENTITY_ID) == DEFAULT_DOOR_SENSOR:
updated_config[CONF_DOOR_SENSOR_ENTITY_ID] = None
Expand Down Expand Up @@ -114,7 +115,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b

device_registry = dr.async_get(hass)

via_device: str | None = None
via_device: tuple[str, Any] | None = None
if config_entry.data.get(CONF_PARENT_ENTRY_ID):
via_device = (DOMAIN, config_entry.data.get(CONF_PARENT_ENTRY_ID))

Expand All @@ -135,12 +136,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b

# _LOGGER.debug(f"[init async_setup_entry] device: {device}")

code_slots: Mapping[int, KeymasterCodeSlot] = {}
code_slots: MutableMapping[int, KeymasterCodeSlot] = {}
for x in range(
config_entry.data[CONF_START],
config_entry.data[CONF_START] + config_entry.data[CONF_SLOTS],
):
dow_slots: Mapping[int, KeymasterCodeSlotDayOfWeek] = {}
dow_slots: MutableMapping[int, KeymasterCodeSlotDayOfWeek] = {}
for i, dow in enumerate(
[
"Monday",
Expand All @@ -158,8 +159,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
code_slots[x] = KeymasterCodeSlot(number=x, accesslimit_day_of_week=dow_slots)

kmlock = KeymasterLock(
lock_name=config_entry.data.get(CONF_LOCK_NAME),
lock_entity_id=config_entry.data.get(CONF_LOCK_ENTITY_ID),
lock_name=config_entry.data[CONF_LOCK_NAME],
lock_entity_id=config_entry.data[CONF_LOCK_ENTITY_ID],
keymaster_config_entry_id=config_entry.entry_id,
alarm_level_or_user_code_entity_id=config_entry.data.get(
CONF_ALARM_LEVEL_OR_USER_CODE_ENTITY_ID
Expand All @@ -168,8 +169,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
CONF_ALARM_TYPE_OR_ACCESS_CONTROL_ENTITY_ID
),
door_sensor_entity_id=config_entry.data.get(CONF_DOOR_SENSOR_ENTITY_ID),
number_of_code_slots=config_entry.data.get(CONF_SLOTS),
starting_code_slot=config_entry.data.get(CONF_START),
number_of_code_slots=config_entry.data[CONF_SLOTS],
starting_code_slot=config_entry.data[CONF_START],
code_slots=code_slots,
parent_name=config_entry.data.get(CONF_PARENT),
parent_config_entry_id=config_entry.data.get(CONF_PARENT_ENTRY_ID),
Expand All @@ -184,12 +185,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
await generate_lovelace(
hass=hass,
kmlock_name=config_entry.data.get(CONF_LOCK_NAME),
kmlock_name=config_entry.data[CONF_LOCK_NAME],
keymaster_config_entry_id=config_entry.entry_id,
parent_config_entry_id=config_entry.data.get(CONF_PARENT_ENTRY_ID),
code_slot_start=config_entry.data.get(CONF_START),
code_slots=config_entry.data.get(CONF_SLOTS),
lock_entity=config_entry.data.get(CONF_LOCK_ENTITY_ID),
code_slot_start=config_entry.data[CONF_START],
code_slots=config_entry.data[CONF_SLOTS],
lock_entity=config_entry.data[CONF_LOCK_ENTITY_ID],
door_sensor=config_entry.data.get(CONF_DOOR_SENSOR_ENTITY_ID),
)

Expand All @@ -207,7 +208,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b

async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Handle removal of an entry."""
lockname: str = config_entry.data.get(CONF_LOCK_NAME)
lockname: str = config_entry.data[CONF_LOCK_NAME]
_LOGGER.info("Unloading %s", lockname)
unload_ok: bool = all(
await asyncio.gather(
Expand Down
7 changes: 4 additions & 3 deletions custom_components/keymaster/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from .coordinator import KeymasterCoordinator
from .entity import KeymasterEntity, KeymasterEntityDescription
from .helpers import async_using_zwave_js
from .lock import KeymasterLock

_LOGGER: logging.Logger = logging.getLogger(__name__)

Expand All @@ -29,7 +28,7 @@ async def async_setup_entry(
):
"""Create the keymaster Binary Sensors."""
coordinator: KeymasterCoordinator = hass.data[DOMAIN][COORDINATOR]
kmlock: KeymasterLock = await coordinator.get_lock_by_config_entry_id(
kmlock = await coordinator.get_lock_by_config_entry_id(
config_entry.entry_id
)
entities: list = []
Expand Down Expand Up @@ -74,7 +73,7 @@ async def async_setup_entry(
return True


@dataclass(kw_only=True)
@dataclass(frozen=True, kw_only=True)
class KeymasterBinarySensorEntityDescription(
KeymasterEntityDescription, BinarySensorEntityDescription
):
Expand All @@ -84,6 +83,8 @@ class KeymasterBinarySensorEntityDescription(
class KeymasterBinarySensor(KeymasterEntity, BinarySensorEntity):
"""Keymaster Binary Sensor Class."""

entity_description: KeymasterBinarySensorEntityDescription

def __init__(
self,
entity_description: KeymasterBinarySensorEntityDescription,
Expand Down
12 changes: 7 additions & 5 deletions custom_components/keymaster/button.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Support for keymaster buttons."""

from collections.abc import MutableMapping
from dataclasses import dataclass
import logging

Expand Down Expand Up @@ -56,10 +57,9 @@ async def async_setup_entry(
]
)
async_add_entities(entities, True)
return True


@dataclass(kw_only=True)
@dataclass(frozen=True, kw_only=True)
class KeymasterButtonEntityDescription(
KeymasterEntityDescription, ButtonEntityDescription
):
Expand All @@ -69,6 +69,8 @@ class KeymasterButtonEntityDescription(
class KeymasterButton(KeymasterEntity, ButtonEntity):
"""Representation of a keymaster button."""

entity_description: KeymasterButtonEntityDescription

def __init__(
self,
entity_description: KeymasterButtonEntityDescription,
Expand All @@ -81,13 +83,13 @@ def __init__(

@callback
def _handle_coordinator_update(self) -> None:
if not self._kmlock.connected:
if not self._kmlock or not self._kmlock.connected:
self._attr_available = False
self.async_write_ha_state()
return

if (
".code_slots" in self._property
".code_slots" in self._property and isinstance(self._kmlock.code_slots, MutableMapping)
and self._code_slot not in self._kmlock.code_slots
):
self._attr_available = False
Expand All @@ -103,7 +105,7 @@ async def async_press(self) -> None:
await self.coordinator.reset_lock(
config_entry_id=self._config_entry.entry_id,
)
elif self._property.endswith(".reset"):
elif self._property.endswith(".reset") and self._code_slot:
await self.coordinator.reset_code_slot(
config_entry_id=self._config_entry.entry_id,
code_slot=self._code_slot,
Expand Down
51 changes: 28 additions & 23 deletions custom_components/keymaster/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from collections.abc import Mapping
from collections.abc import MutableMapping
import contextlib
import logging
from typing import TYPE_CHECKING, Any
Expand All @@ -14,6 +14,7 @@
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
from homeassistant.components.script import DOMAIN as SCRIPT_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.config_entries import ConfigFlowResult
from homeassistant.core import HomeAssistant, callback
from homeassistant.util import slugify

Expand Down Expand Up @@ -44,11 +45,13 @@
_LOGGER: logging.Logger = logging.getLogger(__name__)


class KeymasterFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
class KeymasterFlowHandler(config_entries.ConfigFlow):
"""Config flow for keymaster."""

VERSION = 3
DEFAULTS: Mapping[str, Any] = {
domain: str = DOMAIN

VERSION: int = 3
DEFAULTS: MutableMapping[str, Any] = {
CONF_SLOTS: DEFAULT_CODE_SLOTS,
CONF_START: DEFAULT_START,
CONF_DOOR_SENSOR_ENTITY_ID: DEFAULT_DOOR_SENSOR,
Expand All @@ -57,7 +60,7 @@ class KeymasterFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
CONF_HIDE_PINS: DEFAULT_HIDE_PINS,
}

async def get_unique_name_error(self, user_input) -> Mapping[str, str]:
async def get_unique_name_error(self, user_input) -> MutableMapping[str, str]:
"""Check if name is unique, returning dictionary error if so."""
# Validate that lock name is unique
existing_entry = await self.async_set_unique_id(
Expand All @@ -68,13 +71,13 @@ async def get_unique_name_error(self, user_input) -> Mapping[str, str]:
return {}

async def async_step_user(
self, user_input: Mapping[str, Any] | None = None
) -> Mapping[str, Any]:
self, user_input: MutableMapping[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
return await _start_config_flow(
cls=self,
step_id="user",
title=user_input[CONF_LOCK_NAME] if user_input else None,
title=user_input[CONF_LOCK_NAME] if user_input else "",
user_input=user_input,
defaults=self.DEFAULTS,
)
Expand All @@ -95,7 +98,7 @@ def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize."""
self.config_entry = config_entry

async def get_unique_name_error(self, user_input) -> Mapping[str, str]:
async def get_unique_name_error(self, user_input) -> MutableMapping[str, str]:
"""Check if name is unique, returning dictionary error if so."""
# If lock name has changed, make sure new name isn't already being used
# otherwise show an error
Expand All @@ -106,15 +109,15 @@ async def get_unique_name_error(self, user_input) -> Mapping[str, str]:
return {}

async def async_step_init(
self, user_input: Mapping[str, Any] | None = None
) -> Mapping[str, Any]:
self, user_input: MutableMapping[str, Any] | None = None
) -> MutableMapping[str, Any]:
"""Handle a flow initialized by the user."""
return await _start_config_flow(
cls=self,
step_id="init",
title="",
user_input=user_input,
defaults=self.config_entry.data,
defaults=dict(self.config_entry.data),
entry_id=self.config_entry.entry_id,
)

Expand Down Expand Up @@ -182,16 +185,16 @@ def _get_locks_in_use(hass: HomeAssistant, exclude: str | None = None) -> list[s

def _get_schema(
hass: HomeAssistant,
user_input: Mapping[str, Any] | None,
default_dict: Mapping[str, Any],
user_input: MutableMapping[str, Any] | None,
default_dict: MutableMapping[str, Any],
entry_id: str | None = None,
) -> vol.Schema:
"""Get a schema using the default_dict as a backup."""
if user_input is None:
user_input = {}

if CONF_PARENT in default_dict and default_dict[CONF_PARENT] is None:
check_dict: Mapping[str, Any] = default_dict.copy()
check_dict: MutableMapping[str, Any] = dict(default_dict).copy()
check_dict.pop(CONF_PARENT, None)
default_dict = check_dict

Expand Down Expand Up @@ -310,13 +313,14 @@ async def _start_config_flow(
cls: KeymasterFlowHandler | KeymasterOptionsFlow,
step_id: str,
title: str,
user_input: Mapping[str, Any],
defaults: Mapping[str, Any] | None = None,
user_input: MutableMapping[str, Any] | None,
defaults: MutableMapping[str, Any] | None = None,
entry_id: str | None = None,
):
"""Start a config flow."""
errors = {}
description_placeholders = {}
errors: dict[str, Any] = {}
description_placeholders: dict[str, Any] = {}
defaults = defaults or {}

if user_input is not None:
_LOGGER.debug(
Expand All @@ -325,8 +329,8 @@ async def _start_config_flow(
user_input,
errors,
)
user_input[CONF_SLOTS] = int(user_input.get(CONF_SLOTS))
user_input[CONF_START] = int(user_input.get(CONF_START))
user_input[CONF_SLOTS] = int(user_input[CONF_SLOTS])
user_input[CONF_START] = int(user_input[CONF_START])

# Convert (none) to None
if user_input[CONF_PARENT] == "(none)":
Expand All @@ -341,8 +345,9 @@ async def _start_config_flow(
step_id,
user_input,
)
if step_id == "user":
if step_id == "user" or not entry_id:
return cls.async_create_entry(title=title, data=user_input)
assert isinstance(cls, KeymasterOptionsFlow)
cls.hass.config_entries.async_update_entry(
cls.config_entry, data=user_input
)
Expand All @@ -351,7 +356,7 @@ async def _start_config_flow(

return cls.async_show_form(
step_id=step_id,
data_schema=_get_schema(cls.hass, user_input, defaults, entry_id),
data_schema=_get_schema(hass=cls.hass, user_input=user_input, default_dict=defaults, entry_id=entry_id),
errors=errors,
description_placeholders=description_placeholders,
)
3 changes: 2 additions & 1 deletion custom_components/keymaster/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
]
THROTTLE_SECONDS: int = 5
SYNC_STATUS_THRESHOLD: int = 15
QUICK_REFRESH_SECONDS: int = 15

# hass.data attributes
CHILD_LOCKS = "child_locks"
Expand Down Expand Up @@ -128,7 +129,7 @@
},
}

LOCK_STATE_MAP = {
LOCK_STATE_MAP: dict[str, dict[str, int]] = {
ALARM_TYPE: {
LockState.LOCKED: 24,
LockState.UNLOCKED: 25,
Expand Down
Loading
Loading