Skip to content

Commit 6453bfe

Browse files
authored
Convert codebase to pass mypy checks (rytilahti#1046)
* Convert codebase to pass mypy checks This involves changes throughout the code base to make mypy happy, which involved some minor refactoring in parts of the codebase. This PR also adapts the CI & the pre-commit hooks to use mypy. * Add mypy to dev dependencies * vacuum: make carpetcleaningmode optional * enable mypy checks for github actions * fix linting * run mypy only on cpython as typed_ast doesn't build on pypy
1 parent dd0124c commit 6453bfe

38 files changed

+438
-300
lines changed

Diff for: .github/workflows/ci.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ jobs:
4444
- name: "Documentation build (sphinx)"
4545
run: |
4646
poetry run sphinx-build docs/ generated_docs
47-
# - name: "Typing checks (mypy)"
48-
# run: |
49-
# poetry run pre-commit run mypy --all-files
47+
- name: "Typing checks (mypy)"
48+
run: |
49+
poetry run pre-commit run mypy --all-files
5050
5151
tests:
5252
name: "Python ${{ matrix.python-version}} on ${{ matrix.os }}"

Diff for: .pre-commit-config.yaml

+4-4
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ repos:
4747
args: [-x, 'tests']
4848

4949

50-
#- repo: https://github.com/pre-commit/mirrors-mypy
51-
# rev: v0.740
52-
# hooks:
53-
# - id: mypy
50+
- repo: https://github.com/pre-commit/mirrors-mypy
51+
rev: v0.812
52+
hooks:
53+
- id: mypy
5454
# args: [--no-strict-optional, --ignore-missing-imports]

Diff for: devtools/containers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class Property(DataClassJsonMixin):
6060

6161
value_list: Optional[List[Dict[str, Any]]] = field(
6262
default_factory=list, metadata=config(field_name="value-list")
63-
)
63+
) # type: ignore
6464
value_range: Optional[List[int]] = field(
6565
default=None, metadata=config(field_name="value-range")
6666
)

Diff for: docs/conf.py

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
# add these directories to sys.path here. If the directory is relative to the
1818
# documentation root, use os.path.abspath to make it absolute, like shown here.
1919
#
20+
# type: ignore # ignoring for mypy
2021
import os
2122
import sys
2223

Diff for: miio/airconditioningcompanion.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -312,14 +312,14 @@ def send_ir_code(self, model: str, code: str, slot: int = 0):
312312
:param int slot: Unknown internal register or slot
313313
"""
314314
try:
315-
model = bytes.fromhex(model)
315+
model_bytes = bytes.fromhex(model)
316316
except ValueError:
317317
raise AirConditioningCompanionException(
318318
"Invalid model. A hexadecimal string must be provided"
319319
)
320320

321321
try:
322-
code = bytes.fromhex(code)
322+
code_bytes = bytes.fromhex(code)
323323
except ValueError:
324324
raise AirConditioningCompanionException(
325325
"Invalid code. A hexadecimal string must be provided"
@@ -328,23 +328,23 @@ def send_ir_code(self, model: str, code: str, slot: int = 0):
328328
if slot < 0 or slot > 134:
329329
raise AirConditioningCompanionException("Invalid slot: %s" % slot)
330330

331-
slot = bytes([121 + slot])
331+
slot_bytes = bytes([121 + slot])
332332

333333
# FE + 0487 + 00007145 + 9470 + 1FFF + 7F + FF + 06 + 0042 + 27 + 4E + 0025002D008500AC01...
334-
command = (
335-
code[0:1]
336-
+ model[2:8]
334+
command_bytes = (
335+
code_bytes[0:1]
336+
+ model_bytes[2:8]
337337
+ b"\x94\x70\x1F\xFF"
338-
+ slot
338+
+ slot_bytes
339339
+ b"\xFF"
340-
+ code[13:16]
340+
+ code_bytes[13:16]
341341
+ b"\x27"
342342
)
343343

344-
checksum = sum(command) & 0xFF
345-
command = command + bytes([checksum]) + code[18:]
344+
checksum = sum(command_bytes) & 0xFF
345+
command_bytes = command_bytes + bytes([checksum]) + code_bytes[18:]
346346

347-
return self.send("send_ir_code", [command.hex().upper()])
347+
return self.send("send_ir_code", [command_bytes.hex().upper()])
348348

349349
@command(
350350
click.argument("command", type=str),

Diff for: miio/airdehumidifier.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ def __init__(
174174
else:
175175
self.model = MODEL_DEHUMIDIFIER_V1
176176

177-
self.device_info = None
177+
self.device_info: DeviceInfo
178178

179179
@command(
180180
default_output=format_output(

Diff for: miio/airfilter_util.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import enum
22
import re
3-
from typing import Optional
3+
from typing import Dict, Optional
44

55

66
class FilterType(enum.Enum):
@@ -20,7 +20,7 @@ class FilterType(enum.Enum):
2020
class FilterTypeUtil:
2121
"""Utility class for determining xiaomi air filter type."""
2222

23-
_filter_type_cache = {}
23+
_filter_type_cache: Dict[str, Optional[FilterType]] = {}
2424

2525
def determine_filter_type(
2626
self, rfid_tag: Optional[str], product_id: Optional[str]
@@ -37,7 +37,7 @@ def determine_filter_type(
3737
if product_id is None:
3838
return FilterType.Regular
3939

40-
ft = self._filter_type_cache.get(product_id, None)
40+
ft = self._filter_type_cache.get(product_id)
4141
if ft is None:
4242
for filter_re, filter_type in FILTER_TYPE_RE:
4343
if filter_re.match(product_id):

Diff for: miio/airhumidifier.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ def firmware_version(self) -> str:
158158
159159
For example 1.2.9_5033.
160160
"""
161+
if self.device_info.firmware_version is None:
162+
raise AirHumidifierException("Missing firmware information")
163+
161164
return self.device_info.firmware_version
162165

163166
@property
@@ -240,7 +243,8 @@ def __init__(
240243
else:
241244
self.model = MODEL_HUMIDIFIER_V1
242245

243-
self.device_info = None
246+
# TODO: convert to use generic device info in the future
247+
self.device_info: Optional[DeviceInfo] = None
244248

245249
@command(
246250
default_output=format_output(
@@ -264,7 +268,6 @@ def __init__(
264268
)
265269
def status(self) -> AirHumidifierStatus:
266270
"""Retrieve properties."""
267-
268271
if self.device_info is None:
269272
self.device_info = self.info()
270273

Diff for: miio/airpurifier.py

-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ class LedBrightness(enum.Enum):
4545
class AirPurifierStatus(DeviceStatus):
4646
"""Container for status reports from the air purifier."""
4747

48-
_filter_type_cache = {}
49-
5048
def __init__(self, data: Dict[str, Any]) -> None:
5149
"""Response of a Air Purifier Pro (zhimi.airpurifier.v6):
5250

Diff for: miio/aqaracamera.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ class CameraOffset:
3838
class ArmStatus:
3939
"""Container for arm statuses."""
4040

41-
is_armed = attr.ib(converter=bool)
42-
arm_wait_time = attr.ib(converter=int)
43-
alarm_volume = attr.ib(converter=int)
41+
is_armed: bool = attr.ib(converter=bool)
42+
arm_wait_time: int = attr.ib(converter=int)
43+
alarm_volume: int = attr.ib(converter=int)
4444

4545

4646
class SDCardStatus(IntEnum):

Diff for: miio/chuangmi_ir.py

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import base64
22
import re
3+
from typing import Callable, Set, Tuple
34

45
import click
56
from construct import (
@@ -91,10 +92,11 @@ def play_pronto(self, pronto: str, repeats: int = 1, length: int = -1):
9192
:param int repeats: Number of extra signal repeats.
9293
:param int length: Length of the command. -1 means not sending the length parameter.
9394
"""
94-
return self.play_raw(*self.pronto_to_raw(pronto, repeats), length)
95+
command, frequency = self.pronto_to_raw(pronto, repeats)
96+
return self.play_raw(command, frequency, length)
9597

9698
@classmethod
97-
def pronto_to_raw(cls, pronto: str, repeats: int = 1):
99+
def pronto_to_raw(cls, pronto: str, repeats: int = 1) -> Tuple[str, int]:
98100
"""Play a Pronto Hex encoded IR command. Supports only raw Pronto format,
99101
starting with 0000.
100102
@@ -112,13 +114,13 @@ def pronto_to_raw(cls, pronto: str, repeats: int = 1):
112114
if len(pronto_data.intro) == 0:
113115
repeats += 1
114116

115-
times = set()
117+
times: Set[int] = set()
116118
for pair in pronto_data.intro + pronto_data.repeat * (1 if repeats else 0):
117119
times.add(pair.pulse)
118120
times.add(pair.gap)
119121

120-
times = sorted(times)
121-
times_map = {t: idx for idx, t in enumerate(times)}
122+
times_sorted = sorted(times)
123+
times_map = {t: idx for idx, t in enumerate(times_sorted)}
122124
edge_pairs = []
123125
for pair in pronto_data.intro + pronto_data.repeat * repeats:
124126
edge_pairs.append(
@@ -128,7 +130,7 @@ def pronto_to_raw(cls, pronto: str, repeats: int = 1):
128130
signal_code = base64.b64encode(
129131
ChuangmiIrSignal.build(
130132
{
131-
"times_index": times + [0] * (16 - len(times)),
133+
"times_index": times_sorted + [0] * (16 - len(times)),
132134
"edge_pairs": edge_pairs,
133135
}
134136
)
@@ -158,18 +160,19 @@ def play(self, command: str):
158160
if command_type not in ["raw", "pronto"]:
159161
raise ChuangmiIrException("Invalid command type")
160162

163+
play_method: Callable
161164
if command_type == "raw":
162165
play_method = self.play_raw
163166

164167
elif command_type == "pronto":
165168
play_method = self.play_pronto
166169

167170
try:
168-
command_args = [t(v) for v, t in zip(command_args, arg_types)]
171+
converted_command_args = [t(v) for v, t in zip(command_args, arg_types)]
169172
except Exception as ex:
170173
raise ChuangmiIrException("Invalid command arguments") from ex
171174

172-
return play_method(command, *command_args)
175+
return play_method(command, *converted_command_args)
173176

174177
@command(
175178
click.argument("indicator_led", type=bool),

Diff for: miio/chuangmi_plug.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from .click_common import command, format_output
88
from .device import Device, DeviceStatus
9+
from .exceptions import DeviceException
910
from .utils import deprecated
1011

1112
_LOGGER = logging.getLogger(__name__)
@@ -49,9 +50,11 @@ def power(self) -> bool:
4950
"""Current power state."""
5051
if "on" in self.data:
5152
return self.data["on"] is True or self.data["on"] == "on"
52-
if "power" in self.data:
53+
elif "power" in self.data:
5354
return self.data["power"] == "on"
5455

56+
raise DeviceException("There was neither 'on' or 'power' in data")
57+
5558
@property
5659
def is_on(self) -> bool:
5760
"""True if device is on."""

Diff for: miio/click_common.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import re
1010
import sys
1111
from functools import partial, wraps
12-
from typing import Union
12+
from typing import Callable, Set, Type, Union
1313

1414
import click
1515

@@ -110,16 +110,16 @@ def convert(self, value, param, ctx):
110110

111111

112112
class GlobalContextObject:
113-
def __init__(self, debug: int = 0, output: callable = None):
113+
def __init__(self, debug: int = 0, output: Callable = None):
114114
self.debug = debug
115115
self.output = output
116116

117117

118118
class DeviceGroupMeta(type):
119119

120-
device_classes = set()
120+
device_classes: Set[Type] = set()
121121

122-
def __new__(mcs, name, bases, namespace) -> type:
122+
def __new__(mcs, name, bases, namespace):
123123
commands = {}
124124

125125
def _get_commands_for_namespace(namespace):
@@ -264,8 +264,8 @@ def command(*decorators, name=None, default_output=None, **kwargs):
264264

265265

266266
def format_output(
267-
msg_fmt: Union[str, callable] = "",
268-
result_msg_fmt: Union[str, callable] = "{result}",
267+
msg_fmt: Union[str, Callable] = "",
268+
result_msg_fmt: Union[str, Callable] = "{result}",
269269
):
270270
def decorator(func):
271271
@wraps(func)

0 commit comments

Comments
 (0)