From 51336bc3dc05e8ffa170058774d3b1655f3298a5 Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sat, 16 Nov 2024 16:56:57 +0000 Subject: [PATCH 01/16] chore: add mypy to dev dependencies --- .github/workflows/mypy.yml | 0 pyproject.toml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 .github/workflows/mypy.yml diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml index 952f868..b1c239a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ dynamic = ["version"] [project.optional-dependencies] -dev = ["black", "isort"] +dev = ["black", "isort", "mypy"] test = ["coverage", "pytest", "pytest-cov", "pytest-mock"] [project.scripts] From 25fa4b64233afab0c86bf4c695df1ce209d7582f Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sat, 16 Nov 2024 16:57:34 +0000 Subject: [PATCH 02/16] ci: add mypy github action --- .github/workflows/mypy.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index e69de29..d3e06d0 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -0,0 +1,24 @@ +name: Mypy Type Check + +on: [push, pull_request] + +jobs: + mypy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[dev,test] + + - name: Run mypy + run: mypy src tests \ No newline at end of file From a5eb51ef6c1e062c9a865658f30f30826d9834e2 Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sun, 17 Nov 2024 12:12:27 +0000 Subject: [PATCH 03/16] fix: Handle backend find_blicksticks returning None Handle the case that `USBBackend.find_blinksticks()` returns `None` --- src/blinkstick/backends/unix_like.py | 3 ++- src/blinkstick/backends/win32.py | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/blinkstick/backends/unix_like.py b/src/blinkstick/backends/unix_like.py index b328996..e593184 100644 --- a/src/blinkstick/backends/unix_like.py +++ b/src/blinkstick/backends/unix_like.py @@ -46,7 +46,8 @@ def find_blinksticks(find_all: bool = True) -> list[usb.core.Device] | None: @staticmethod def find_by_serial(serial: str) -> list[usb.core.Device] | None: - for d in UnixLikeBackend.find_blinksticks(): + found_devices = UnixLikeBackend.find_blinksticks() or [] + for d in found_devices: try: if usb.util.get_string(d, 3, 1033) == serial: devices = [d] diff --git a/src/blinkstick/backends/win32.py b/src/blinkstick/backends/win32.py index 0a3154d..86a7ca4 100644 --- a/src/blinkstick/backends/win32.py +++ b/src/blinkstick/backends/win32.py @@ -25,9 +25,8 @@ def __init__(self, device=None): @staticmethod def find_by_serial(serial: str) -> list[hid.HidDevice] | None: - devices = [ - d for d in Win32Backend.find_blinksticks() if d.serial_number == serial - ] + found_devices = Win32Backend.find_blinksticks() or [] + devices = [d for d in found_devices if d.serial_number == serial] if len(devices) > 0: return devices From f781ffa239c1ee7faefd45c0032f2d0156c927df Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sun, 17 Nov 2024 12:15:30 +0000 Subject: [PATCH 04/16] fix: ignore type errors from USB driver packages Neither the win32 or unix USB wrapper packages provide type hints or stubs, so just ignore the mypy errors --- src/blinkstick/blinkstick.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blinkstick/blinkstick.py b/src/blinkstick/blinkstick.py index 8b6e007..4af8d07 100644 --- a/src/blinkstick/blinkstick.py +++ b/src/blinkstick/blinkstick.py @@ -18,11 +18,11 @@ if sys.platform == "win32": from blinkstick.backends.win32 import Win32Backend as USBBackend - import pywinusb.hid as hid + import pywinusb.hid as hid # type: ignore else: from blinkstick.backends.unix_like import UnixLikeBackend as USBBackend - import usb.core - import usb.util + import usb.core # type: ignore + import usb.util # type: ignore from random import randint From 6326545c579ccd2f270ce5c501f56b52b413e58a Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sun, 17 Nov 2024 12:17:55 +0000 Subject: [PATCH 05/16] fix: return tuple from _get_color_rgb --- src/blinkstick/blinkstick.py | 6 +++--- tests/clients/test_blinkstick.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/blinkstick/blinkstick.py b/src/blinkstick/blinkstick.py index 4af8d07..b98785a 100644 --- a/src/blinkstick/blinkstick.py +++ b/src/blinkstick/blinkstick.py @@ -222,13 +222,13 @@ def _get_color_rgb(self, index: int = 0) -> tuple[int, int, int]: 0x80 | 0x20, 0x1, 0x0001, 0, 33 ) if self.inverse: - return [ + return ( 255 - device_bytes[1], 255 - device_bytes[2], 255 - device_bytes[3], - ] + ) else: - return [device_bytes[1], device_bytes[2], device_bytes[3]] + return (device_bytes[1], device_bytes[2], device_bytes[3]) else: data = self.get_led_data((index + 1) * 3) diff --git a/tests/clients/test_blinkstick.py b/tests/clients/test_blinkstick.py index ad6fc09..fe46c63 100644 --- a/tests/clients/test_blinkstick.py +++ b/tests/clients/test_blinkstick.py @@ -242,7 +242,7 @@ def test_set_inverse_type_checking(make_blinkstick, input_value, expected_result (0, 255, 0, 0), (255, 0, 0), False, - [255, 0, 0], + (255, 0, 0), id="RGB, NoInverse", ), pytest.param( @@ -258,7 +258,7 @@ def test_set_inverse_type_checking(make_blinkstick, input_value, expected_result (0, 255, 0, 0), (255, 0, 0), True, - [0, 255, 255], + (0, 255, 255), id="RGB, Inverse", ), pytest.param( From f10fa55d6b10f263383795c0940d32a342f4e67a Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sun, 17 Nov 2024 12:20:43 +0000 Subject: [PATCH 06/16] fix: allow str or None for color_format param in get_color we're deprecating the use of color_format, so we set the default value to None in order to test whether it is set/truthy. So, update the type hint to allow None as a type. --- src/blinkstick/blinkstick.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blinkstick/blinkstick.py b/src/blinkstick/blinkstick.py index b98785a..cbd1fe9 100644 --- a/src/blinkstick/blinkstick.py +++ b/src/blinkstick/blinkstick.py @@ -242,7 +242,7 @@ def get_color( self, index: int = 0, color_mode: ColorFormat = ColorFormat.RGB, - color_format: str = None, + color_format: str | None = None, ) -> tuple[int, int, int] | str: """ Get the current backend color in the defined format. From 08bc20332882845748c455a03523bc6eac4fb004 Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sun, 17 Nov 2024 12:21:51 +0000 Subject: [PATCH 07/16] fix: Use correct default when getting color_function --- src/blinkstick/blinkstick.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blinkstick/blinkstick.py b/src/blinkstick/blinkstick.py index cbd1fe9..3c5cd6d 100644 --- a/src/blinkstick/blinkstick.py +++ b/src/blinkstick/blinkstick.py @@ -287,7 +287,7 @@ def get_color( ColorFormat.HEX: self._get_color_hex, } - return color_funcs.get(color_mode, ColorFormat.RGB)(index) + return color_funcs.get(color_mode, self._get_color_rgb)(index) def _determine_report_id(self, led_count: int) -> tuple[int, int]: report_id = 9 From 684c79e2b53c2eb5069f77e83ee32434b0fba079 Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sun, 17 Nov 2024 12:29:41 +0000 Subject: [PATCH 08/16] chore: Fix type hinting for get_color type hint the `color_funcs` dict correctly, so that mypy doesn't get confused --- src/blinkstick/blinkstick.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/blinkstick/blinkstick.py b/src/blinkstick/blinkstick.py index 3c5cd6d..3f01d29 100644 --- a/src/blinkstick/blinkstick.py +++ b/src/blinkstick/blinkstick.py @@ -4,6 +4,7 @@ import time import warnings from importlib.metadata import version +from typing import Callable from blinkstick.colors import ( hex_to_rgb, @@ -282,7 +283,7 @@ def get_color( except ValueError: color_mode = ColorFormat.RGB - color_funcs = { + color_funcs: dict[ColorFormat, Callable[[int], tuple[int, int, int] | str]] = { ColorFormat.RGB: self._get_color_rgb, ColorFormat.HEX: self._get_color_hex, } From 6847d336d7e999c4e5d01f98ed55ad7a8494c578 Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sun, 17 Nov 2024 12:31:53 +0000 Subject: [PATCH 09/16] fix: remove redundant parens --- src/blinkstick/blinkstick.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blinkstick/blinkstick.py b/src/blinkstick/blinkstick.py index 3f01d29..601c053 100644 --- a/src/blinkstick/blinkstick.py +++ b/src/blinkstick/blinkstick.py @@ -229,7 +229,7 @@ def _get_color_rgb(self, index: int = 0) -> tuple[int, int, int]: 255 - device_bytes[3], ) else: - return (device_bytes[1], device_bytes[2], device_bytes[3]) + return device_bytes[1], device_bytes[2], device_bytes[3] else: data = self.get_led_data((index + 1) * 3) From e5676f8ac02cc9cc0a656c9fab584fe941ac1179 Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sun, 17 Nov 2024 12:33:29 +0000 Subject: [PATCH 10/16] fix: map floats to ints set_color expects ints for the r,g,b channel values, so cast gradient channel values to ints before calling --- src/blinkstick/blinkstick.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blinkstick/blinkstick.py b/src/blinkstick/blinkstick.py index 601c053..abb175b 100644 --- a/src/blinkstick/blinkstick.py +++ b/src/blinkstick/blinkstick.py @@ -687,7 +687,7 @@ def morph( ) for grad in gradient: - grad_r, grad_g, grad_b = grad + grad_r, grad_g, grad_b = map(int, grad) self.set_color( channel=channel, index=index, red=grad_r, green=grad_g, blue=grad_b From 8923e8e3d665db1ff86975dd6097c0f7e6884749 Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sun, 17 Nov 2024 12:35:34 +0000 Subject: [PATCH 11/16] chore: ignore mypy sadness mypy gets sad that value is ever treated as a string, but we need to keep that graceful fallback in there just in case. So just shush mypy --- src/blinkstick/blinkstick.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blinkstick/blinkstick.py b/src/blinkstick/blinkstick.py index abb175b..4fed85a 100644 --- a/src/blinkstick/blinkstick.py +++ b/src/blinkstick/blinkstick.py @@ -730,7 +730,7 @@ def set_inverse(self, value: bool) -> None: @param value: True/False to set the inverse mode """ if type(value) is str: - value = value.lower() == "true" + value = value.lower() == "true" # type: ignore self.inverse = bool(value) def set_max_rgb_value(self, value: int) -> None: From 054595bcd7cc492d927ccae750d74954e558b812 Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sun, 17 Nov 2024 12:36:30 +0000 Subject: [PATCH 12/16] fix: don't send_data if there's no blinkstick --- src/blinkstick/blinkstick.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/blinkstick/blinkstick.py b/src/blinkstick/blinkstick.py index 4fed85a..2139587 100644 --- a/src/blinkstick/blinkstick.py +++ b/src/blinkstick/blinkstick.py @@ -918,6 +918,9 @@ def send_data(self, channel: int) -> None: - 1 - G pin on BlinkStick Pro board - 2 - B pin on BlinkStick Pro board """ + if self.bstick is None: + return + packet_data = [item for sublist in self.data[channel] for item in sublist] try: From 45cff9e1d6bd9cffd5e11a334d7e6795e95afb87 Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sun, 17 Nov 2024 12:37:59 +0000 Subject: [PATCH 13/16] chore: no implicit returns of None Explicit is better than implicit. --- src/blinkstick/blinkstick.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/blinkstick/blinkstick.py b/src/blinkstick/blinkstick.py index 2139587..a236a6e 100644 --- a/src/blinkstick/blinkstick.py +++ b/src/blinkstick/blinkstick.py @@ -1437,6 +1437,7 @@ def find_first() -> BlinkStick | None: if d: return BlinkStick(device=d) + return None def find_by_serial(serial: str | None = None) -> BlinkStick | None: """ @@ -1451,6 +1452,8 @@ def find_by_serial(serial: str | None = None) -> BlinkStick | None: if devices: return BlinkStick(device=devices[0]) + return None + def get_blinkstick_package_version() -> str: return version("blinkstick") From 2b5e8dd101181f6fb89f1de05a80717819ad2d8e Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sun, 17 Nov 2024 12:40:08 +0000 Subject: [PATCH 14/16] fix: Handle HEX_COLOR_RE returns None In the event that we can't match the supplied hex value using the RE, raise a ValueError. Otherwise we risk attempting to access the `groups()` method of `None` --- src/blinkstick/colors.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/blinkstick/colors.py b/src/blinkstick/colors.py index 12ab0f7..5e1d4dd 100644 --- a/src/blinkstick/colors.py +++ b/src/blinkstick/colors.py @@ -226,8 +226,11 @@ def normalize_hex(hex_value: str) -> str: ValueError: '0099cc' is not a valid hexadecimal color value. """ + invalid_hex_value_msg = "'%s' is not a valid hexadecimal color value." + if not (hex_match := HEX_COLOR_RE.match(hex_value)): + raise ValueError(invalid_hex_value_msg % hex_value) try: - hex_digits = HEX_COLOR_RE.match(hex_value).groups()[0] + hex_digits = hex_match.groups()[0] except AttributeError: raise ValueError("'%s' is not a valid hexadecimal color value." % hex_value) if len(hex_digits) == 3: From bfabf6ce71f30044927a61550836df323a86dec6 Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sun, 17 Nov 2024 12:41:35 +0000 Subject: [PATCH 15/16] fix: Default serial param to empty string When doing `find_by_serial`, default the `serial` param to an empty string, not None. --- src/blinkstick/blinkstick.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/blinkstick/blinkstick.py b/src/blinkstick/blinkstick.py index a236a6e..bb80731 100644 --- a/src/blinkstick/blinkstick.py +++ b/src/blinkstick/blinkstick.py @@ -1439,7 +1439,8 @@ def find_first() -> BlinkStick | None: return None -def find_by_serial(serial: str | None = None) -> BlinkStick | None: + +def find_by_serial(serial: str = "") -> BlinkStick | None: """ Find BlinkStick backend based on serial number. From ba981c3cfe18d7d651ac8dc8ae1e3156f49fd1e8 Mon Sep 17 00:00:00 2001 From: Rob Berwick Date: Sun, 17 Nov 2024 13:05:06 +0000 Subject: [PATCH 16/16] fix: return bytes from _data_to_message --- src/blinkstick/blinkstick.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/blinkstick/blinkstick.py b/src/blinkstick/blinkstick.py index bb80731..1ccbb78 100644 --- a/src/blinkstick/blinkstick.py +++ b/src/blinkstick/blinkstick.py @@ -454,7 +454,7 @@ def get_info_block2(self) -> str: result += chr(i) return result - def _data_to_message(self, data) -> bytes: + def _data_to_message(self, data: str) -> bytes: """ Helper method to convert a string to byte array of 32 bytes. @@ -464,14 +464,14 @@ def _data_to_message(self, data) -> bytes: @rtype: byte[32] @return: It fills the rest of bytes with zeros. """ - bytes = [1] + byte_array = bytearray([1]) for c in data: - bytes.append(ord(c)) + byte_array.append(ord(c)) for i in range(32 - len(data)): - bytes.append(0) + byte_array.append(0) - return bytes + return bytes(byte_array) def set_info_block1(self, data: str) -> None: """