Forráskód Böngészése

Finish enabling ruff rules (#327)

J. Nick Koston 1 napja
szülő
commit
5e58ac7cde

+ 28 - 0
pyproject.toml

@@ -28,8 +28,13 @@ ignore = [
     "UP038", # Use `X | Y` in `isinstance` is slower
     "S603", #  check for execution of untrusted input
     "S105", # possible hard coded creds
+    "TID252", # not for this lib
+    "TRY003", # nice but too many to fix,
+    "G201", # too noisy
+    "PLR2004", # too many to fix
 ]
 select = [
+    "ASYNC", # async rules
     "B",   # flake8-bugbear
     "D",   # flake8-docstrings
     "C4",  # flake8-comprehensions
@@ -40,6 +45,24 @@ select = [
     "UP",  # pyupgrade
     "I",   # isort
     "RUF", # ruff specific
+    "FLY", # flynt
+    "G", # flake8-logging-format   ,
+    "PERF", # Perflint
+    "PGH", # pygrep-hooks
+    "PIE", # flake8-pie
+    "PL", # pylint
+    "PT", # flake8-pytest-style
+    "PTH", # flake8-pathlib
+    "PYI", # flake8-pyi
+    "RET", # flake8-return
+    "RSE", # flake8-raise    ,
+    "SIM", # flake8-simplify
+    "SLF", # flake8-self
+    "SLOT", # flake8-slots
+    "T100", # Trace found: {name} used
+    "T20", # flake8-print
+    "TID", # Tidy imports
+    "TRY", # tryceratops
 ]
 
 [tool.ruff.lint.per-file-ignores]
@@ -50,10 +73,15 @@ select = [
     "D103",
     "D104",
     "S101",
+    "SLF001",
+    "PLR2004",
 ]
 "setup.py" = ["D100"]
 "conftest.py" = ["D100"]
 "docs/conf.py" = ["D100"]
+"scripts/**/*" = [
+    "T201"
+]
 
 [tool.ruff.lint.isort]
 known-first-party = ["pySwitchbot", "tests"]

+ 3 - 6
scripts/get_encryption_key.py

@@ -8,12 +8,9 @@ from switchbot import SwitchbotLock
 def main():
     if len(sys.argv) < 3:
         print(f"Usage: {sys.argv[0]} <device_mac> <username> [<password>]")
-        exit(1)
+        sys.exit(1)
 
-    if len(sys.argv) == 3:
-        password = getpass.getpass()
-    else:
-        password = sys.argv[3]
+    password = getpass.getpass() if len(sys.argv) == 3 else sys.argv[3]
 
     try:
         result = SwitchbotLock.retrieve_encryption_key(
@@ -21,7 +18,7 @@ def main():
         )
     except RuntimeError as e:
         print(e)
-        exit(1)
+        sys.exit(1)
 
     print("Key ID: " + result["key_id"])
     print("Encryption key: " + result["encryption_key"])

+ 2 - 4
switchbot/adv_parser.py

@@ -285,10 +285,8 @@ def parse_advertisement_data(
             _mfr_id,
             model,
         )
-    except Exception as err:  # pylint: disable=broad-except
-        _LOGGER.exception(
-            "Failed to parse advertisement data: %s: %s", advertisement_data, err
-        )
+    except Exception:  # pylint: disable=broad-except
+        _LOGGER.exception("Failed to parse advertisement data: %s", advertisement_data)
         return None
 
     if not data:

+ 1 - 3
switchbot/adv_parsers/hub2.py

@@ -30,7 +30,7 @@ def process_wohub2(data: bytes | None, mfr_data: bytes | None) -> dict[str, Any]
     if _temp_c == 0 and humidity == 0:
         return {}
 
-    _wohub2_data = {
+    return {
         # Data should be flat, but we keep the original structure for now
         "temp": {"c": _temp_c, "f": _temp_f},
         "temperature": _temp_c,
@@ -40,8 +40,6 @@ def process_wohub2(data: bytes | None, mfr_data: bytes | None) -> dict[str, Any]
         "illuminance": calculate_light_intensity(light_level),
     }
 
-    return _wohub2_data
-
 
 def calculate_light_intensity(light_level: int) -> int:
     """

+ 1 - 2
switchbot/adv_parsers/hubmini_matter.py

@@ -28,10 +28,9 @@ def process_hubmini_matter(
     if _temp_c == 0 and humidity == 0:
         return {}
 
-    paraser_data = {
+    return {
         "temp": {"c": _temp_c, "f": _temp_f},
         "temperature": _temp_c,
         "fahrenheit": bool(temp_data[2] & 0b10000000),
         "humidity": humidity,
     }
-    return paraser_data

+ 1 - 3
switchbot/adv_parsers/meter.py

@@ -35,7 +35,7 @@ def process_wosensorth(data: bytes | None, mfr_data: bytes | None) -> dict[str,
     if _temp_c == 0 and humidity == 0 and battery == 0:
         return {}
 
-    _wosensorth_data = {
+    return {
         # Data should be flat, but we keep the original structure for now
         "temp": {"c": _temp_c, "f": _temp_f},
         "temperature": _temp_c,
@@ -44,8 +44,6 @@ def process_wosensorth(data: bytes | None, mfr_data: bytes | None) -> dict[str,
         "battery": battery,
     }
 
-    return _wosensorth_data
-
 
 def process_wosensorth_c(data: bytes | None, mfr_data: bytes | None) -> dict[str, Any]:
     """Process woSensorTH/Temp sensor services data with CO2."""

+ 15 - 2
switchbot/const/__init__.py

@@ -3,10 +3,10 @@
 from __future__ import annotations
 
 from ..enum import StrEnum
-from .fan import FanMode as FanMode
+from .fan import FanMode
 
 # Preserve old LockStatus export for backwards compatibility
-from .lock import LockStatus as LockStatus
+from .lock import LockStatus
 
 DEFAULT_RETRY_COUNT = 3
 DEFAULT_RETRY_TIMEOUT = 1
@@ -67,3 +67,16 @@ class SwitchbotModel(StrEnum):
     ROLLER_SHADE = "Roller Shade"
     HUBMINI_MATTER = "HubMini Matter"
     CIRCULATOR_FAN = "Circulator Fan"
+
+
+__all__ = [
+    "DEFAULT_RETRY_COUNT",
+    "DEFAULT_RETRY_TIMEOUT",
+    "DEFAULT_SCAN_TIMEOUT",
+    "FanMode",
+    "LockStatus",
+    "SwitchbotAccountConnectionError",
+    "SwitchbotApiError",
+    "SwitchbotAuthenticationError",
+    "SwitchbotModel",
+]

+ 1 - 2
switchbot/devices/blind_tilt.py

@@ -100,8 +100,7 @@ class SwitchbotBlindTilt(SwitchbotBaseCover, SwitchbotSequenceDevice):
         """Send close command."""
         if self.get_position() > 50:
             return await self.close_up()
-        else:
-            return await self.close_down()
+        return await self.close_down()
 
     def get_position(self) -> Any:
         """Return cached tilt (0-100) of Blind Tilt."""

+ 4 - 6
switchbot/devices/device.py

@@ -646,7 +646,7 @@ class SwitchbotBaseDevice:
         """
         if not self._sb_adv_data:
             _LOGGER.exception("No advertisement data to update")
-            return
+            return None
         old_data = self._sb_adv_data.data.get("data") or {}
         merged_data = _merge_data(old_data, new_data)
         if merged_data == old_data:
@@ -688,9 +688,7 @@ class SwitchbotBaseDevice:
         ):
             return False
         time_since_last_full_update = time.monotonic() - self._last_full_update
-        if time_since_last_full_update < PASSIVE_POLL_INTERVAL:
-            return False
-        return True
+        return not time_since_last_full_update < PASSIVE_POLL_INTERVAL
 
 
 class SwitchbotDevice(SwitchbotBaseDevice):
@@ -723,11 +721,11 @@ class SwitchbotEncryptedDevice(SwitchbotDevice):
         """Switchbot base class constructor for encrypted devices."""
         if len(key_id) == 0:
             raise ValueError("key_id is missing")
-        elif len(key_id) != 2:
+        if len(key_id) != 2:
             raise ValueError("key_id is invalid")
         if len(encryption_key) == 0:
             raise ValueError("encryption_key is missing")
-        elif len(encryption_key) != 32:
+        if len(encryption_key) != 32:
             raise ValueError("encryption_key is invalid")
         self._key_id = key_id
         self._encryption_key = bytearray.fromhex(encryption_key)

+ 1 - 1
switchbot/devices/evaporative_humidifier.py

@@ -117,7 +117,7 @@ class SwitchbotEvaporativeHumidifier(SwitchbotEncryptedDevice):
         """Set device mode."""
         if mode == HumidifierMode.DRYING_FILTER:
             return await self.start_drying_filter()
-        elif mode not in MODES_COMMANDS:
+        if mode not in MODES_COMMANDS:
             raise ValueError("Invalid mode")
 
         command = COMMAND_SET_MODE + MODES_COMMANDS[mode]

+ 1 - 2
switchbot/devices/fan.py

@@ -86,8 +86,7 @@ class SwitchbotFan(SwitchbotSequenceDevice):
         """Send command to set fan oscillation"""
         if oscillating:
             return await self._send_command(COMMAND_START_OSCILLATION)
-        else:
-            return await self._send_command(COMMAND_STOP_OSCILLATION)
+        return await self._send_command(COMMAND_STOP_OSCILLATION)
 
     @update_after_operation
     async def turn_on(self) -> bool:

+ 2 - 5
switchbot/devices/relay_switch.py

@@ -106,9 +106,7 @@ class SwitchbotRelaySwitch(SwitchbotEncryptedDevice):
         ):
             return False
         time_since_last_full_update = time.monotonic() - self._last_full_update
-        if time_since_last_full_update < PASSIVE_POLL_INTERVAL:
-            return False
-        return True
+        return not time_since_last_full_update < PASSIVE_POLL_INTERVAL
 
     async def turn_on(self) -> bool:
         """Turn device on."""
@@ -131,8 +129,7 @@ class SwitchbotRelaySwitch(SwitchbotEncryptedDevice):
     async def async_toggle(self, **kwargs) -> bool:
         """Toggle device."""
         result = await self._send_command(COMMAND_TOGGLE)
-        status = self._check_command_result(result, 0, {1})
-        return status
+        return self._check_command_result(result, 0, {1})
 
     def is_on(self) -> bool | None:
         """Return switch state from cache."""

+ 2 - 6
switchbot/enum.py

@@ -3,17 +3,13 @@
 from __future__ import annotations
 
 from enum import Enum
-from typing import Any, TypeVar
-
-_StrEnumT = TypeVar("_StrEnumT", bound="StrEnum")
+from typing import Any, Self
 
 
 class StrEnum(str, Enum):
     """Partial backport of Python 3.11's StrEnum for our basic use cases."""
 
-    def __new__(
-        cls: type[_StrEnumT], value: str, *args: Any, **kwargs: Any
-    ) -> _StrEnumT:
+    def __new__(cls, value: str, *args: Any, **kwargs: Any) -> Self:
         """Create a new StrEnum instance."""
         if not isinstance(value, str):
             raise TypeError(f"{value!r} is not a string")

+ 2 - 2
tests/test_base_cover.py

@@ -111,7 +111,7 @@ async def test_get_extended_info_adv_returns_both_devices():
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
-    "data_value,result",
+    ("data_value", "result"),
     [
         (0, "not_charging"),
         (1, "charging_by_adapter"),
@@ -132,7 +132,7 @@ async def test_get_extended_info_adv_returns_device0_charge_states(data_value, r
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
-    "data_value,result",
+    ("data_value", "result"),
     [
         (0, "not_charging"),
         (1, "charging_by_adapter"),

+ 4 - 3
tests/test_blind_tilt.py

@@ -61,7 +61,8 @@ async def test_open():
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
-    "position,keys", [(5, blind_tilt.CLOSE_DOWN_KEYS), (55, blind_tilt.CLOSE_UP_KEYS)]
+    ("position", "keys"),
+    [(5, blind_tilt.CLOSE_DOWN_KEYS), (55, blind_tilt.CLOSE_UP_KEYS)],
 )
 async def test_close(position, keys):
     blind_device = create_device_for_command_testing(position=position)
@@ -79,7 +80,7 @@ async def test_get_basic_info_returns_none_when_no_data():
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
-    "reverse_mode,data,result",
+    ("reverse_mode", "data", "result"),
     [
         (
             False,
@@ -152,7 +153,7 @@ async def test_get_extended_info_summary_returns_none_when_bad_data(data_value):
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
-    "data,result", [(bytes([0, 0]), False), (bytes([0, 255]), True)]
+    ("data", "result"), [(bytes([0, 0]), False), (bytes([0, 255]), True)]
 )
 async def test_get_extended_info_summary(data, result):
     blind_device = create_device_for_command_testing()

+ 3 - 3
tests/test_curtain.py

@@ -268,7 +268,7 @@ async def test_get_basic_info_returns_none_when_no_data():
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
-    "data,result",
+    ("data", "result"),
     [
         (
             bytes([0, 1, 10, 2, 255, 255, 50, 4]),
@@ -373,7 +373,7 @@ async def test_get_extended_info_summary_returns_none_when_bad_data(data_value):
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
-    "data,result",
+    ("data", "result"),
     [
         ([0, 0, 0], [True, False, False, "right_to_left"]),
         ([255, 255, 0], [False, True, True, "left_to_right"]),
@@ -392,7 +392,7 @@ async def test_get_extended_info_summary_returns_device0(data, result):
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
-    "data,result",
+    ("data", "result"),
     [
         ([0, 0, 1], [True, False, False, "right_to_left"]),
         ([255, 255, 255], [False, True, True, "left_to_right"]),

+ 1 - 1
tests/test_evaporative_humidifier.py

@@ -143,7 +143,7 @@ async def test_set_mode():
     await device.set_mode(HumidifierMode.DRYING_FILTER)
     assert device.get_mode() is HumidifierMode.DRYING_FILTER
 
-    with pytest.raises(ValueError):
+    with pytest.raises(ValueError):  # noqa: PT011
         await device.set_mode(0)
 
     with pytest.raises(TypeError):

+ 7 - 5
tests/test_fan.py

@@ -50,7 +50,7 @@ def make_advertisement_data(ble_device: BLEDevice, init_data: dict | None = None
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
-    "response, expected",
+    ("response", "expected"),
     [
         (b"\x00", None),
         (b"\x07", None),
@@ -66,7 +66,7 @@ async def test__get_basic_info(response, expected):
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
-    "basic_info,firmware_info", [(True, False), (False, True), (False, False)]
+    ("basic_info", "firmware_info"), [(True, False), (False, True), (False, False)]
 )
 async def test_get_basic_info_returns_none(basic_info, firmware_info):
     fan_device = create_device_for_command_testing()
@@ -74,8 +74,9 @@ async def test_get_basic_info_returns_none(basic_info, firmware_info):
     async def mock_get_basic_info(arg):
         if arg == fan.COMMAND_GET_BASIC_INFO:
             return basic_info
-        elif arg == fan.DEVICE_GET_BASIC_SETTINGS_KEY:
+        if arg == fan.DEVICE_GET_BASIC_SETTINGS_KEY:
             return firmware_info
+        return None
 
     fan_device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
 
@@ -84,7 +85,7 @@ async def test_get_basic_info_returns_none(basic_info, firmware_info):
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
-    "basic_info,firmware_info,result",
+    ("basic_info", "firmware_info", "result"),
     [
         (
             bytearray(b"\x01\x02W\x82g\xf5\xde4\x01=dPP\x03\x14P\x00\x00\x00\x00"),
@@ -104,8 +105,9 @@ async def test_get_basic_info(basic_info, firmware_info, result):
     async def mock_get_basic_info(arg):
         if arg == fan.COMMAND_GET_BASIC_INFO:
             return basic_info
-        elif arg == fan.DEVICE_GET_BASIC_SETTINGS_KEY:
+        if arg == fan.DEVICE_GET_BASIC_SETTINGS_KEY:
             return firmware_info
+        return None
 
     fan_device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
 

+ 1 - 1
tests/test_roller_shade.py

@@ -83,7 +83,7 @@ async def test_get_basic_info_returns_none_when_no_data():
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
-    "reverse_mode,data,result",
+    ("reverse_mode", "data", "result"),
     [
         (
             True,