From 3846b65758c6b3d947634469a843c1cf1a682ae4 Mon Sep 17 00:00:00 2001 From: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com> Date: Wed, 13 May 2026 15:28:41 -0400 Subject: [PATCH] fix(ci): address remaining pyright errors (#1368) * fix(ci): address remaining pyright errors * fix: implement review feedback --- pyproject.toml | 2 + .../controllers/preview_thumb_controller.py | 14 ++++- src/tagstudio/qt/helpers/text_wrapper.py | 4 +- src/tagstudio/qt/previews/renderer.py | 59 +++++++++++-------- src/tagstudio/qt/utils/custom_runnable.py | 2 +- tests/qt/test_tag_search_panel.py | 2 +- 6 files changed, 51 insertions(+), 32 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8051f01e..5d7c4ddf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,6 +102,7 @@ include = ["src/tagstudio", "tests"] reportAny = false reportIgnoreCommentWithoutRule = false reportImplicitStringConcatenation = false +reportImportCycles = false reportMissingTypeArgument = false reportMissingTypeStubs = false # reportOptionalMemberAccess = false @@ -110,6 +111,7 @@ reportUnknownArgumentType = false reportUnknownLambdaType = false reportUnknownMemberType = false reportUnusedCallResult = false +reportUninitializedInstanceVariable = false [tool.ruff] exclude = ["home_ui.py", "resources.py", "resources_rc.py"] diff --git a/src/tagstudio/qt/controllers/preview_thumb_controller.py b/src/tagstudio/qt/controllers/preview_thumb_controller.py index 2c17e242..db9c2f33 100644 --- a/src/tagstudio/qt/controllers/preview_thumb_controller.py +++ b/src/tagstudio/qt/controllers/preview_thumb_controller.py @@ -4,7 +4,7 @@ import io from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override import cv2 import rawpy @@ -12,6 +12,10 @@ import structlog from PIL import Image, UnidentifiedImageError from PIL.Image import DecompressionBombError from PySide6.QtCore import QSize +from rawpy import ( + LibRawFileUnsupportedError, # pyright: ignore[reportPrivateImportUsage] + LibRawIOError, # pyright: ignore[reportPrivateImportUsage] +) from tagstudio.core.library.alchemy.library import Library from tagstudio.core.media_types import MediaCategories @@ -50,8 +54,8 @@ class PreviewThumb(PreviewThumbView): stats.width = image.width stats.height = image.height except ( - rawpy.LibRawIOError, - rawpy.LibRawFileUnsupportedError, + LibRawIOError, + LibRawFileUnsupportedError, FileNotFoundError, ): pass @@ -144,18 +148,22 @@ class PreviewThumb(PreviewThumbView): self._display_image(filepath) return self.__get_image_stats(filepath) + @override def _open_file_action_callback(self): open_file( self.__current_file, windows_start_command=self.__driver.settings.windows_start_command ) + @override def _open_explorer_action_callback(self): open_file(self.__current_file, file_manager=True) + @override def _delete_action_callback(self): if bool(self.__current_file): self.__driver.delete_files_callback(self.__current_file) + @override def _button_wrapper_callback(self): open_file( self.__current_file, windows_start_command=self.__driver.settings.windows_start_command diff --git a/src/tagstudio/qt/helpers/text_wrapper.py b/src/tagstudio/qt/helpers/text_wrapper.py index ce4bae5d..5ac29e63 100644 --- a/src/tagstudio/qt/helpers/text_wrapper.py +++ b/src/tagstudio/qt/helpers/text_wrapper.py @@ -7,7 +7,7 @@ from PIL import Image, ImageDraw, ImageFont def wrap_line( text: str, - font: ImageFont.ImageFont, + font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont, width: int = 256, draw: ImageDraw.ImageDraw | None = None, ) -> int: @@ -31,7 +31,7 @@ def wrap_line( def wrap_full_text( text: str, - font: ImageFont.ImageFont, + font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont, width: int = 256, draw: ImageDraw.ImageDraw | None = None, ) -> str: diff --git a/src/tagstudio/qt/previews/renderer.py b/src/tagstudio/qt/previews/renderer.py index 1f4215f6..1a493f4a 100644 --- a/src/tagstudio/qt/previews/renderer.py +++ b/src/tagstudio/qt/previews/renderer.py @@ -43,7 +43,7 @@ from PIL import ( UnidentifiedImageError, ) from PIL.Image import DecompressionBombError -from pillow_heif import register_heif_opener +from pillow_heif import register_heif_opener # pyright: ignore[reportUnknownVariableType] from PySide6.QtCore import ( QBuffer, QFile, @@ -58,6 +58,10 @@ from PySide6.QtCore import ( from PySide6.QtGui import QGuiApplication, QImage, QPainter, QPixmap from PySide6.QtPdf import QPdfDocument, QPdfDocumentRenderOptions from PySide6.QtSvg import QSvgRenderer +from rawpy import ( + LibRawFileUnsupportedError, # pyright: ignore[reportPrivateImportUsage] + LibRawIOError, # pyright: ignore[reportPrivateImportUsage] +) from tagstudio.core.constants import ( FONT_SAMPLE_SIZES, @@ -75,9 +79,11 @@ from tagstudio.qt.helpers.gradients import four_corner_gradient from tagstudio.qt.helpers.image_effects import replace_transparent_pixels from tagstudio.qt.helpers.text_wrapper import wrap_full_text from tagstudio.qt.models.palette import UI_COLORS, ColorType, UiColor, get_ui_color -from tagstudio.qt.previews.vendored.blender_renderer import blend_thumb +from tagstudio.qt.previews.vendored.blender_renderer import ( + blend_thumb, # pyright: ignore[reportUnknownVariableType] +) from tagstudio.qt.previews.vendored.pydub.audio_segment import ( - _AudioSegment as AudioSegment, + _AudioSegment as AudioSegment, # pyright: ignore[reportPrivateUsage] ) from tagstudio.qt.resource_manager import ResourceManager @@ -118,7 +124,7 @@ class _TarFile: def __init__(self, filepath: Path, mode: Literal["r"]) -> None: self.tar: tarfile.TarFile self.filepath = filepath - self.mode = mode + self.mode: Literal["r"] = mode def namelist(self) -> list[str]: return self.tar.getnames() @@ -127,10 +133,10 @@ class _TarFile: return unwrap(self.tar.extractfile(name)).read() def __enter__(self) -> "_TarFile": - self.tar = tarfile.open(self.filepath, self.mode).__enter__() + self.tar = tarfile.open(name=self.filepath, mode=self.mode).__enter__() return self - def __exit__(self, *args) -> None: + def __exit__(self, *args) -> None: # pyright: ignore[reportUnknownParameterType, reportMissingParameterType] self.tar.__exit__(*args) @@ -293,7 +299,7 @@ class ThumbRenderer(QObject): im: Image.Image = Image.new( mode="L", - size=tuple([d * smooth_factor for d in size]), # type: ignore + size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType] color="black", ) draw = ImageDraw.Draw(im) @@ -324,7 +330,7 @@ class ThumbRenderer(QObject): # Highlight im_hl: Image.Image = Image.new( mode="RGBA", - size=tuple([d * smooth_factor for d in size]), # type: ignore + size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType] color="#00000000", ) draw = ImageDraw.Draw(im_hl) @@ -343,7 +349,7 @@ class ThumbRenderer(QObject): # Shadow im_sh: Image.Image = Image.new( mode="RGBA", - size=tuple([d * smooth_factor for d in size]), # type: ignore + size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType] color="#00000000", ) draw = ImageDraw.Draw(im_sh) @@ -388,7 +394,7 @@ class ThumbRenderer(QObject): # Create larger blank image based on smooth_factor im: Image.Image = Image.new( "RGBA", - size=tuple([d * smooth_factor for d in size]), # type: ignore + size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType] color="#FF000000", ) @@ -396,13 +402,13 @@ class ThumbRenderer(QObject): bg: Image.Image bg = Image.new( "RGB", - size=tuple([d * smooth_factor for d in size]), # type: ignore + size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType] color="#000000FF", ) # Use a background image if provided if bg_image: - bg_im = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # type: ignore + bg_im = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # type: ignore # pyright: ignore[reportArgumentType] bg_im = ImageEnhance.Brightness(bg_im).enhance(0.3) # Reduce the brightness bg.paste(bg_im) @@ -411,7 +417,7 @@ class ThumbRenderer(QObject): bg, (0, 0), mask=self._get_mask( - tuple([d * smooth_factor for d in size]), # type: ignore + tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType] (pixel_ratio * smooth_factor), ), ) @@ -495,19 +501,19 @@ class ThumbRenderer(QObject): # Create larger blank image based on smooth_factor im: Image.Image = Image.new( "RGBA", - size=tuple([d * smooth_factor for d in size]), # type: ignore + size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType] color="#00000000", ) bg: Image.Image # Use a background image if provided if bg_image: - bg = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # type: ignore + bg = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # type: ignore # pyright: ignore[reportArgumentType] # Create solid background color else: bg = Image.new( "RGB", - size=tuple([d * smooth_factor for d in size]), # type: ignore + size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType] color="#000000", ) # Apply color overlay @@ -521,7 +527,7 @@ class ThumbRenderer(QObject): bg, (0, 0), mask=self._get_mask( - tuple([d * smooth_factor for d in size]), # type: ignore + tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType] (pixel_ratio * smooth_factor), ), ) @@ -662,17 +668,17 @@ class ThumbRenderer(QObject): artwork = None if ext in [".mp3"]: id3_tags: id3.ID3 = id3.ID3(filepath) - id3_covers: list = id3_tags.getall("APIC") + id3_covers: list = id3_tags.getall("APIC") # pyright: ignore[reportUnknownVariableType] if id3_covers: artwork = Image.open(BytesIO(id3_covers[0].data)) elif ext in [".flac"]: flac_tags: flac.FLAC = flac.FLAC(filepath) - flac_covers: list = flac_tags.pictures + flac_covers: list = flac_tags.pictures # pyright: ignore[reportUnknownVariableType] if flac_covers: artwork = Image.open(BytesIO(flac_covers[0].data)) elif ext in [".mp4", ".m4a", ".aac"]: mp4_tags: mp4.MP4 = mp4.MP4(filepath) - mp4_covers: list | None = mp4_tags.get("covr") # pyright: ignore[reportAssignmentType] + mp4_covers: list | None = mp4_tags.get("covr") # pyright: ignore[reportUnknownVariableType] if mp4_covers: artwork = Image.open(BytesIO(mp4_covers[0])) if artwork: @@ -1088,7 +1094,7 @@ class ThumbRenderer(QObject): font = ImageFont.truetype(filepath, size=font_size) text_wrapped: str = wrap_full_text( FONT_SAMPLE_TEXT, - font=font, # pyright: ignore[reportArgumentType] + font=font, width=size, draw=draw, ) @@ -1120,8 +1126,8 @@ class ThumbRenderer(QObject): ) except ( DecompressionBombError, - rawpy.LibRawIOError, - rawpy.LibRawFileUnsupportedError, + LibRawIOError, + LibRawFileUnsupportedError, ) as e: logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__) return im @@ -1137,6 +1143,7 @@ class ThumbRenderer(QObject): try: # Load the EXR data to an array and rotate the color space from BGRA -> RGBA raw_array = cv2.imread(str(filepath), cv2.IMREAD_UNCHANGED) + assert raw_array is not None raw_array[..., :3] = raw_array[..., 2::-1] # Correct the gamma of the raw array @@ -1209,7 +1216,7 @@ class ThumbRenderer(QObject): # Write the image to a buffer as png buffer: QBuffer = QBuffer() buffer.open(QBuffer.OpenModeFlag.ReadWrite) - q_image.save(buffer, "PNG") # type: ignore[call-overload] + q_image.save(device=buffer, format="PNG") # type: ignore # pyright: ignore[reportArgumentType] # Load the image from the buffer im = Image.new("RGB", (size, size), color="#1e1e1e") @@ -1258,7 +1265,7 @@ class ThumbRenderer(QObject): return im @staticmethod - def _model_stl_thumb(filepath: Path, size: int) -> Image.Image | None: + def _model_stl_thumb(filepath: Path, size: int) -> Image.Image | None: # pyright: ignore[reportUnusedParameter] """Render a thumbnail for an STL file. Args: @@ -1614,6 +1621,7 @@ class ThumbRenderer(QObject): def fetch_cached_image(file_name: Path): image: Image.Image | None = None + assert self.driver.cache_manager is not None cached_path = self.driver.cache_manager.get_file_path(file_name) if cached_path and cached_path.is_file(): @@ -1876,6 +1884,7 @@ class ThumbRenderer(QObject): image = self._resize_image(image, (adj_size, adj_size)) if save_to_file and savable_media_type and image: + assert self.driver.cache_manager is not None self.driver.cache_manager.save_image(image, save_to_file, mode="RGBA") except ( diff --git a/src/tagstudio/qt/utils/custom_runnable.py b/src/tagstudio/qt/utils/custom_runnable.py index 6c10dd06..06932bc7 100644 --- a/src/tagstudio/qt/utils/custom_runnable.py +++ b/src/tagstudio/qt/utils/custom_runnable.py @@ -5,7 +5,7 @@ from PySide6.QtCore import QObject, QRunnable, Signal -class CustomRunnable(QRunnable, QObject): +class CustomRunnable(QRunnable, QObject): # pyright: ignore[reportUnsafeMultipleInheritance] done = Signal() def __init__(self, function) -> None: diff --git a/tests/qt/test_tag_search_panel.py b/tests/qt/test_tag_search_panel.py index 04a176cc..05bd68a9 100644 --- a/tests/qt/test_tag_search_panel.py +++ b/tests/qt/test_tag_search_panel.py @@ -29,7 +29,7 @@ def test_tag_widget_actions_replaced_correctly(qtbot: QtBot, qt_driver: QtDriver # Set the widget tags = library.tags panel.set_tag_widget(tags[0], 0) - tag_widget: TagWidget = panel.scroll_layout.itemAt(0).widget() + tag_widget: TagWidget = panel.scroll_layout.itemAt(0).widget() # pyright: ignore[reportAssignmentType] should_replace_actions = { tag_widget: ["on_edit()", "on_remove()"],