Make get_module return typed module (#892)

Passing in a string still works and returns either `IotModule` or
`SmartModule` type when called on `IotDevice` or `SmartDevice`
respectively. When calling on `Device` will return `Module` type.

Passing in a module type is then typed to that module, i.e.:
```py
smartdev.get_module(FanModule)  # type is FanModule
smartdev.get_module("FanModule")  # type is SmartModule
```
Only thing this doesn't do is check that you can't pass an `IotModule`
to a `SmartDevice.get_module()`. However there is a runtime check which
will return null if the passed `ModuleType` is not a subclass of
`SmartModule`.

Many thanks to @cdce8p for helping with this.
This commit is contained in:
Steven B
2024-05-03 16:01:21 +01:00
committed by GitHub
parent 530fb841b0
commit c5d65b624b
8 changed files with 114 additions and 16 deletions

View File

@@ -6,7 +6,7 @@ import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Mapping, Sequence
from typing import Any, Mapping, Sequence, overload
from .credentials import Credentials
from .device_type import DeviceType
@@ -15,7 +15,7 @@ from .emeterstatus import EmeterStatus
from .exceptions import KasaException
from .feature import Feature
from .iotprotocol import IotProtocol
from .module import Module
from .module import Module, ModuleT
from .protocol import BaseProtocol
from .xortransport import XorTransport
@@ -116,6 +116,18 @@ class Device(ABC):
def modules(self) -> Mapping[str, Module]:
"""Return the device modules."""
@overload
@abstractmethod
def get_module(self, module_type: type[ModuleT]) -> ModuleT | None: ...
@overload
@abstractmethod
def get_module(self, module_type: str) -> Module | None: ...
@abstractmethod
def get_module(self, module_type: type[ModuleT] | str) -> ModuleT | Module | None:
"""Return the module from the device modules or None if not present."""
@property
@abstractmethod
def is_on(self) -> bool: