diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e2adfefb1..ed752eb36 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -12,54 +12,14 @@ repos: - id: check-ast - repo: https://github.com/astral-sh/ruff-pre-commit - # Ruff version. - rev: v0.7.1 + rev: c59bba8fb259db0fec2bbb77ad8ba51ea7341b56 # v0.15.20 hooks: - # Run the linter. - id: ruff - args: [--fix] - # Run the formatter. + args: [--fix, --exit-non-zero-on-fix] - id: ruff-format -- repo: https://github.com/pre-commit/mirrors-isort - rev: v5.10.1 - hooks: - - id: isort - additional_dependencies: [toml] - -#- repo: https://github.com/PyCQA/doc8 -# rev: v1.1.1 -# hooks: -# - id: doc8 -# additional_dependencies: [myst-parser] - -# - repo: https://github.com/myint/docformatter -# rev: v1.7.5 -# hooks: -# - id: docformatter -# args: [--in-place, --wrap-summaries, '88', --wrap-descriptions, '88', --black - -- repo: https://github.com/pycqa/flake8 - rev: 7.0.0 - hooks: - - id: flake8 - additional_dependencies: [flake8-docstrings, flake8-bugbear, flake8-builtins, flake8-print, flake8-pytest-style, flake8-return, flake8-simplify, flake8-annotations] - -- repo: https://github.com/PyCQA/bandit - rev: 1.7.7 - hooks: - - id: bandit - args: [-x, 'tests', -x, '**/test_*.py'] - - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.8.0 + rev: d2823d321df3af8f878f7ee3414dc94d037145b9 # v2.1.0 hooks: - id: mypy additional_dependencies: [types-attrs, types-PyYAML, types-requests, types-pytz, types-croniter, types-freezegun] - -- repo: https://github.com/asottile/pyupgrade - rev: v3.15.0 - hooks: - - id: pyupgrade - args: ['--py311-plus'] diff --git a/docs/examples/push_server/gateway_alarm_trigger.py b/docs/examples/push_server/gateway_alarm_trigger.py index a3aa7cc23..0bf7a7e35 100644 --- a/docs/examples/push_server/gateway_alarm_trigger.py +++ b/docs/examples/push_server/gateway_alarm_trigger.py @@ -8,7 +8,7 @@ logging.basicConfig(level="INFO") gateway_ip = "192.168.1.IP" -token = "TokenTokenToken" # nosec +token = "TokenTokenToken" # noqa: S105 async def asyncio_demo(loop): diff --git a/docs/examples/push_server/gateway_button_press.py b/docs/examples/push_server/gateway_button_press.py index a901f606d..b527adfb4 100644 --- a/docs/examples/push_server/gateway_button_press.py +++ b/docs/examples/push_server/gateway_button_press.py @@ -8,7 +8,7 @@ logging.basicConfig(level="INFO") gateway_ip = "192.168.1.IP" -token = "TokenTokenToken" # nosec +token = "TokenTokenToken" # noqa: S105 button_sid = "lumi.123456789abcdef" diff --git a/miio/click_common.py b/miio/click_common.py index 8a1d9a238..14ce35096 100644 --- a/miio/click_common.py +++ b/miio/click_common.py @@ -31,7 +31,7 @@ def validate_ip(ctx, param, value): ipaddress.ip_address(value) return value except ValueError as ex: - raise click.BadParameter(f"Invalid IP: {ex}") + raise click.BadParameter(f"Invalid IP: {ex}") from ex def validate_token(ctx, param, value): diff --git a/miio/cloud.py b/miio/cloud.py index 921bf2610..f4ffb844f 100644 --- a/miio/cloud.py +++ b/miio/cloud.py @@ -103,10 +103,10 @@ def _login(self): try: from micloud import MiCloud # noqa: F811 from micloud.micloudexception import MiCloudAccessDenied - except ImportError: + except ImportError as ex: raise CloudException( "You need to install 'micloud' package to use cloud interface" - ) + ) from ex self._micloud: MiCloud = MiCloud(username=self.username, password=self.password) try: # login() can either return False or raise an exception on failure @@ -165,9 +165,9 @@ def cloud(ctx: click.Context, username, password): """Cloud commands.""" try: import micloud # noqa: F401 - except ImportError: + except ImportError as ex: _LOGGER.error("micloud is not installed, no cloud access available") - raise CloudException("install micloud for cloud access") + raise CloudException("install micloud for cloud access") from ex ctx.obj = CloudInterface(username=username, password=password) if ctx.invoked_subcommand is None: diff --git a/miio/device.py b/miio/device.py index a9b68a10e..06aa895cc 100644 --- a/miio/device.py +++ b/miio/device.py @@ -329,7 +329,7 @@ def call_action(self, name: str, params=None): try: act = self.actions()[name] except KeyError: - raise ValueError(f"Unable to find action '{name}'") + raise ValueError(f"Unable to find action '{name}'") from None if params is None: return act.method() @@ -346,7 +346,7 @@ def change_setting(self, name: str, params=None): try: setting = self.settings()[name] except KeyError: - raise ValueError(f"Unable to find setting '{name}'") + raise ValueError(f"Unable to find setting '{name}'") from None params = params if params is not None else [] diff --git a/miio/devtools/pcapparser.py b/miio/devtools/pcapparser.py index 010986133..9806cc30c 100644 --- a/miio/devtools/pcapparser.py +++ b/miio/devtools/pcapparser.py @@ -51,7 +51,7 @@ def read_payloads_from_file(file, tokens: list[str]): try: decrypted = Message.parse(data, token=bytes.fromhex(token)) break - except BaseException: # noqa: B036 + except BaseException: # noqa: B036, S112 continue if decrypted is None: diff --git a/miio/devtools/simulators/common.py b/miio/devtools/simulators/common.py index e0964448e..9d9ea5e22 100644 --- a/miio/devtools/simulators/common.py +++ b/miio/devtools/simulators/common.py @@ -33,7 +33,7 @@ def did_and_mac_for_model(model): These identifiers allow making a simulated device unique for testing. """ - m = md5() # nosec + m = md5() # noqa: S324 m.update(model.encode()) digest = m.hexdigest()[:12] did = int(digest[:8], base=16) diff --git a/miio/devtools/simulators/miotsimulator.py b/miio/devtools/simulators/miotsimulator.py index eeab7e8f3..fd796b772 100644 --- a/miio/devtools/simulators/miotsimulator.py +++ b/miio/devtools/simulators/miotsimulator.py @@ -30,18 +30,18 @@ def create_random(values): if values["choices"] is not None: choices = values["choices"] - choice = choices[random.randint(0, len(choices) - 1)] # nosec + choice = choices[random.randint(0, len(choices) - 1)] # noqa: S311 _LOGGER.debug("Got enum %r for %s", choice, piid) return choice.value if values["range"] is not None: range = values["range"] - value = random.randint(range[0], range[1]) # nosec + value = random.randint(range[0], range[1]) # noqa: S311 _LOGGER.debug("Got value %r from %s for piid %s", value, range, piid) return value if values["format"] == bool: - value = bool(random.randint(0, 1)) # nosec + value = bool(random.randint(0, 1)) # noqa: S311 _LOGGER.debug("Got bool %r for piid %s", value, piid) return value @@ -277,13 +277,13 @@ def miot_simulator(file, model): dev = SimulatedDeviceModel.parse_obj(schema) except Exception as ex: # this is far from optimal, but considering this is a developer tool it can be fixed later - fn = f"/tmp/pythonmiio_unparseable_{model}.json" # nosec + fn = f"/tmp/pythonmiio_unparseable_{model}.json" # noqa: S108 with open(fn, "w") as f: json.dump(schema, f, indent=4) _LOGGER.error("Unable to parse the schema, see %s: %s", fn, ex) return loop = asyncio.get_event_loop() - random.seed(1) # nosec + random.seed(1) # noqa: S311 loop.run_until_complete(main(dev, model=model)) loop.run_forever() diff --git a/miio/extract_tokens.py b/miio/extract_tokens.py index cbd57a8a2..8cc1cd840 100644 --- a/miio/extract_tokens.py +++ b/miio/extract_tokens.py @@ -34,7 +34,7 @@ def read_android_yeelight(db) -> Iterator[DeviceConfig]: devicelist = xml.find(".//set[@name='deviceList']") if not devicelist: _LOGGER.warning("Unable to find deviceList") - return [] + return for dev_elem in list(devicelist): dev = json.loads(dev_elem.text) @@ -82,7 +82,7 @@ def decrypt_ztoken(ztoken): key = bytes.fromhex(keystring) cipher = Cipher( algorithms.AES(key), - modes.ECB(), # nosec + modes.ECB(), # noqa: S305 backend=default_backend(), ) decryptor = cipher.decryptor() diff --git a/miio/integrations/airdog/airpurifier/airpurifier_airdog.py b/miio/integrations/airdog/airpurifier/airpurifier_airdog.py index 0cac59bd2..ab6f19395 100644 --- a/miio/integrations/airdog/airpurifier/airpurifier_airdog.py +++ b/miio/integrations/airdog/airpurifier/airpurifier_airdog.py @@ -119,7 +119,9 @@ def status(self) -> AirDogStatus: ) values = self.get_properties(properties, max_properties=10) - return AirDogStatus(defaultdict(lambda: None, zip(properties, values))) + return AirDogStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/cgllc/airmonitor/airqualitymonitor.py b/miio/integrations/cgllc/airmonitor/airqualitymonitor.py index c1ebab3d0..1d26bb794 100644 --- a/miio/integrations/cgllc/airmonitor/airqualitymonitor.py +++ b/miio/integrations/cgllc/airmonitor/airqualitymonitor.py @@ -199,7 +199,7 @@ def status(self) -> AirQualityMonitorStatus: return AirQualityMonitorStatus(defaultdict(lambda: None, values)) else: return AirQualityMonitorStatus( - defaultdict(lambda: None, zip(properties, values)) + defaultdict(lambda: None, zip(properties, values, strict=False)) ) @command(default_output=format_output("Powering on")) diff --git a/miio/integrations/cgllc/airmonitor/test_airqualitymonitor_miot.py b/miio/integrations/cgllc/airmonitor/test_airqualitymonitor_miot.py index 5733b27d9..ca807693d 100644 --- a/miio/integrations/cgllc/airmonitor/test_airqualitymonitor_miot.py +++ b/miio/integrations/cgllc/airmonitor/test_airqualitymonitor_miot.py @@ -45,7 +45,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture(scope="function") +@pytest.fixture def airqualitymonitorcgdn1(request): request.cls.device = DummyAirQualityMonitorCGDN1() diff --git a/miio/integrations/chuangmi/camera/chuangmi_camera.py b/miio/integrations/chuangmi/camera/chuangmi_camera.py index b53867209..dac1b4744 100644 --- a/miio/integrations/chuangmi/camera/chuangmi_camera.py +++ b/miio/integrations/chuangmi/camera/chuangmi_camera.py @@ -200,7 +200,7 @@ def status(self) -> CameraStatus: values = self.get_properties(properties) - return CameraStatus(dict(zip(properties, values))) + return CameraStatus(dict(zip(properties, values, strict=False))) @command(default_output=format_output("Power on")) def on(self): diff --git a/miio/integrations/chuangmi/plug/chuangmi_plug.py b/miio/integrations/chuangmi/plug/chuangmi_plug.py index dba2232bb..3429e6acf 100644 --- a/miio/integrations/chuangmi/plug/chuangmi_plug.py +++ b/miio/integrations/chuangmi/plug/chuangmi_plug.py @@ -119,7 +119,9 @@ def status(self) -> ChuangmiPlugStatus: properties.append("load_power") values.append(load_power[0] * 0.01) - return ChuangmiPlugStatus(defaultdict(lambda: None, zip(properties, values))) + return ChuangmiPlugStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/chuangmi/remote/chuangmi_ir.py b/miio/integrations/chuangmi/remote/chuangmi_ir.py index 3a9318d57..4c3a9c711 100644 --- a/miio/integrations/chuangmi/remote/chuangmi_ir.py +++ b/miio/integrations/chuangmi/remote/chuangmi_ir.py @@ -170,7 +170,9 @@ def play(self, command: str): play_method = self.play_pronto try: - converted_command_args = [t(v) for v, t in zip(command_args, arg_types)] + converted_command_args = [ + t(v) for v, t in zip(command_args, arg_types, strict=False) + ] except Exception as ex: raise ValueError("Invalid command arguments") from ex diff --git a/miio/integrations/chuangmi/remote/test_chuangmi_ir.py b/miio/integrations/chuangmi/remote/test_chuangmi_ir.py index 3ad664903..b78ce82db 100644 --- a/miio/integrations/chuangmi/remote/test_chuangmi_ir.py +++ b/miio/integrations/chuangmi/remote/test_chuangmi_ir.py @@ -64,7 +64,7 @@ def test_play_raw(self): for args in test_data["test_raw_ok"]: with self.subTest(): self.device._reset_state() - self.assertTrue(self.device.play_raw(*args["in"])) + assert self.device.play_raw(*args["in"]) self.assertSequenceEqual( self.device.state["last_ir_played"], args["out"] ) @@ -84,7 +84,7 @@ def test_play_pronto(self): for args in test_data["test_pronto_ok"]: with self.subTest(): self.device._reset_state() - self.assertTrue(self.device.play_pronto(*args["in"])) + assert self.device.play_pronto(*args["in"]) self.assertSequenceEqual( self.device.state["last_ir_played"], args["out"] ) @@ -99,7 +99,7 @@ def test_play_auto(self): continue with self.subTest(): self.device._reset_state() - self.assertTrue(self.device.play(*args["in"])) + assert self.device.play(*args["in"]) self.assertSequenceEqual( self.device.state["last_ir_played"], args["out"] ) @@ -112,7 +112,7 @@ def test_play_with_type(self): for args in tests: with self.subTest(): command = "{}:{}".format(type_, ":".join(map(str, args["in"]))) - self.assertTrue(self.device.play(command)) + assert self.device.play(command) self.assertSequenceEqual( self.device.state["last_ir_played"], args["out"] ) diff --git a/miio/integrations/chunmi/cooker/cooker.py b/miio/integrations/chunmi/cooker/cooker.py index 81e314089..1131a259c 100644 --- a/miio/integrations/chunmi/cooker/cooker.py +++ b/miio/integrations/chunmi/cooker/cooker.py @@ -629,7 +629,9 @@ def status(self) -> CookerStatus: values_count, ) - return CookerStatus(defaultdict(lambda: None, zip(properties, values))) + return CookerStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) @command( click.argument("profile", type=str), diff --git a/miio/integrations/chunmi/cooker_multi/cooker_multi.py b/miio/integrations/chunmi/cooker_multi/cooker_multi.py index b67349f80..9231d3342 100644 --- a/miio/integrations/chunmi/cooker_multi/cooker_multi.py +++ b/miio/integrations/chunmi/cooker_multi/cooker_multi.py @@ -359,7 +359,9 @@ def status(self) -> CookerStatus: values_count, ) - return CookerStatus(defaultdict(lambda: None, zip(properties, values))) + return CookerStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) @command( click.argument("profile", type=str, required=True), diff --git a/miio/integrations/deerma/humidifier/airhumidifier_mjjsq.py b/miio/integrations/deerma/humidifier/airhumidifier_mjjsq.py index c9fcfd552..5fc93705b 100644 --- a/miio/integrations/deerma/humidifier/airhumidifier_mjjsq.py +++ b/miio/integrations/deerma/humidifier/airhumidifier_mjjsq.py @@ -152,7 +152,9 @@ def status(self) -> AirHumidifierStatus: ) values = self.get_properties(properties, max_properties=1) - return AirHumidifierStatus(defaultdict(lambda: None, zip(properties, values))) + return AirHumidifierStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/deerma/humidifier/tests/test_airhumidifier_jsqs.py b/miio/integrations/deerma/humidifier/tests/test_airhumidifier_jsqs.py index f54ac0dd1..3f6233187 100644 --- a/miio/integrations/deerma/humidifier/tests/test_airhumidifier_jsqs.py +++ b/miio/integrations/deerma/humidifier/tests/test_airhumidifier_jsqs.py @@ -35,9 +35,9 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture() +@pytest.fixture def dev(request): - yield DummyAirHumidifierJsqs() + return DummyAirHumidifierJsqs() def test_on(dev): diff --git a/miio/integrations/dmaker/airfresh/airfresh_t2017.py b/miio/integrations/dmaker/airfresh/airfresh_t2017.py index 8a1c0da1e..9abf695d4 100644 --- a/miio/integrations/dmaker/airfresh/airfresh_t2017.py +++ b/miio/integrations/dmaker/airfresh/airfresh_t2017.py @@ -248,7 +248,9 @@ def status(self) -> AirFreshStatus: ) values = self.get_properties(properties, max_properties=15) - return AirFreshStatus(defaultdict(lambda: None, zip(properties, values))) + return AirFreshStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/dmaker/fan/fan.py b/miio/integrations/dmaker/fan/fan.py index b87ab8eba..e2a4220b4 100644 --- a/miio/integrations/dmaker/fan/fan.py +++ b/miio/integrations/dmaker/fan/fan.py @@ -138,7 +138,7 @@ def status(self) -> FanStatusP5: properties = AVAILABLE_PROPERTIES[self.model] values = self.get_properties(properties, max_properties=15) - return FanStatusP5(dict(zip(properties, values))) + return FanStatusP5(dict(zip(properties, values, strict=False))) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/dreame/vacuum/tests/test_dreamevacuum_miot.py b/miio/integrations/dreame/vacuum/tests/test_dreamevacuum_miot.py index 6583747ca..71583d700 100644 --- a/miio/integrations/dreame/vacuum/tests/test_dreamevacuum_miot.py +++ b/miio/integrations/dreame/vacuum/tests/test_dreamevacuum_miot.py @@ -101,12 +101,12 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture(scope="function") +@pytest.fixture def dummydreame1cvacuum(request): request.cls.device = DummyDreame1CVacuumMiot() -@pytest.fixture(scope="function") +@pytest.fixture def dummydreamef9vacuum(request): request.cls.device = DummyDreameF9VacuumMiot() diff --git a/miio/integrations/genericmiot/tests/test_status.py b/miio/integrations/genericmiot/tests/test_status.py index b275cd9c6..305b3745f 100644 --- a/miio/integrations/genericmiot/tests/test_status.py +++ b/miio/integrations/genericmiot/tests/test_status.py @@ -8,13 +8,13 @@ @pytest.fixture(scope="session") def mockdev(): - yield Mock() + return Mock() VALID_RESPONSE = {"code": 0, "did": "valid-response", "piid": 1, "siid": 1, "value": 1} -@pytest.mark.parametrize("key", ("did", "piid", "siid", "value", "code")) +@pytest.mark.parametrize("key", ["did", "piid", "siid", "value", "code"]) def test_response_with_missing_value(key, mockdev, caplog: pytest.LogCaptureFixture): """Verify that property responses without necessary keys are ignored.""" caplog.set_level(logging.DEBUG) @@ -27,7 +27,7 @@ def test_response_with_missing_value(key, mockdev, caplog: pytest.LogCaptureFixt assert len(status.data) == 1 -@pytest.mark.parametrize("code", (-123, 123)) +@pytest.mark.parametrize("code", [-123, 123]) def test_response_with_error_codes(code, mockdev, caplog: pytest.LogCaptureFixture): caplog.set_level(logging.WARNING) diff --git a/miio/integrations/huayi/light/test_huizuo.py b/miio/integrations/huayi/light/test_huizuo.py index f546ac0a2..920dbae86 100644 --- a/miio/integrations/huayi/light/test_huizuo.py +++ b/miio/integrations/huayi/light/test_huizuo.py @@ -4,11 +4,16 @@ from miio.tests.dummies import DummyMiotDevice -from .huizuo import MODEL_HUIZUO_FANWY # Fan model extended -from .huizuo import MODEL_HUIZUO_FANWY2 # Fan model basic -from .huizuo import MODEL_HUIZUO_PIS123 # Basic model -from .huizuo import MODEL_HUIZUO_WYHEAT # Heater model -from .huizuo import Huizuo, HuizuoLampFan, HuizuoLampHeater, UnsupportedFeatureException +from .huizuo import ( + MODEL_HUIZUO_FANWY, # Fan model extended + MODEL_HUIZUO_FANWY2, # Fan model basic + MODEL_HUIZUO_PIS123, # Basic model + MODEL_HUIZUO_WYHEAT, # Heater model + Huizuo, + HuizuoLampFan, + HuizuoLampHeater, + UnsupportedFeatureException, +) _INITIAL_STATE = { "power": True, @@ -63,22 +68,22 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture(scope="function") +@pytest.fixture def huizuo(request): request.cls.device = DummyHuizuo() -@pytest.fixture(scope="function") +@pytest.fixture def huizuo_fan(request): request.cls.device = DummyHuizuoFan() -@pytest.fixture(scope="function") +@pytest.fixture def huizuo_fan2(request): request.cls.device = DummyHuizuoFan2() -@pytest.fixture(scope="function") +@pytest.fixture def huizuo_heater(request): request.cls.device = DummyHuizuoHeater() diff --git a/miio/integrations/ijai/vacuum/test_pro2vacuum.py b/miio/integrations/ijai/vacuum/test_pro2vacuum.py index bb992861f..ee936f78d 100644 --- a/miio/integrations/ijai/vacuum/test_pro2vacuum.py +++ b/miio/integrations/ijai/vacuum/test_pro2vacuum.py @@ -46,7 +46,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture(scope="function") +@pytest.fixture def dummypro2vacuum(request): request.cls.device = DummyPRO2Vacuum() diff --git a/miio/integrations/ksmb/walkingpad/walkingpad.py b/miio/integrations/ksmb/walkingpad/walkingpad.py index 89d34b89c..7384a015d 100644 --- a/miio/integrations/ksmb/walkingpad/walkingpad.py +++ b/miio/integrations/ksmb/walkingpad/walkingpad.py @@ -121,7 +121,9 @@ def status(self) -> WalkingpadStatus: properties_additional = ["power", "mode", "start_speed", "sensitivity"] values_additional = self.get_properties(properties_additional, max_properties=1) - additional_props = dict(zip(properties_additional, values_additional)) + additional_props = dict( + zip(properties_additional, values_additional, strict=False) + ) data.update(additional_props) return WalkingpadStatus(data) diff --git a/miio/integrations/leshow/fan/fan_leshow.py b/miio/integrations/leshow/fan/fan_leshow.py index 67bbe8a02..6f553227a 100644 --- a/miio/integrations/leshow/fan/fan_leshow.py +++ b/miio/integrations/leshow/fan/fan_leshow.py @@ -109,7 +109,7 @@ def status(self) -> FanLeshowStatus: ) values = self.get_properties(properties, max_properties=15) - return FanLeshowStatus(dict(zip(properties, values))) + return FanLeshowStatus(dict(zip(properties, values, strict=False))) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/lumi/acpartner/airconditioningcompanion.py b/miio/integrations/lumi/acpartner/airconditioningcompanion.py index a4c6c19a5..24da4d035 100644 --- a/miio/integrations/lumi/acpartner/airconditioningcompanion.py +++ b/miio/integrations/lumi/acpartner/airconditioningcompanion.py @@ -310,13 +310,17 @@ def send_ir_code(self, model: str, code: str, slot: int = 0): """ try: model_bytes = bytes.fromhex(model) - except ValueError: - raise ValueError("Invalid model. A hexadecimal string must be provided") + except ValueError as ex: + raise ValueError( + "Invalid model. A hexadecimal string must be provided" + ) from ex try: code_bytes = bytes.fromhex(code) - except ValueError: - raise ValueError("Invalid code. A hexadecimal string must be provided") + except ValueError as ex: + raise ValueError( + "Invalid code. A hexadecimal string must be provided" + ) from ex if slot < 0 or slot > 134: raise ValueError(f"Invalid slot: {slot}") diff --git a/miio/integrations/lumi/acpartner/airconditioningcompanionMCN.py b/miio/integrations/lumi/acpartner/airconditioningcompanionMCN.py index 92e6d7eb8..ba6b57db6 100644 --- a/miio/integrations/lumi/acpartner/airconditioningcompanionMCN.py +++ b/miio/integrations/lumi/acpartner/airconditioningcompanionMCN.py @@ -111,7 +111,7 @@ def __init__( model: str = MODEL_ACPARTNER_MCN02, ) -> None: if start_id is None: - start_id = random.randint(0, 999) # nosec + start_id = random.randint(0, 999) # noqa: S311 super().__init__( ip, token, start_id, debug, lazy_discover, timeout=timeout, model=model ) diff --git a/miio/integrations/lumi/acpartner/test_airconditioningcompanion.py b/miio/integrations/lumi/acpartner/test_airconditioningcompanion.py index 8d79bc54f..b1348cea0 100644 --- a/miio/integrations/lumi/acpartner/test_airconditioningcompanion.py +++ b/miio/integrations/lumi/acpartner/test_airconditioningcompanion.py @@ -200,7 +200,7 @@ def test_send_ir_code(self): for args in test_data["test_send_ir_code_ok"]: with self.subTest(): self.device._reset_state() - self.assertTrue(self.device.send_ir_code(*args["in"])) + assert self.device.send_ir_code(*args["in"]) self.assertSequenceEqual(self.device.get_last_ir_played(), args["out"]) for args in test_data["test_send_ir_code_exception"]: @@ -214,7 +214,7 @@ def test_send_configuration(self): for args in test_data["test_send_configuration_ok"]: with self.subTest(): self.device._reset_state() - self.assertTrue(self.device.send_configuration(*args["in"])) + assert self.device.send_configuration(*args["in"]) self.assertSequenceEqual(self.device.get_last_ir_played(), args["out"]) diff --git a/miio/integrations/nwt/dehumidifier/airdehumidifier.py b/miio/integrations/nwt/dehumidifier/airdehumidifier.py index b9ab5a16e..2885cb2b7 100644 --- a/miio/integrations/nwt/dehumidifier/airdehumidifier.py +++ b/miio/integrations/nwt/dehumidifier/airdehumidifier.py @@ -184,7 +184,8 @@ def status(self) -> AirDehumidifierStatus: values = self.get_properties(properties, max_properties=1) return AirDehumidifierStatus( - defaultdict(lambda: None, zip(properties, values)), self.info() + defaultdict(lambda: None, zip(properties, values, strict=False)), + self.info(), ) @command(default_output=format_output("Powering on")) diff --git a/miio/integrations/philips/light/ceil.py b/miio/integrations/philips/light/ceil.py index e5e54d72c..36772694a 100644 --- a/miio/integrations/philips/light/ceil.py +++ b/miio/integrations/philips/light/ceil.py @@ -90,7 +90,9 @@ def status(self) -> CeilStatus: properties = ["power", "bright", "cct", "snm", "dv", "bl", "ac"] values = self.get_properties(properties) - return CeilStatus(defaultdict(lambda: None, zip(properties, values))) + return CeilStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/philips/light/philips_bulb.py b/miio/integrations/philips/light/philips_bulb.py index 86e0e5933..d91d88cf5 100644 --- a/miio/integrations/philips/light/philips_bulb.py +++ b/miio/integrations/philips/light/philips_bulb.py @@ -92,7 +92,9 @@ def status(self) -> PhilipsBulbStatus: ) values = self.get_properties(properties) - return PhilipsBulbStatus(defaultdict(lambda: None, zip(properties, values))) + return PhilipsBulbStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/philips/light/philips_eyecare.py b/miio/integrations/philips/light/philips_eyecare.py index 8a4667d99..96ae3d461 100644 --- a/miio/integrations/philips/light/philips_eyecare.py +++ b/miio/integrations/philips/light/philips_eyecare.py @@ -104,7 +104,9 @@ def status(self) -> PhilipsEyecareStatus: ] values = self.get_properties(properties) - return PhilipsEyecareStatus(defaultdict(lambda: None, zip(properties, values))) + return PhilipsEyecareStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/philips/light/philips_moonlight.py b/miio/integrations/philips/light/philips_moonlight.py index 9fa6e86a9..097e190aa 100644 --- a/miio/integrations/philips/light/philips_moonlight.py +++ b/miio/integrations/philips/light/philips_moonlight.py @@ -141,7 +141,7 @@ def status(self) -> PhilipsMoonlightStatus: values = self.get_properties(properties) return PhilipsMoonlightStatus( - defaultdict(lambda: None, zip(properties, values)) + defaultdict(lambda: None, zip(properties, values, strict=False)) ) @command(default_output=format_output("Powering on")) diff --git a/miio/integrations/philips/light/philips_rwread.py b/miio/integrations/philips/light/philips_rwread.py index fc5941840..3cb7de9c0 100644 --- a/miio/integrations/philips/light/philips_rwread.py +++ b/miio/integrations/philips/light/philips_rwread.py @@ -99,7 +99,9 @@ def status(self) -> PhilipsRwreadStatus: ) values = self.get_properties(properties) - return PhilipsRwreadStatus(defaultdict(lambda: None, zip(properties, values))) + return PhilipsRwreadStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/pwzn/relay/pwzn_relay.py b/miio/integrations/pwzn/relay/pwzn_relay.py index 64b6d4e67..3380de57b 100644 --- a/miio/integrations/pwzn/relay/pwzn_relay.py +++ b/miio/integrations/pwzn/relay/pwzn_relay.py @@ -110,7 +110,9 @@ def status(self) -> PwznRelayStatus: ).copy() values = self.get_properties(properties) - return PwznRelayStatus(defaultdict(lambda: None, zip(properties, values))) + return PwznRelayStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) @command( click.argument("number", type=int), diff --git a/miio/integrations/roborock/vacuum/vacuum_cli.py b/miio/integrations/roborock/vacuum/vacuum_cli.py index c6bcfd0db..51b4340ba 100644 --- a/miio/integrations/roborock/vacuum/vacuum_cli.py +++ b/miio/integrations/roborock/vacuum/vacuum_cli.py @@ -19,6 +19,7 @@ validate_token, ) from miio.device import Device, UpdateState +from miio.discovery import Discovery from miio.exceptions import DeviceInfoUnavailableException from miio.miioprotocol import MiIOProtocol from miio.updater import OneShotServer @@ -26,8 +27,6 @@ from .vacuum import CarpetCleaningMode, Consumable, RoborockVacuum, TimerState from .vacuum_tui import VacuumTUI -from miio.discovery import Discovery - _LOGGER = logging.getLogger(__name__) pass_dev = click.make_pass_decorator(Device, ensure=True) diff --git a/miio/integrations/roidmi/vacuum/tests/test_roidmivacuum_miot.py b/miio/integrations/roidmi/vacuum/tests/test_roidmivacuum_miot.py index beb3212e8..89e81d90a 100644 --- a/miio/integrations/roidmi/vacuum/tests/test_roidmivacuum_miot.py +++ b/miio/integrations/roidmi/vacuum/tests/test_roidmivacuum_miot.py @@ -65,7 +65,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture(scope="function") +@pytest.fixture def dummyroidmivacuum(request): request.cls.device = DummyRoidmiVacuumMiot() @@ -200,7 +200,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture(scope="function") +@pytest.fixture def dummyroidmivacuum2(request): request.cls.device = DummyRoidmiVacuumMiot2() diff --git a/miio/integrations/shuii/humidifier/airhumidifier_jsq.py b/miio/integrations/shuii/humidifier/airhumidifier_jsq.py index 640959141..d0364e32a 100644 --- a/miio/integrations/shuii/humidifier/airhumidifier_jsq.py +++ b/miio/integrations/shuii/humidifier/airhumidifier_jsq.py @@ -188,7 +188,9 @@ def status(self) -> AirHumidifierStatus: values, ) - return AirHumidifierStatus({k: v for k, v in zip(properties, values)}) + return AirHumidifierStatus( + {k: v for k, v in zip(properties, values, strict=False)} + ) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/tinymu/toiletlid/toiletlid.py b/miio/integrations/tinymu/toiletlid/toiletlid.py index 2584feba9..2ebf0fb67 100644 --- a/miio/integrations/tinymu/toiletlid/toiletlid.py +++ b/miio/integrations/tinymu/toiletlid/toiletlid.py @@ -94,7 +94,9 @@ def status(self) -> ToiletlidStatus: values = self.get_properties(properties) color = self.get_ambient_light() - return ToiletlidStatus(dict(zip(properties, values), ambient_light=color)) + return ToiletlidStatus( + dict(zip(properties, values, strict=False), ambient_light=color) + ) @command(default_output=format_output("Nozzle clean")) def nozzle_clean(self): diff --git a/miio/integrations/viomi/vacuum/tests/test_viomivacuum.py b/miio/integrations/viomi/vacuum/tests/test_viomivacuum.py index 3bc72c53a..0fa03be4c 100644 --- a/miio/integrations/viomi/vacuum/tests/test_viomivacuum.py +++ b/miio/integrations/viomi/vacuum/tests/test_viomivacuum.py @@ -97,9 +97,9 @@ def __init__(self, *args, **kwargs): super().__init__(args, kwargs) -@pytest.fixture() +@pytest.fixture def dev(request): - yield DummyViomiVacuum() + return DummyViomiVacuum() class TestViomiVacuumStatus: diff --git a/miio/integrations/viomi/vacuum/viomivacuum.py b/miio/integrations/viomi/vacuum/viomivacuum.py index fdedc1b9c..e47b91278 100644 --- a/miio/integrations/viomi/vacuum/viomivacuum.py +++ b/miio/integrations/viomi/vacuum/viomivacuum.py @@ -687,7 +687,9 @@ def status(self) -> ViomiVacuumStatus: values = self.get_properties(properties) - status = ViomiVacuumStatus(defaultdict(lambda: None, zip(properties, values))) + status = ViomiVacuumStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) status.embed("consumables", self.consumable_status()) status.embed("dnd", self.dnd_status()) @@ -820,7 +822,7 @@ def get_positions(self, plan_multiplicator=1) -> list[ViomiPositionPoint]: results = self.send("get_curpos", []) positions = [] # Group result 4 by 4 - for res in [i for i in zip(*(results[i::4] for i in range(4)))]: + for res in [i for i in zip(*(results[i::4] for i in range(4)), strict=False)]: # ignore type require for mypy error # "ViomiPositionPoint" gets multiple values for keyword argument "plan_multiplicator" positions.append( diff --git a/miio/integrations/viomi/viomidishwasher/test_viomidishwasher.py b/miio/integrations/viomi/viomidishwasher/test_viomidishwasher.py index 4e118cba5..2fc72aecc 100644 --- a/miio/integrations/viomi/viomidishwasher/test_viomidishwasher.py +++ b/miio/integrations/viomi/viomidishwasher/test_viomidishwasher.py @@ -125,8 +125,8 @@ def test_status(self): assert self.device._is_running() is True assert SystemStatus.ThermistorError in self.state().errors assert SystemStatus.InsufficientWaterSoftener in self.state().errors - self.assertIsInstance(self.state().air_refresh_interval, int) - self.assertIsInstance(self.state().temperature, int) + assert isinstance(self.state().air_refresh_interval, int) + assert isinstance(self.state().temperature, int) def test_child_lock(self): self.device.on() # ensure on @@ -160,14 +160,14 @@ def test_schedule(self): second=0, microsecond=0 ) self.device.schedule(enough_time, Program.Quick) - self.assertIsInstance(self.state().schedule, datetime) + assert isinstance(self.state().schedule, datetime) assert self.state().schedule == enough_time def test_freshdry_interval(self): self.device.on() # ensure on assert self.is_on() is True - self.assertIsInstance(self.state().air_refresh_interval, int) + assert isinstance(self.state().air_refresh_interval, int) self.device.airrefresh(8) assert self.state().air_refresh_interval == 8 diff --git a/miio/integrations/viomi/viomidishwasher/viomidishwasher.py b/miio/integrations/viomi/viomidishwasher/viomidishwasher.py index 38cdddb12..ff452d787 100644 --- a/miio/integrations/viomi/viomidishwasher/viomidishwasher.py +++ b/miio/integrations/viomi/viomidishwasher/viomidishwasher.py @@ -273,7 +273,9 @@ def status(self) -> ViomiDishwasherStatus: values = self.get_properties(properties, max_properties=1) - return ViomiDishwasherStatus(defaultdict(lambda: None, zip(properties, values))) + return ViomiDishwasherStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) # FIXME: Change these to use the ViomiDishwasherStatus once we can query multiple properties at once (or cache?). def _is_on(self) -> bool: diff --git a/miio/integrations/xiaomi/aircondition/test_airconditioner_miot.py b/miio/integrations/xiaomi/aircondition/test_airconditioner_miot.py index ae0329718..c2d9a3586 100644 --- a/miio/integrations/xiaomi/aircondition/test_airconditioner_miot.py +++ b/miio/integrations/xiaomi/aircondition/test_airconditioner_miot.py @@ -60,7 +60,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture(scope="function") +@pytest.fixture def airconditionermiot(request): request.cls.device = DummyAirConditionerMiot() diff --git a/miio/integrations/yeelight/dual_switch/test_yeelight_dual_switch.py b/miio/integrations/yeelight/dual_switch/test_yeelight_dual_switch.py index 7af74cbb0..5a5329a31 100644 --- a/miio/integrations/yeelight/dual_switch/test_yeelight_dual_switch.py +++ b/miio/integrations/yeelight/dual_switch/test_yeelight_dual_switch.py @@ -28,7 +28,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture(scope="function") +@pytest.fixture def switch(request): request.cls.device = DummyYeelightDualControlModule() diff --git a/miio/integrations/yeelight/light/tests/test_yeelight.py b/miio/integrations/yeelight/light/tests/test_yeelight.py index 863296f7f..bd1ae665e 100644 --- a/miio/integrations/yeelight/light/tests/test_yeelight.py +++ b/miio/integrations/yeelight/light/tests/test_yeelight.py @@ -238,32 +238,25 @@ def test_status(self): assert status.delay_off == 0 assert status.music_mode is True assert len(status.lights) == 1 - assert status.is_on is False and status.is_on == status.lights[0].is_on - assert ( - status.brightness == 100 - and status.brightness == status.lights[0].brightness - ) - assert ( - status.color_mode == YeelightMode.ColorTemperature - and status.color_mode == status.lights[0].color_mode - ) - assert ( - status.color_temp == 3584 - and status.color_temp == status.lights[0].color_temp - ) - assert status.rgb is None and status.rgb == status.lights[0].rgb - assert status.hsv is None and status.hsv == status.lights[0].hsv + assert status.is_on is False + assert status.is_on == status.lights[0].is_on + assert status.brightness == 100 + assert status.brightness == status.lights[0].brightness + assert status.color_mode == YeelightMode.ColorTemperature + assert status.color_mode == status.lights[0].color_mode + assert status.color_temp == 3584 + assert status.color_temp == status.lights[0].color_temp + assert status.rgb is None + assert status.rgb == status.lights[0].rgb + assert status.hsv is None + assert status.hsv == status.lights[0].hsv # following are tested in set mode tests # assert status.rgb == 16711680 # assert status.hsv == (359, 100, 100) - assert ( - status.color_flowing is False - and status.color_flowing == status.lights[0].color_flowing - ) - assert ( - status.color_flow_params is None - and status.color_flow_params == status.lights[0].color_flow_params - ) + assert status.color_flowing is False + assert status.color_flowing == status.lights[0].color_flowing + assert status.color_flow_params is None + assert status.color_flow_params == status.lights[0].color_flow_params # color_flow_params will be tested after future implementation # assert status.color_flow_params == "0,0,1000,1,16711680,100,1000,1,65280,100,1000,1,255,100" and status.color_flow_params == status.lights[0].color_flow_params assert status.moonlight_mode is None @@ -366,32 +359,25 @@ def test_status(self): assert status.delay_off == 0 assert status.music_mode is None assert len(status.lights) == 1 - assert status.is_on is False and status.is_on == status.lights[0].is_on - assert ( - status.brightness == 100 - and status.brightness == status.lights[0].brightness - ) - assert ( - status.color_mode == YeelightMode.ColorTemperature - and status.color_mode == status.lights[0].color_mode - ) - assert ( - status.color_temp == 3584 - and status.color_temp == status.lights[0].color_temp - ) - assert status.rgb is None and status.rgb == status.lights[0].rgb - assert status.hsv is None and status.hsv == status.lights[0].hsv + assert status.is_on is False + assert status.is_on == status.lights[0].is_on + assert status.brightness == 100 + assert status.brightness == status.lights[0].brightness + assert status.color_mode == YeelightMode.ColorTemperature + assert status.color_mode == status.lights[0].color_mode + assert status.color_temp == 3584 + assert status.color_temp == status.lights[0].color_temp + assert status.rgb is None + assert status.rgb == status.lights[0].rgb + assert status.hsv is None + assert status.hsv == status.lights[0].hsv # following are tested in set mode tests # assert status.rgb == 16711680 # assert status.hsv == (359, 100, 100) - assert ( - status.color_flowing is False - and status.color_flowing == status.lights[0].color_flowing - ) - assert ( - status.color_flow_params is None - and status.color_flow_params == status.lights[0].color_flow_params - ) + assert status.color_flowing is False + assert status.color_flowing == status.lights[0].color_flowing + assert status.color_flow_params is None + assert status.color_flow_params == status.lights[0].color_flow_params # color_flow_params will be tested after future implementation # assert status.color_flow_params == "0,0,1000,1,16711680,100,1000,1,65280,100,1000,1,255,100" and status.color_flow_params == status.lights[0].color_flow_params assert status.moonlight_mode is True @@ -450,32 +436,25 @@ def test_status(self): assert status.delay_off == 0 assert status.music_mode is None assert len(status.lights) == 2 - assert status.is_on is False and status.is_on == status.lights[0].is_on - assert ( - status.brightness == 100 - and status.brightness == status.lights[0].brightness - ) - assert ( - status.color_mode == YeelightMode.ColorTemperature - and status.color_mode == status.lights[0].color_mode - ) - assert ( - status.color_temp == 3584 - and status.color_temp == status.lights[0].color_temp - ) - assert status.rgb is None and status.rgb == status.lights[0].rgb - assert status.hsv is None and status.hsv == status.lights[0].hsv + assert status.is_on is False + assert status.is_on == status.lights[0].is_on + assert status.brightness == 100 + assert status.brightness == status.lights[0].brightness + assert status.color_mode == YeelightMode.ColorTemperature + assert status.color_mode == status.lights[0].color_mode + assert status.color_temp == 3584 + assert status.color_temp == status.lights[0].color_temp + assert status.rgb is None + assert status.rgb == status.lights[0].rgb + assert status.hsv is None + assert status.hsv == status.lights[0].hsv # following are tested in set mode tests # assert status.rgb == 16711680 # assert status.hsv == (359, 100, 100) - assert ( - status.color_flowing is False - and status.color_flowing == status.lights[0].color_flowing - ) - assert ( - status.color_flow_params is None - and status.color_flow_params == status.lights[0].color_flow_params - ) + assert status.color_flowing is False + assert status.color_flowing == status.lights[0].color_flowing + assert status.color_flow_params is None + assert status.color_flow_params == status.lights[0].color_flow_params # color_flow_params will be tested after future implementation # assert status.color_flow_params == "0,0,1000,1,16711680,100,1000,1,65280,100,1000,1,255,100" and status.color_flow_params == status.lights[0].color_flow_params assert status.lights[1].is_on is False diff --git a/miio/integrations/yeelight/light/yeelight.py b/miio/integrations/yeelight/light/yeelight.py index 6f06806c5..d9f78369c 100644 --- a/miio/integrations/yeelight/light/yeelight.py +++ b/miio/integrations/yeelight/light/yeelight.py @@ -326,7 +326,7 @@ def status(self) -> YeelightStatus: values = self.get_properties(properties) - return YeelightStatus(dict(zip(properties, values))) + return YeelightStatus(dict(zip(properties, values, strict=False))) @property def color_temperature_range(self) -> ValidSettingRange: diff --git a/miio/integrations/yunmi/waterpurifier/waterpurifier.py b/miio/integrations/yunmi/waterpurifier/waterpurifier.py index a6e435d8e..26abdf7e7 100644 --- a/miio/integrations/yunmi/waterpurifier/waterpurifier.py +++ b/miio/integrations/yunmi/waterpurifier/waterpurifier.py @@ -145,7 +145,7 @@ def status(self) -> WaterPurifierStatus: _props_per_request = 1 values = self.get_properties(properties, max_properties=_props_per_request) - return WaterPurifierStatus(dict(zip(properties, values))) + return WaterPurifierStatus(dict(zip(properties, values, strict=False))) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/yunmi/waterpurifier/waterpurifier_yunmi.py b/miio/integrations/yunmi/waterpurifier/waterpurifier_yunmi.py index 8009c6300..9ae07e71a 100644 --- a/miio/integrations/yunmi/waterpurifier/waterpurifier_yunmi.py +++ b/miio/integrations/yunmi/waterpurifier/waterpurifier_yunmi.py @@ -316,4 +316,4 @@ def status(self) -> WaterPurifierYunmiStatus: val_count, ) - return WaterPurifierYunmiStatus(dict(zip(properties, values))) + return WaterPurifierYunmiStatus(dict(zip(properties, values, strict=False))) diff --git a/miio/integrations/zhimi/airpurifier/airfresh.py b/miio/integrations/zhimi/airpurifier/airfresh.py index 8387f3f7c..677ddcfc6 100644 --- a/miio/integrations/zhimi/airpurifier/airfresh.py +++ b/miio/integrations/zhimi/airpurifier/airfresh.py @@ -246,7 +246,7 @@ def status(self) -> AirFreshStatus: values = self.get_properties(properties, max_properties=15) return AirFreshStatus( - defaultdict(lambda: None, zip(properties, values)), self.model + defaultdict(lambda: None, zip(properties, values, strict=False)), self.model ) @command(default_output=format_output("Powering on")) diff --git a/miio/integrations/zhimi/airpurifier/airpurifier.py b/miio/integrations/zhimi/airpurifier/airpurifier.py index 6ca5c23e8..286e258f6 100644 --- a/miio/integrations/zhimi/airpurifier/airpurifier.py +++ b/miio/integrations/zhimi/airpurifier/airpurifier.py @@ -386,7 +386,9 @@ def status(self) -> AirPurifierStatus: values = self.get_properties(properties, max_properties=15) - return AirPurifierStatus(defaultdict(lambda: None, zip(properties, values))) + return AirPurifierStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/zhimi/airpurifier/tests/test_airpurifier_miot.py b/miio/integrations/zhimi/airpurifier/tests/test_airpurifier_miot.py index b4b4411c3..c4046e039 100644 --- a/miio/integrations/zhimi/airpurifier/tests/test_airpurifier_miot.py +++ b/miio/integrations/zhimi/airpurifier/tests/test_airpurifier_miot.py @@ -95,7 +95,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture(scope="function") +@pytest.fixture def airpurifier(request): request.cls.device = DummyAirPurifierMiot() @@ -243,7 +243,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture(scope="function") +@pytest.fixture def airpurifierMB4(request): request.cls.device = DummyAirPurifierMiotMB4() @@ -313,7 +313,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture(scope="function") +@pytest.fixture def airpurifierVA2(request): request.cls.device = DummyAirPurifierMiotVA2() diff --git a/miio/integrations/zhimi/fan/fan.py b/miio/integrations/zhimi/fan/fan.py index 892bb2d22..73a987bec 100644 --- a/miio/integrations/zhimi/fan/fan.py +++ b/miio/integrations/zhimi/fan/fan.py @@ -260,9 +260,9 @@ def status(self) -> FanStatus: # The ZA4 has a countdown timer in minutes if self.model in [MODEL_FAN_ZA4]: - return FanStatusZA4(dict(zip(properties, values))) + return FanStatusZA4(dict(zip(properties, values, strict=False))) - return FanStatus(dict(zip(properties, values))) + return FanStatus(dict(zip(properties, values, strict=False))) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/zhimi/heater/heater.py b/miio/integrations/zhimi/heater/heater.py index 298c4ac73..242875ffc 100644 --- a/miio/integrations/zhimi/heater/heater.py +++ b/miio/integrations/zhimi/heater/heater.py @@ -153,7 +153,7 @@ def status(self) -> HeaterStatus: values = self.get_properties(properties, max_properties=_props_per_request) - return HeaterStatus(dict(zip(properties, values))) + return HeaterStatus(dict(zip(properties, values, strict=False))) @command(default_output=format_output("Powering on")) def on(self): diff --git a/miio/integrations/zhimi/humidifier/airhumidifier.py b/miio/integrations/zhimi/humidifier/airhumidifier.py index 20408ba94..c02dd59dd 100644 --- a/miio/integrations/zhimi/humidifier/airhumidifier.py +++ b/miio/integrations/zhimi/humidifier/airhumidifier.py @@ -349,7 +349,8 @@ def status(self) -> AirHumidifierStatus: values = self.get_properties(properties, max_properties=_props_per_request) return AirHumidifierStatus( - defaultdict(lambda: None, zip(properties, values)), self.info() + defaultdict(lambda: None, zip(properties, values, strict=False)), + self.info(), ) @command(default_output=format_output("Powering on")) diff --git a/miio/integrations/zhimi/humidifier/tests/test_airhumidifier.py b/miio/integrations/zhimi/humidifier/tests/test_airhumidifier.py index 9ed5dbeb3..e3b130406 100644 --- a/miio/integrations/zhimi/humidifier/tests/test_airhumidifier.py +++ b/miio/integrations/zhimi/humidifier/tests/test_airhumidifier.py @@ -102,7 +102,7 @@ def _get_device_info(self, _): params=[MODEL_HUMIDIFIER_V1, MODEL_HUMIDIFIER_CA1, MODEL_HUMIDIFIER_CB1] ) def dev(request): - yield DummyAirHumidifier(model=request.param) + return DummyAirHumidifier(model=request.param) # TODO add ability to test on a real device @@ -295,7 +295,8 @@ def dry(): @pytest.mark.parametrize( - "depth,expected", [(-1, 0), (0, 0), (60, 50), (120, 100), (125, 100), (127, None)] + ("depth", "expected"), + [(-1, 0), (0, 0), (60, 50), (120, 100), (125, 100), (127, None)], ) def test_water_level(dev, depth, expected): """Test the water level conversions.""" diff --git a/miio/integrations/zhimi/humidifier/tests/test_airhumidifier_miot.py b/miio/integrations/zhimi/humidifier/tests/test_airhumidifier_miot.py index b5ee3e281..d87ba63eb 100644 --- a/miio/integrations/zhimi/humidifier/tests/test_airhumidifier_miot.py +++ b/miio/integrations/zhimi/humidifier/tests/test_airhumidifier_miot.py @@ -46,9 +46,9 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture() +@pytest.fixture def dev(request): - yield DummyAirHumidifierMiot() + return DummyAirHumidifierMiot() def test_on(dev): @@ -197,7 +197,8 @@ def clean_mode(): @pytest.mark.parametrize( - "depth,expected", [(-1, 0), (0, 0), (60, 50), (120, 100), (125, 100), (127, None)] + ("depth", "expected"), + [(-1, 0), (0, 0), (60, 50), (120, 100), (125, 100), (127, None)], ) def test_water_level(dev, depth, expected): dev.set_property("water_level", depth) diff --git a/miio/integrations/zhimi/humidifier/tests/test_airhumidifier_miot_ca6.py b/miio/integrations/zhimi/humidifier/tests/test_airhumidifier_miot_ca6.py index d89bb7f1b..129446a8d 100644 --- a/miio/integrations/zhimi/humidifier/tests/test_airhumidifier_miot_ca6.py +++ b/miio/integrations/zhimi/humidifier/tests/test_airhumidifier_miot_ca6.py @@ -44,9 +44,9 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture() +@pytest.fixture def dev(request): - yield DummyAirHumidifierMiotCA6() + return DummyAirHumidifierMiotCA6() def test_on(dev): @@ -176,7 +176,7 @@ def clean_mode(): assert clean_mode() is False -@pytest.mark.parametrize("given,expected", [(0, 0), (1, 50), (2, 100)]) +@pytest.mark.parametrize(("given", "expected"), [(0, 0), (1, 50), (2, 100)]) def test_water_level(dev, given, expected): dev.set_property("water_level", given) assert dev.status().water_level == expected diff --git a/miio/integrations/zimi/powerstrip/powerstrip.py b/miio/integrations/zimi/powerstrip/powerstrip.py index 49d34c829..6e303187a 100644 --- a/miio/integrations/zimi/powerstrip/powerstrip.py +++ b/miio/integrations/zimi/powerstrip/powerstrip.py @@ -173,7 +173,9 @@ def status(self) -> PowerStripStatus: ) values = self.get_properties(properties) - return PowerStripStatus(defaultdict(lambda: None, zip(properties, values))) + return PowerStripStatus( + defaultdict(lambda: None, zip(properties, values, strict=False)) + ) @command(click.argument("power", type=bool)) def set_power(self, power: bool): diff --git a/miio/protocol.py b/miio/protocol.py index fddff7c95..1b93ec1e8 100644 --- a/miio/protocol.py +++ b/miio/protocol.py @@ -58,7 +58,7 @@ def verify_token(token: bytes): @staticmethod def md5(data: bytes) -> bytes: """Calculates a md5 hashsum for the given bytes object.""" - checksum = hashlib.md5() # nosec + checksum = hashlib.md5() # noqa: S324 checksum.update(data) return checksum.digest() diff --git a/miio/push_server/server.py b/miio/push_server/server.py index a8b0e6c3f..696f63071 100644 --- a/miio/push_server/server.py +++ b/miio/push_server/server.py @@ -58,7 +58,7 @@ def __init__(self, *, device_ip=None, device_id=None): """Initialize the class.""" self._device_ip = device_ip - self._address = "0.0.0.0" # nosec + self._address = "0.0.0.0" # noqa: S104 self._server_ip = None self._device_id = device_id if device_id is not None else int(FAKE_DEVICE_ID) @@ -234,7 +234,7 @@ async def _create_udp_server(self): sock=udp_socket, ) - def _construct_event( # nosec + def _construct_event( self, event_id: str, info: EventInfo, @@ -253,7 +253,7 @@ def _construct_event( # nosec command = f"{self.server_model}.{info.action}:{source_id}" key = f"event.{info.source_model}.{info.event}" message_id = 0 - magic_number = randint(1590161094, 1642025774) # nosec, min/max taken from packet captures, unknown use + magic_number = randint(1590161094, 1642025774) # noqa: S311 # min/max taken from packet captures, unknown use if len(command) > 49: _LOGGER.error( diff --git a/miio/push_server/test_serverprotocol.py b/miio/push_server/test_serverprotocol.py index 5ccb395be..07b565e8c 100644 --- a/miio/push_server/test_serverprotocol.py +++ b/miio/push_server/test_serverprotocol.py @@ -26,7 +26,7 @@ def protocol(mocker, event_loop) -> ServerProtocol: proto = ServerProtocol(event_loop, socket, server) proto.transport = mocker.Mock() - yield proto + return proto def test_send_ping_ack(protocol: ServerProtocol, mocker): @@ -114,7 +114,7 @@ def test_datagram_with_known_method(protocol: ServerProtocol, mocker): @pytest.mark.parametrize( - "method,err_code", [("unknown_method", ERR_UNSUPPORTED), (None, ERR_INVALID)] + ("method", "err_code"), [("unknown_method", ERR_UNSUPPORTED), (None, ERR_INVALID)] ) def test_datagram_with_unknown_method( method, err_code, protocol: ServerProtocol, mocker diff --git a/miio/tests/conftest.py b/miio/tests/conftest.py index def5c2d69..f080882df 100644 --- a/miio/tests/conftest.py +++ b/miio/tests/conftest.py @@ -4,7 +4,7 @@ from ..devicestatus import DeviceStatus, action, sensor, setting -@pytest.fixture() +@pytest.fixture def dummy_status(): """Fixture for a status class with different sensors and settings.""" @@ -34,10 +34,10 @@ def setting_with_unit(self): def sensor_returning_none(self): return None - yield Status() + return Status() -@pytest.fixture() +@pytest.fixture def dummy_device(mocker, dummy_status): """Returns a very basic device with patched out I/O and a dummy status.""" @@ -55,4 +55,4 @@ def test_action(self): patched_status.__annotations__ = {} patched_status.__annotations__["return"] = dummy_status - yield d + return d diff --git a/miio/tests/dummies.py b/miio/tests/dummies.py index d3f2aa6ba..e3bba876b 100644 --- a/miio/tests/dummies.py +++ b/miio/tests/dummies.py @@ -14,7 +14,9 @@ def send(self, command: str, parameters=None, retry_count=3, extra_parameters=No try: return self.dummy_device.return_values[command](parameters) except KeyError: - raise DeviceError({"code": -32601, "message": "Method not found."}) + raise DeviceError( + {"code": -32601, "message": "Method not found."} + ) from None class DummyDevice: @@ -51,7 +53,7 @@ def __init__(self, *args, **kwargs): # TODO: ugly hack to check for pre-existing _model if getattr(self, "_model", None) is None: self._model = "dummy.model" - self.token = "ffffffffffffffffffffffffffffffff" # nosec + self.token = "ffffffffffffffffffffffffffffffff" # noqa: S105 self.ip = "192.0.2.1" def _reset_state(self): diff --git a/miio/tests/test_descriptorcollection.py b/miio/tests/test_descriptorcollection.py index 4bac8788f..c994343e9 100644 --- a/miio/tests/test_descriptorcollection.py +++ b/miio/tests/test_descriptorcollection.py @@ -46,7 +46,7 @@ def test_setting(self): @pytest.mark.parametrize( - "cls, params", + ("cls", "params"), [ pytest.param(ActionDescriptor, {"method": lambda _: _}, id="action"), pytest.param(PropertyDescriptor, {"status_attribute": "foo"}), diff --git a/miio/tests/test_deviceinfo.py b/miio/tests/test_deviceinfo.py index bd0f3362d..e93122d3b 100644 --- a/miio/tests/test_deviceinfo.py +++ b/miio/tests/test_deviceinfo.py @@ -3,7 +3,7 @@ from miio.deviceinfo import DeviceInfo -@pytest.fixture() +@pytest.fixture def info(): """Example response from Xiaomi Smart WiFi Plug (c&p from deviceinfo ctor).""" return DeviceInfo( diff --git a/miio/tests/test_miot_models.py b/miio/tests/test_miot_models.py index 046ad2a08..450497af3 100644 --- a/miio/tests/test_miot_models.py +++ b/miio/tests/test_miot_models.py @@ -107,7 +107,7 @@ def test_enum_missing_description(): ] -@pytest.mark.parametrize("format,expected_type", TYPES_FOR_FORMAT) +@pytest.mark.parametrize(("format", "expected_type"), TYPES_FOR_FORMAT) def test_format(format, expected_type): class Wrapper(BaseModel): """Need to wrap as plain string is not valid json.""" diff --git a/miio/tests/test_miotdevice.py b/miio/tests/test_miotdevice.py index 5ae88061e..0c44483c7 100644 --- a/miio/tests/test_miotdevice.py +++ b/miio/tests/test_miotdevice.py @@ -43,7 +43,7 @@ def test_get_property_by(dev): @pytest.mark.parametrize( - "value_type,value", + ("value_type", "value"), [ (None, 1), (MiotValueType.Int, "1"), @@ -107,7 +107,7 @@ def test_call_action_by(dev): @pytest.mark.parametrize( - "model,expected_mapping,expected_log", + ("model", "expected_mapping", "expected_log"), [ ("some.model", {"x": {"y": 1}}, ""), ("unknown.model", {"x": {"y": 1}}, "Unable to find mapping"), @@ -174,7 +174,7 @@ def test_call_action_from_mapping(dev): @pytest.mark.parametrize( - "props,included_in_request", + ("props", "included_in_request"), [ ({"access": ["read"]}, True), # read only ({"access": ["read", "write"]}, True), # read-write diff --git a/miio/updater.py b/miio/updater.py index 0145fe75c..c7d86c11d 100644 --- a/miio/updater.py +++ b/miio/updater.py @@ -58,7 +58,7 @@ def __init__(self, file, interface=None): with open(file, "rb") as f: self.payload = f.read() self.server.payload = self.payload - self.md5 = hashlib.md5(self.payload).hexdigest() # nosec + self.md5 = hashlib.md5(self.payload).hexdigest() # noqa: S324 _LOGGER.info(f"Using local {file} (md5: {self.md5})") @staticmethod @@ -104,5 +104,5 @@ def serve_once(self): if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) - upd = OneShotServer("/tmp/test") # nosec + upd = OneShotServer("/tmp/test") # noqa: S108 upd.serve_once() diff --git a/poetry.lock b/poetry.lock index 1592b0209..2c2c798ff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1983,4 +1983,4 @@ updater = ["netifaces"] [metadata] lock-version = "2.1" python-versions = "^3.11" -content-hash = "36c6770ec380f4e83d46f0174fcb33e84cef9f6cfbcc3ea060f193bd11e4dec8" +content-hash = "97b5da356be7bf2bda26df0bf1be53c5e6bbaf7041d3f64fe513b5b44e67182a" diff --git a/pyproject.toml b/pyproject.toml index 9526a6769..8af1c9678 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ updater = ["netifaces"] backup_extract = ["android_backup"] crcmod = ["crcmod"] -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = ">=6.2.5" pytest-cov = "*" pytest-mock = "*" @@ -80,29 +80,6 @@ mypy = {version = "*", markers = "platform_python_implementation == 'CPython'"} coverage = {extras = ["toml"], version = "*"} -[tool.isort] -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true -line_length = 88 -forced_separate = "miio.discover" -known_first_party = "miio" -known_third_party = [ - "attr", - "click", - "construct", - "croniter", - "cryptography", - "netifaces", - "platformdirs", - "pytest", - "pytz", - "setuptools", - "tqdm", - "zeroconf" -] - [tool.coverage.run] source = ["miio"] @@ -137,7 +114,14 @@ ignore-path-errors = ["docs/index.rst;D000"] target-version = "py311" [tool.ruff.lint] -select = ["UP", "F401"] +select = ["UP", "F", "B", "S", "PT", "I"] +ignore = [ + "PT011", # pytest.raises() without match= — too many existing cases to fix at once +] + +[tool.ruff.lint.per-file-ignores] +"**/test_*.py" = ["S101", "S105", "S106", "B018", "B904", "PT009", "PT012", "PT027"] +"miio/tests/**" = ["S101", "S105", "S106", "B018", "B904", "PT009", "PT012", "PT027"] [build-system] requires = ["poetry-core"]