From ac023a7716a68871b79d3c6228aead114e1a2bd6 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 8 Sep 2025 21:10:52 +0200 Subject: [PATCH 1/4] fix: apply unwrap where necessary --- src/tagstudio/core/library/alchemy/library.py | 10 +++++++--- src/tagstudio/qt/helpers/gradients.py | 10 ++++++---- src/tagstudio/qt/mixed/build_color.py | 5 +++-- src/tagstudio/qt/mixed/build_tag.py | 14 ++++++++------ src/tagstudio/qt/mixed/color_box.py | 3 ++- src/tagstudio/qt/mixed/drop_import_modal.py | 15 ++++++++++----- src/tagstudio/qt/mixed/migration_modal.py | 17 +++++++---------- src/tagstudio/qt/previews/renderer.py | 4 ++-- 8 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/tagstudio/core/library/alchemy/library.py b/src/tagstudio/core/library/alchemy/library.py index 64aeac4f6..0ecfd6295 100644 --- a/src/tagstudio/core/library/alchemy/library.py +++ b/src/tagstudio/core/library/alchemy/library.py @@ -1850,13 +1850,17 @@ def set_version(self, key: str, value: int) -> None: # TODO: Remove this once the 'preferences' table is removed. @deprecated("Use `get_version() for version and `ts_ignore` system for extension exclusion.") - def prefs(self, key: str | LibraryPrefs): # pyright: ignore[reportUnknownParameterType] + def prefs(self, key: str | LibraryPrefs): # load given item from Preferences table with Session(self.engine) as session: if isinstance(key, LibraryPrefs): - return session.scalar(select(Preferences).where(Preferences.key == key.name)).value # pyright: ignore + return unwrap( + session.scalar(select(Preferences).where(Preferences.key == key.name)) + ).value else: - return session.scalar(select(Preferences).where(Preferences.key == key)).value # pyright: ignore + return unwrap( + session.scalar(select(Preferences).where(Preferences.key == key)) + ).value # TODO: Remove this once the 'preferences' table is removed. @deprecated("Use `get_version() for version and `ts_ignore` system for extension exclusion.") diff --git a/src/tagstudio/qt/helpers/gradients.py b/src/tagstudio/qt/helpers/gradients.py index fee4f151b..1e672868b 100644 --- a/src/tagstudio/qt/helpers/gradients.py +++ b/src/tagstudio/qt/helpers/gradients.py @@ -5,16 +5,18 @@ from PIL import Image +from tagstudio.core.utils.types import unwrap + def four_corner_gradient( image: Image.Image, size: tuple[int, int], mask: Image.Image | None = None ) -> Image.Image: if image.size != size: # Four-Corner Gradient Background. - tl = image.getpixel((0, 0)) - tr = image.getpixel(((image.size[0] - 1), 0)) - bl = image.getpixel((0, (image.size[1] - 1))) - br = image.getpixel(((image.size[0] - 1), (image.size[1] - 1))) + tl = unwrap(image.getpixel((0, 0))) + tr = unwrap(image.getpixel(((image.size[0] - 1), 0))) + bl = unwrap(image.getpixel((0, (image.size[1] - 1)))) + br = unwrap(image.getpixel(((image.size[0] - 1), (image.size[1] - 1)))) bg = Image.new(mode="RGB", size=(2, 2)) bg.paste(tl, (0, 0, 2, 2)) bg.paste(tr, (1, 0, 2, 2)) diff --git a/src/tagstudio/qt/mixed/build_color.py b/src/tagstudio/qt/mixed/build_color.py index a007c9eed..a33ef435d 100644 --- a/src/tagstudio/qt/mixed/build_color.py +++ b/src/tagstudio/qt/mixed/build_color.py @@ -23,6 +23,7 @@ from tagstudio.core.library.alchemy.enums import TagColorEnum from tagstudio.core.library.alchemy.library import Library, slugify from tagstudio.core.library.alchemy.models import TagColorGroup +from tagstudio.core.utils.types import unwrap from tagstudio.qt.mixed.tag_color_preview import TagColorPreview from tagstudio.qt.mixed.tag_widget import ( get_border_color, @@ -126,8 +127,8 @@ def __init__(self, library: Library, color_group: TagColorGroup): self.border_checkbox.setFixedSize(22, 22) self.border_checkbox.clicked.connect( lambda checked: self.update_secondary( - color=QColor(self.preview_button.tag_color_group.secondary) - if self.preview_button.tag_color_group.secondary + color=QColor(unwrap(self.preview_button.tag_color_group).secondary) + if unwrap(self.preview_button.tag_color_group).secondary else None, color_border=checked, ) diff --git a/src/tagstudio/qt/mixed/build_tag.py b/src/tagstudio/qt/mixed/build_tag.py index 846a19611..b19e33048 100644 --- a/src/tagstudio/qt/mixed/build_tag.py +++ b/src/tagstudio/qt/mixed/build_tag.py @@ -28,6 +28,7 @@ from tagstudio.core.library.alchemy.enums import TagColorEnum from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Tag, TagColorGroup +from tagstudio.core.utils.types import unwrap from tagstudio.qt.mixed.tag_color_preview import TagColorPreview from tagstudio.qt.mixed.tag_color_selection import TagColorSelection from tagstudio.qt.mixed.tag_search import TagSearchModal, TagSearchPanel @@ -181,6 +182,7 @@ def __init__(self, library: Library, tag: Tag | None = None) -> None: self.color_layout.addWidget(self.color_title) self.color_button: TagColorPreview try: + assert tag is not None self.color_button = TagColorPreview(self.lib, tag.color) except Exception as e: # TODO: Investigate why this happens during tests @@ -316,7 +318,7 @@ def add_alias_callback(self): item = self.aliases_table.cellWidget(row, 1) item.setFocus() - def remove_alias_callback(self, alias_name: str, alias_id: int | None = None): + def remove_alias_callback(self, alias_name: str, alias_id: int): logger.info("remove_alias_callback") self.alias_ids.remove(alias_id) @@ -468,7 +470,7 @@ def add_aliases(self): def _update_new_alias_name_dict(self): for i in range(0, self.aliases_table.rowCount()): - widget = self.aliases_table.cellWidget(i, 1) + widget = cast(CustomTableItem, self.aliases_table.cellWidget(i, 1)) self.new_alias_names[widget.id] = widget.text() def _set_aliases(self): @@ -479,7 +481,7 @@ def _set_aliases(self): self.alias_names.clear() - last: QWidget = self.panel_save_button + last: QWidget = unwrap(self.panel_save_button) for alias_id in self.alias_ids: alias = self.lib.get_alias(self.tag.id, alias_id) @@ -574,8 +576,8 @@ def parent_post_init(self): self.setTabOrder(self.shorthand_field, self.aliases_add_button) self.setTabOrder(self.aliases_add_button, self.parent_tags_add_button) self.setTabOrder(self.parent_tags_add_button, self.color_button) - self.setTabOrder(self.color_button, self.panel_cancel_button) - self.setTabOrder(self.panel_cancel_button, self.panel_save_button) - self.setTabOrder(self.panel_save_button, self.aliases_table.cellWidget(0, 1)) + self.setTabOrder(self.color_button, unwrap(self.panel_cancel_button)) + self.setTabOrder(unwrap(self.panel_cancel_button), unwrap(self.panel_save_button)) + self.setTabOrder(unwrap(self.panel_save_button), self.aliases_table.cellWidget(0, 1)) self.name_field.selectAll() self.name_field.setFocus() diff --git a/src/tagstudio/qt/mixed/color_box.py b/src/tagstudio/qt/mixed/color_box.py index b5224994b..20866c438 100644 --- a/src/tagstudio/qt/mixed/color_box.py +++ b/src/tagstudio/qt/mixed/color_box.py @@ -13,6 +13,7 @@ from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX from tagstudio.core.library.alchemy.enums import TagColorEnum from tagstudio.core.library.alchemy.models import TagColorGroup +from tagstudio.core.utils.types import unwrap from tagstudio.qt.mixed.build_color import BuildColorPanel from tagstudio.qt.mixed.field_widget import FieldWidget from tagstudio.qt.mixed.tag_color_label import TagColorLabel @@ -88,7 +89,7 @@ def set_colors(self, colors: Iterable[TagColorGroup]): color_widgets: list[TagColorLabel] = [] while self.base_layout.itemAt(0): - self.base_layout.takeAt(0).widget().deleteLater() + unwrap(self.base_layout.takeAt(0)).widget().deleteLater() for color in colors_: color_widget = TagColorLabel( diff --git a/src/tagstudio/qt/mixed/drop_import_modal.py b/src/tagstudio/qt/mixed/drop_import_modal.py index 049fe9c80..766ce6083 100644 --- a/src/tagstudio/qt/mixed/drop_import_modal.py +++ b/src/tagstudio/qt/mixed/drop_import_modal.py @@ -21,6 +21,7 @@ QWidget, ) +from tagstudio.core.utils.types import unwrap from tagstudio.qt.mixed.progress_bar import ProgressWidget from tagstudio.qt.translations import Translations @@ -120,7 +121,9 @@ def collect_files_to_import(self, urls: list[QUrl]): continue self.files.append(f) - if (self.driver.lib.library_dir / self._get_relative_path(file)).exists(): + if ( + unwrap(self.driver.lib.library_dir) / self._get_relative_path(file) + ).exists(): self.duplicate_files.append(f) self.dirs_in_root.append(file.parent) @@ -132,7 +135,7 @@ def collect_files_to_import(self, urls: list[QUrl]): file.parent ) # to create relative path of files not in folder - if (Path(self.driver.lib.library_dir) / file.name).exists(): + if (Path(unwrap(self.driver.lib.library_dir)) / file.name).exists(): self.duplicate_files.append(file) def ask_duplicates_choice(self): @@ -205,8 +208,10 @@ def copy_files(self): new_name = self._get_renamed_duplicate_filename(dest_file) dest_file = dest_file.with_name(new_name) - (self.driver.lib.library_dir / dest_file).parent.mkdir(parents=True, exist_ok=True) - shutil.copyfile(file, self.driver.lib.library_dir / dest_file) + (unwrap(self.driver.lib.library_dir) / dest_file).parent.mkdir( + parents=True, exist_ok=True + ) + shutil.copyfile(file, unwrap(self.driver.lib.library_dir) / dest_file) file_count += 1 yield [file_count, duplicated_files_progress] @@ -226,7 +231,7 @@ def _get_renamed_duplicate_filename(self, filepath: Path) -> str: except ValueError: dot_idx = len(o_filename) - while (self.driver.lib.library_dir / filepath).exists(): + while (unwrap(self.driver.lib.library_dir) / filepath).exists(): filepath = filepath.with_name( o_filename[:dot_idx] + f" ({index})" + o_filename[dot_idx:] ) diff --git a/src/tagstudio/qt/mixed/migration_modal.py b/src/tagstudio/qt/mixed/migration_modal.py index c98505d18..13ee99b37 100644 --- a/src/tagstudio/qt/mixed/migration_modal.py +++ b/src/tagstudio/qt/mixed/migration_modal.py @@ -37,6 +37,7 @@ from tagstudio.core.library.alchemy.models import Entry, TagAlias from tagstudio.core.library.json.library import Library as JsonLibrary from tagstudio.core.library.json.library import Tag as JsonTag +from tagstudio.core.utils.types import unwrap from tagstudio.qt.controllers.paged_panel_controller import PagedPanel from tagstudio.qt.controllers.paged_panel_state import PagedPanelState from tagstudio.qt.translations import Translations @@ -561,7 +562,7 @@ def sanitize_json_field(value): sql_fields: list[tuple] = [] json_fields: list[tuple] = [] - sql_entry: Entry = self.sql_lib.get_entry_full(json_entry.id + 1) + sql_entry: Entry = unwrap(self.sql_lib.get_entry_full(json_entry.id + 1)) if not sql_entry: logger.info( "[Field Comparison]", @@ -595,7 +596,7 @@ def sanitize_json_field(value): tags_count += 1 json_tags = json_tags.union(value or []) else: - key: str = self.sql_lib.get_field_name_from_id(int_key).name + key: str = unwrap(self.sql_lib.get_field_name_from_id(int_key)).name json_fields.append((json_entry.id + 1, key, value)) json_fields.sort() @@ -710,8 +711,6 @@ def check_alias_parity(self) -> bool: def check_name_parity(self) -> bool: """Check if all JSON tag names match the new SQL tag names.""" - sql_name: str = None - json_name: str = None def sanitize(value): """Return value or convert a "not" value into None.""" @@ -719,8 +718,8 @@ def sanitize(value): for tag in self.sql_lib.tags: tag_id = tag.id # Tag IDs start at 0 - sql_name = sanitize(tag.name) - json_name = sanitize(self.json_lib.get_tag(tag_id).name) + sql_name: str = unwrap(sanitize(tag.name)) + json_name: str = unwrap(sanitize(self.json_lib.get_tag(tag_id).name)) logger.info( "[Name Parity]", @@ -742,8 +741,6 @@ def sanitize(value): def check_shorthand_parity(self) -> bool: """Check if all JSON shorthands match the new SQL shorthands.""" - sql_shorthand: str = None - json_shorthand: str = None def sanitize(value): """Return value or convert a "not" value into None.""" @@ -751,8 +748,8 @@ def sanitize(value): for tag in self.sql_lib.tags: tag_id = tag.id # Tag IDs start at 0 - sql_shorthand = sanitize(tag.shorthand) - json_shorthand = sanitize(self.json_lib.get_tag(tag_id).shorthand) + sql_shorthand: str = unwrap(sanitize(tag.shorthand)) + json_shorthand: str = unwrap(sanitize(self.json_lib.get_tag(tag_id).shorthand)) logger.info( "[Shorthand Parity]", diff --git a/src/tagstudio/qt/previews/renderer.py b/src/tagstudio/qt/previews/renderer.py index 069e735e4..8020d66dc 100644 --- a/src/tagstudio/qt/previews/renderer.py +++ b/src/tagstudio/qt/previews/renderer.py @@ -627,7 +627,7 @@ def _audio_album_thumb(filepath: Path, ext: str) -> Image.Image | None: artwork = Image.open(BytesIO(flac_covers[0].data)) elif ext in [".mp4", ".m4a", ".aac"]: mp4_tags: mp4.MP4 = mp4.MP4(filepath) - mp4_covers: list = mp4_tags.get("covr") + mp4_covers: list = unwrap(mp4_tags.get("covr")) if mp4_covers: artwork = Image.open(BytesIO(mp4_covers[0])) if artwork: @@ -1080,7 +1080,7 @@ def _image_thumb(filepath: Path) -> Image.Image: new_bg = Image.new("RGB", im.size, color="#1e1e1e") new_bg.paste(im, mask=im.getchannel(3)) im = new_bg - im = ImageOps.exif_transpose(im) + im = unwrap(ImageOps.exif_transpose(im)) except ( FileNotFoundError, UnidentifiedImageError, From f115edd659b16f84a20cc7c521ec460ea933a98c Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Tue, 9 Sep 2025 19:10:52 +0200 Subject: [PATCH 2/4] fix: revert change that caused tests to fail --- src/tagstudio/qt/mixed/build_tag.py | 2 +- src/tagstudio/qt/mixed/migration_modal.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tagstudio/qt/mixed/build_tag.py b/src/tagstudio/qt/mixed/build_tag.py index b19e33048..aabdc47a9 100644 --- a/src/tagstudio/qt/mixed/build_tag.py +++ b/src/tagstudio/qt/mixed/build_tag.py @@ -481,7 +481,7 @@ def _set_aliases(self): self.alias_names.clear() - last: QWidget = unwrap(self.panel_save_button) + last: QWidget = self.panel_save_button for alias_id in self.alias_ids: alias = self.lib.get_alias(self.tag.id, alias_id) diff --git a/src/tagstudio/qt/mixed/migration_modal.py b/src/tagstudio/qt/mixed/migration_modal.py index 13ee99b37..632cd8c49 100644 --- a/src/tagstudio/qt/mixed/migration_modal.py +++ b/src/tagstudio/qt/mixed/migration_modal.py @@ -748,8 +748,8 @@ def sanitize(value): for tag in self.sql_lib.tags: tag_id = tag.id # Tag IDs start at 0 - sql_shorthand: str = unwrap(sanitize(tag.shorthand)) - json_shorthand: str = unwrap(sanitize(self.json_lib.get_tag(tag_id).shorthand)) + sql_shorthand = sanitize(tag.shorthand) + json_shorthand = sanitize(self.json_lib.get_tag(tag_id).shorthand) logger.info( "[Shorthand Parity]", From 3a78bfdbfde93b1777b952ce311a3c8b90d288e3 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Tue, 9 Sep 2025 22:41:05 +0200 Subject: [PATCH 3/4] fix: revert removal of pyright ignore commet --- src/tagstudio/core/library/alchemy/library.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tagstudio/core/library/alchemy/library.py b/src/tagstudio/core/library/alchemy/library.py index 0ecfd6295..88c714206 100644 --- a/src/tagstudio/core/library/alchemy/library.py +++ b/src/tagstudio/core/library/alchemy/library.py @@ -1850,7 +1850,7 @@ def set_version(self, key: str, value: int) -> None: # TODO: Remove this once the 'preferences' table is removed. @deprecated("Use `get_version() for version and `ts_ignore` system for extension exclusion.") - def prefs(self, key: str | LibraryPrefs): + def prefs(self, key: str | LibraryPrefs): # pyright: ignore[reportUnknownParameterType] # load given item from Preferences table with Session(self.engine) as session: if isinstance(key, LibraryPrefs): From f08f21a13fdef23e8f2653c25118e4f1d9b7369b Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Wed, 10 Sep 2025 21:03:08 +0200 Subject: [PATCH 4/4] fix: add missing ignore comments --- src/tagstudio/core/library/alchemy/library.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tagstudio/core/library/alchemy/library.py b/src/tagstudio/core/library/alchemy/library.py index 88c714206..986183209 100644 --- a/src/tagstudio/core/library/alchemy/library.py +++ b/src/tagstudio/core/library/alchemy/library.py @@ -1856,11 +1856,11 @@ def prefs(self, key: str | LibraryPrefs): # pyright: ignore[reportUnknownParame if isinstance(key, LibraryPrefs): return unwrap( session.scalar(select(Preferences).where(Preferences.key == key.name)) - ).value + ).value # pyright: ignore[reportUnknownVariableType] else: return unwrap( session.scalar(select(Preferences).where(Preferences.key == key)) - ).value + ).value # pyright: ignore[reportUnknownVariableType] # TODO: Remove this once the 'preferences' table is removed. @deprecated("Use `get_version() for version and `ts_ignore` system for extension exclusion.")