From 576a169bd7dc3df5b0bc507f6c09f20ea13ab266 Mon Sep 17 00:00:00 2001 From: Keyboard Destroyer Date: Sat, 21 Feb 2026 14:52:19 +0300 Subject: [PATCH] Added pylint run to project --- .github/workflows/python-pytest.yml | 7 ++-- .pylintrc | 17 +++++++++ pyproject.toml | 2 +- src/epomakercontroller/cli.py | 6 +-- .../commands/EpomakerCommand.py | 8 ++-- .../commands/EpomakerImageCommand.py | 9 +++-- .../commands/EpomakerKeyRGBCommand.py | 14 ++++--- .../commands/EpomakerTimeCommand.py | 2 - .../commands/reports/Report.py | 16 +++----- .../commands/reports/ReportWithData.py | 2 + .../controllers/controller.py | 6 ++- src/epomakercontroller/epomakercontroller.py | 15 ++++++-- src/epomakercontroller/logger/logger.py | 2 + src/epomakercontroller/utils/app_version.py | 7 ++-- src/epomakercontroller/utils/keyboard_gui.py | 26 +++++++++---- src/epomakercontroller/utils/keyboard_keys.py | 5 +-- src/epomakercontroller/utils/sensors.py | 37 +++++++++++-------- 17 files changed, 113 insertions(+), 68 deletions(-) create mode 100644 .pylintrc diff --git a/.github/workflows/python-pytest.yml b/.github/workflows/python-pytest.yml index 0fec384..2c29b97 100644 --- a/.github/workflows/python-pytest.yml +++ b/.github/workflows/python-pytest.yml @@ -25,9 +25,10 @@ jobs: run: | python -m pip install --upgrade pip pip install -e . - pip install pytest matplotlib + pip install pytest matplotlib pylint - name: Pytest run run: | pytest - - + - name: PyLint pass + run: | + pylint src/**/*.py diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..be54df3 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,17 @@ +[MESSAGES CONTROL] +disable= + missing-module-docstring, + missing-function-docstring, + missing-class-docstring, + c-extension-no-member, + no-member, + invalid-name, + too-few-public-methods, + fixme # Remove this someday and fix every TODO + +[FORMAT] +max-line-length=121 + +[DEPRECATED_BUILTINS] +bad-functions=print +load-plugins=pylint.extensions.bad_builtin \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 24e76aa..8f18ed7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "EpomakerController" -version = "0.0.8" +version = "0.0.9" description = "Epomakercontroller" authors = ["Sam Rodgers "] license = "MIT" diff --git a/src/epomakercontroller/cli.py b/src/epomakercontroller/cli.py index 3a9096b..ae214b4 100644 --- a/src/epomakercontroller/cli.py +++ b/src/epomakercontroller/cli.py @@ -1,10 +1,11 @@ # src/epomakercontroller/cli.py """Simple CLI for the EpomakerController package.""" -import click import tkinter as tk from functools import wraps +import click + from .commands.data.constants import Profile from .configs.configs import load_main_config from .epomakercontroller import EpomakerController @@ -36,7 +37,6 @@ def wrapper(*args, **kwargs): @click.version_option(retrieve_app_version(), prog_name="EpomakerController") def cli() -> None: """A simple CLI for the EpomakerController.""" - pass @cli.command() @@ -235,7 +235,7 @@ def show_keymap(keymap_filter: str | None) -> None: to_show = [item for item in data if keymap_filter.lower() in item["name"].lower()] for item in to_show: - print(f"{item['name']}: {item['value']}") + Logger.log_info(f"{item['name']}: {item['value']}") if __name__ == "__main__": diff --git a/src/epomakercontroller/commands/EpomakerCommand.py b/src/epomakercontroller/commands/EpomakerCommand.py index c1f1223..9527758 100644 --- a/src/epomakercontroller/commands/EpomakerCommand.py +++ b/src/epomakercontroller/commands/EpomakerCommand.py @@ -2,10 +2,10 @@ import dataclasses from typing import Iterator -from .reports.Report import Report, ReportCollection import numpy as np import numpy.typing as npt +from .reports.Report import Report, ReportCollection from ..logger.logger import Logger @@ -106,8 +106,7 @@ def __iter__(self) -> Iterator[Report]: Yields: Iterator[Report]: The reports in the command. """ - for report in self.reports: - yield report + yield from self.reports def __getitem__(self, key: int) -> Report: """Gets a report by index. @@ -127,5 +126,4 @@ def iter_report_bytes(self) -> Iterator[bytes]: Yields: Iterator[bytes]: The report bytes. """ - for report_bytes in self.reports.iter_report_bytes(): - yield report_bytes + yield from self.reports.iter_report_bytes() diff --git a/src/epomakercontroller/commands/EpomakerImageCommand.py b/src/epomakercontroller/commands/EpomakerImageCommand.py index 0e69963..212e93d 100644 --- a/src/epomakercontroller/commands/EpomakerImageCommand.py +++ b/src/epomakercontroller/commands/EpomakerImageCommand.py @@ -60,6 +60,8 @@ def _decode_rgb565(pixel: int) -> tuple[int, int, int]: return r, g, b + # TODO: fix this pylint warning, mova some functionality to separate methods + # pylint: disable=too-many-locals def encode_image(self, image_path: str) -> None: """Encode an image to 16-bit RGB565. @@ -90,6 +92,7 @@ def encode_image(self, image_path: str) -> None: for x in range(image.shape[1]): r, g, b = image[y, x] image_16bit[y, x] = self._encode_rgb565(r, g, b) + # pylint: disable=broad-exception-caught except Exception as e: Logger.log_error(f"Exception while converting image: {e}") return @@ -146,11 +149,9 @@ def encode_image(self, image_path: str) -> None: data_buff_pointer : data_buff_pointer + data_buff_length ].tobytes() ) - # Need some padding at the end of the image data - footer_report._pad() - self._insert_report(footer_report) - self.report_footer_prepared = True + self._insert_report(footer_report) + self.report_footer_prepared = footer_report.prepared if len(self.reports) != len(self.structure): Logger.log_error(f"Expected {len(self.structure)} reports, got {len(self.reports)}.") diff --git a/src/epomakercontroller/commands/EpomakerKeyRGBCommand.py b/src/epomakercontroller/commands/EpomakerKeyRGBCommand.py index 81f2db6..6967f45 100644 --- a/src/epomakercontroller/commands/EpomakerKeyRGBCommand.py +++ b/src/epomakercontroller/commands/EpomakerKeyRGBCommand.py @@ -73,7 +73,8 @@ def overlay( class EpomakerKeyRGBCommand(EpomakerCommand): """Change a selection of keys to specific RGB values.""" - + # TODO: Move some logic to separate method to fix the following pylint warning + # pylint: disable=too-many-locals def __init__(self, frames: list[KeyboardRGBFrame]) -> None: """Initializes the EpomakerKeyRGBCommand with a list of frames. @@ -151,9 +152,10 @@ def report_data_contain_index(self, report: ReportWithData, index: int) -> bool: """ report_index_count = 0 data_buffer_length = BUFF_LENGTH - self.report_data_header_length - for report in self.get_data_reports(): - report_data = report[self.report_data_header_length :] - if report_index_count <= index < (report_index_count + data_buffer_length): - return True - report_index_count += len(report_data) + + report_data = report[self.report_data_header_length :] + if report_index_count <= index < (report_index_count + data_buffer_length): + return True + report_index_count += len(report_data) + return False diff --git a/src/epomakercontroller/commands/EpomakerTimeCommand.py b/src/epomakercontroller/commands/EpomakerTimeCommand.py index 6066ce5..50eb031 100644 --- a/src/epomakercontroller/commands/EpomakerTimeCommand.py +++ b/src/epomakercontroller/commands/EpomakerTimeCommand.py @@ -28,8 +28,6 @@ def _format_time(time: datetime) -> str: Returns: str: The formatted command string. """ - print("Using:", time) - # Example of formatting for a specific date and time format # Adjust the formatting based on your specific requirements year = time.year diff --git a/src/epomakercontroller/commands/reports/Report.py b/src/epomakercontroller/commands/reports/Report.py index 1631583..295c372 100644 --- a/src/epomakercontroller/commands/reports/Report.py +++ b/src/epomakercontroller/commands/reports/Report.py @@ -29,7 +29,7 @@ class Report: def __post_init__(self) -> None: """Initializes the report after the dataclass is created.""" - if self.header_format_values == {}: + if not self.header_format_values: self.report_bytearray = bytearray.fromhex(self.header_format_string) else: self.report_bytearray = bytearray.fromhex( @@ -133,8 +133,7 @@ def __iter__(self) -> Iterator[Report]: Yields: Iterator[Report]: The reports in the collection. """ - for report in self.reports: - yield report + yield from self.reports def __getitem__(self, key: int) -> Report: """Gets a report by index. @@ -155,21 +154,18 @@ def __len__(self) -> int: """ return len(self.reports) - def __setitem__(self, report: Report) -> None: + def __setitem__(self, index: int, report: Report) -> None: """Adds a report to the collection. Args: report (Report): The report to add. """ - if report.index in [ + if report.index in set( r.index for r in self.reports - ]: - # Ignoring duplicating report, which is not good btw, - # but currently I'm maintaining previous behaviour - # TODO: Review code logic and refactor if needed + ): return - self.reports.append(report) + self.reports.insert(index, report) def append(self, report: Report) -> None: """Appends a report to the collection. diff --git a/src/epomakercontroller/commands/reports/ReportWithData.py b/src/epomakercontroller/commands/reports/ReportWithData.py index 5b7496e..48e521a 100644 --- a/src/epomakercontroller/commands/reports/ReportWithData.py +++ b/src/epomakercontroller/commands/reports/ReportWithData.py @@ -15,6 +15,8 @@ class ReportWithData(Report): """Represents a report with additional data.""" + # TODO: Fix the following warning --> + # pylint: disable=too-many-arguments, too-many-positional-arguments def __init__( self, header_format_string: str, diff --git a/src/epomakercontroller/controllers/controller.py b/src/epomakercontroller/controllers/controller.py index 2d966a9..bf856a3 100644 --- a/src/epomakercontroller/controllers/controller.py +++ b/src/epomakercontroller/controllers/controller.py @@ -1,4 +1,6 @@ from __future__ import annotations + +import sys import typing import signal @@ -48,9 +50,9 @@ def _setup_signal_handling(self) -> None: """Sets up signal handling to close the HID device on termination.""" signal.signal(signal.SIGINT, self._signal_handler) # Handle Ctrl+C signal.signal(signal.SIGTERM, self._signal_handler) # Handle termination - signal.signal(signal.SIGQUIT, self._signal_handler) + signal.signal(signal.SIGQUIT, self._signal_handler) def _signal_handler(self, sig: int, _: Optional[FrameType]) -> None: """Handles signals to ensure the HID device is closed.""" self.close_device() - exit(sig) # Pass code to system + sys.exit(sig) # Pass code to system diff --git a/src/epomakercontroller/epomakercontroller.py b/src/epomakercontroller/epomakercontroller.py index 79da91d..f6db550 100644 --- a/src/epomakercontroller/epomakercontroller.py +++ b/src/epomakercontroller/epomakercontroller.py @@ -9,13 +9,15 @@ import dataclasses import os import time -import hid # type: ignore[import-not-found] import subprocess import re + from typing import override from datetime import datetime from json import dumps +import hid # type: ignore[import-not-found] + from .configs.constants import TMP_FILE_PATH, RULE_FILE_PATH from .logger.logger import Logger from .utils.sensors import get_cpu_usage, get_device_temp @@ -43,7 +45,12 @@ from typing import Any, Optional +# pylint: disable=R0903 class EpomakerConfig: + """ + Configuration class that stores EpomakerController settings, parsed from config.json + """ + def __init__(self, config_main: Config) -> None: all_configs = get_all_configs() self.config_layout = all_configs.get(ConfigType.CONF_LAYOUT) @@ -156,6 +163,7 @@ def _find_product_id(self) -> Optional[int]: int | None: The product ID if found, None otherwise. """ + # pylint: disable=W0511 # Todo: optimization for pid in self.config.product_ids: self.device_list = hid.enumerate(self.config.vendor_id, pid) @@ -228,6 +236,7 @@ def print_device_info(self) -> None: device["vendor_id"] = f"0x{device['vendor_id']:04x}" device["product_id"] = f"0x{device['product_id']:04x}" + # pylint: disable=bad-builtin print( dumps( devices, @@ -315,8 +324,8 @@ def _send_command(self, command: EpomakerCommand.EpomakerCommand) -> None: try: self.device.get_product_string() - except: # noqa: E722 - raise IOError("Could not communicate with device") + except IOError as e: # noqa: E722 + raise IOError("Could not communicate with device") from e if not command.report_data_prepared: return diff --git a/src/epomakercontroller/logger/logger.py b/src/epomakercontroller/logger/logger.py index 75c636d..da454fc 100644 --- a/src/epomakercontroller/logger/logger.py +++ b/src/epomakercontroller/logger/logger.py @@ -12,6 +12,8 @@ class LogScope(enum.StrEnum): class Logger: @staticmethod def log(scope: LogScope, message: str): + # TODO: Refactor to logging library w/ custom format + file writing + # pylint: disable=bad-builtin print(f"{scope.value}: {message}") @staticmethod diff --git a/src/epomakercontroller/utils/app_version.py b/src/epomakercontroller/utils/app_version.py index 26ea2fb..584c2a6 100644 --- a/src/epomakercontroller/utils/app_version.py +++ b/src/epomakercontroller/utils/app_version.py @@ -2,10 +2,11 @@ def retrieve_app_version(): try: - return version("EpomakerController") + return version("EpomakerController") except PackageNotFoundError: return "version number not found" - + if __name__ == "__main__": - print(retrieve_app_version()) \ No newline at end of file + # pylint: disable=W0141 + print(retrieve_app_version()) diff --git a/src/epomakercontroller/utils/keyboard_gui.py b/src/epomakercontroller/utils/keyboard_gui.py index 4da10a2..134109f 100644 --- a/src/epomakercontroller/utils/keyboard_gui.py +++ b/src/epomakercontroller/utils/keyboard_gui.py @@ -1,16 +1,26 @@ +from __future__ import annotations + +import typing + from pathlib import Path import tkinter as tk -from tkinter.colorchooser import askcolor as askcolour # thats right +from tkinter.colorchooser import askcolor as askcolour # that's right from .keyboard_keys import KeyboardKey, KeyboardKeys from ..commands.EpomakerKeyRGBCommand import KeyMap, KeyboardRGBFrame -from typing import Callable, Literal from ..configs.configs import Config +from ..logger.logger import Logger + + +if typing.TYPE_CHECKING: + from typing import Callable, Literal + DEFAULT_KEY_WIDTH = 8 DFAULT_KEY_HEIGHT = 4 +# pylint: disable=too-many-instance-attributes class RGBKeyboardGUI: def __init__( self, @@ -54,15 +64,15 @@ def _handle_customization(self, item: tuple[str, int]) -> bool: if identifier == "w": self.key_width = int(DEFAULT_KEY_WIDTH * value) return True - elif identifier == "h": + if identifier == "h": self.key_height = int(DFAULT_KEY_HEIGHT * value) return True - elif identifier == "x": + if identifier == "x": self.col_offset += int(DEFAULT_KEY_WIDTH * value) elif identifier == "y": self.row_offset += int(DFAULT_KEY_HEIGHT * value) else: - print(f"Warning: Unknown customization identifier: {identifier}") + Logger.log_warning(f"Unknown customization identifier: {identifier}") return False @@ -100,8 +110,8 @@ def noop() -> None: keyboardkeys_row.append(key) else: # We will still display the key but it will show as being disabled. - print( - f"Warning: key from config json with name {col} does not match any KeyboardKey" + Logger.log_warning( + f"key from config json with name {col} does not match any KeyboardKey" ) # Create the button @@ -162,7 +172,7 @@ def apply_colour_to_selected_keys(self, _: object) -> None: self.frame.overlay(self.selected_key, (r, g, b)) self.callback([self.frame]) - print( + Logger.log_info( f"Set {','.join([k.name for k in self.selected_key])} keys to {colour}" ) self.selected_key.clear() diff --git a/src/epomakercontroller/utils/keyboard_keys.py b/src/epomakercontroller/utils/keyboard_keys.py index 4b239dd..99b2b3e 100644 --- a/src/epomakercontroller/utils/keyboard_keys.py +++ b/src/epomakercontroller/utils/keyboard_keys.py @@ -23,7 +23,7 @@ class KeyboardKeys: def __init__(self, config: Config) -> None: if not config: - Logger.log_error(f"KeyboardKeys config is empty") + Logger.log_error("KeyboardKeys config is empty") return self.all_keys = [KeyboardKey(**key) for key in config.data] @@ -32,8 +32,7 @@ def __init__(self, config: Config) -> None: self.name_to_key_dict[key.name] = key def __iter__(self) -> Iterator[KeyboardKey]: - for key in self.all_keys: - yield key + yield from self.all_keys def get_key_by_name(self, name: str) -> KeyboardKey | None: return self.name_to_key_dict.get(name, None) diff --git a/src/epomakercontroller/utils/sensors.py b/src/epomakercontroller/utils/sensors.py index 14e9648..e6c9449 100644 --- a/src/epomakercontroller/utils/sensors.py +++ b/src/epomakercontroller/utils/sensors.py @@ -1,8 +1,11 @@ -import psutil import random +import psutil import gpustat from pynvml import NVMLError +from epomakercontroller.logger.logger import Logger + + def get_cpu_usage(test_mode: bool = False) -> int: """Get the current CPU usage. @@ -38,15 +41,14 @@ def get_device_temp(temp_key: str, test_mode: bool = False) -> int: device_temp = temps[temp_key] return int(round(device_temp)) - else: - available_keys = list(temps.keys()) - print( - ( - f"Temperature key {temp_key!r} not found." - f"Available keys: {available_keys}" - ) + available_keys = list(temps.keys()) + # pylint: disable=bad-builtin + print( + ( + f"Temperature key {temp_key!r} not found." + f"Available keys: {available_keys}" ) - + ) return 0 @@ -54,15 +56,15 @@ def _get_temp_devices() -> dict[str, float] | None: try: hw_temperatures = psutil.sensors_temperatures() except AttributeError: - print("Temperature monitoring not supported on this system.") + Logger.log_error("Temperature monitoring not supported on this system.") return None try: gpu_stats = gpustat.new_query() except OSError as e: - print(f"No NVIDIA sensors available: {e.message}") + Logger.log_error(f"No NVIDIA sensors available: {e.strerror}") gpu_stats = None except NVMLError as e: - print(f"No NVIDIA driver available: {e}") + Logger.log_error(f"No NVIDIA driver available: {e}") gpu_stats = None temperature_sensors: dict[str, float] = {} @@ -88,10 +90,15 @@ def print_temp_devices() -> None: """Print available temperature sensors by key and current temperature.""" temps = _get_temp_devices() if not temps: - print("No temperature sensors found.") + Logger.log_error("No temperature sensors found.") return format_whitespace = len(max(temps.keys(), key=len)) + 10 - print("{key:{whitespace}} {value}".format(key="DEVICE KEY", whitespace=format_whitespace, value="CURRENT TEMPERATURE")) + # pylint: disable=bad-builtin + print( + f"DEVICE KEY:{format_whitespace} CURRENT TEMPERATURE" + ) + for device_key, temp in temps.items(): - print("{key:{whitespace}} {value}°C".format(key=device_key, whitespace=format_whitespace, value=temp)) + # pylint: disable=bad-builtin + print(f"{device_key}:{format_whitespace} {temp}°C")