diff --git a/src/tagstudio/core/library/alchemy/library.py b/src/tagstudio/core/library/alchemy/library.py index d7e3ecd3..9ddaceea 100644 --- a/src/tagstudio/core/library/alchemy/library.py +++ b/src/tagstudio/core/library/alchemy/library.py @@ -93,6 +93,7 @@ from tagstudio.core.library.alchemy.models import ( ) from tagstudio.core.library.alchemy.visitors import SQLBoolExpressionBuilder from tagstudio.core.library.json.library import Library as JsonLibrary +from tagstudio.core.utils.types import unwrap from tagstudio.qt.translations import Translations if TYPE_CHECKING: @@ -926,10 +927,9 @@ class Library: :return: number of entries matching the query and one page of results. """ assert isinstance(search, BrowsingState) - assert self.engine assert self.library_dir - with Session(self.engine, expire_on_commit=False) as session: + with Session(unwrap(self.engine), expire_on_commit=False) as session: statement = select(Entry.id, func.count().over()) if search.ast: @@ -1762,18 +1762,18 @@ class Library: # set given item in Preferences table with Session(self.engine) as session: # load existing preference and update value - pref: Preferences | None - stuff = session.scalars(select(Preferences)) logger.info([x.key for x in list(stuff)]) - if isinstance(key, LibraryPrefs): - pref = session.scalar(select(Preferences).where(Preferences.key == key.name)) - else: - pref = session.scalar(select(Preferences).where(Preferences.key == key)) + pref: Preferences = unwrap( + session.scalar( + select(Preferences).where( + Preferences.key == (key.name if isinstance(key, LibraryPrefs) else key) + ) + ) + ) logger.info("loading pref", pref=pref, key=key, value=value) - assert pref is not None pref.value = value session.add(pref) session.commit() diff --git a/src/tagstudio/core/library/alchemy/models.py b/src/tagstudio/core/library/alchemy/models.py index a1fc92e7..ea60e6f0 100644 --- a/src/tagstudio/core/library/alchemy/models.py +++ b/src/tagstudio/core/library/alchemy/models.py @@ -127,8 +127,8 @@ class Tag(Base): def __init__( self, + name: str, id: int | None = None, - name: str | None = None, shorthand: str | None = None, aliases: set[TagAlias] | None = None, parent_tags: set["Tag"] | None = None, @@ -147,8 +147,7 @@ class Tag(Base): self.shorthand = shorthand self.disambiguation_id = disambiguation_id self.is_category = is_category - assert not self.id - self.id = id + self.id = id # pyright: ignore[reportAttributeAccessIssue] super().__init__() def __str__(self) -> str: @@ -233,7 +232,7 @@ class Entry(Base): ) -> None: self.path = path self.folder = folder - self.id = id + self.id = id # pyright: ignore[reportAttributeAccessIssue] self.filename = path.name self.suffix = path.suffix.lstrip(".").lower() diff --git a/src/tagstudio/core/utils/missing_files.py b/src/tagstudio/core/utils/missing_files.py index 6326a2b9..37fc0631 100644 --- a/src/tagstudio/core/utils/missing_files.py +++ b/src/tagstudio/core/utils/missing_files.py @@ -8,6 +8,7 @@ from wcmatch import pathlib from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Entry from tagstudio.core.library.ignore import PATH_GLOB_FLAGS, Ignore +from tagstudio.core.utils.types import unwrap logger = structlog.get_logger() @@ -26,12 +27,11 @@ class MissingRegistry: def refresh_missing_files(self) -> Iterator[int]: """Track the number of entries that point to an invalid filepath.""" - assert self.library.library_dir logger.info("[refresh_missing_files] Refreshing missing files...") self.missing_file_entries = [] for i, entry in enumerate(self.library.all_entries()): - full_path = self.library.library_dir / entry.path + full_path = unwrap(self.library.library_dir) / entry.path if not full_path.exists() or not full_path.is_file(): self.missing_file_entries.append(entry) yield i @@ -41,11 +41,11 @@ class MissingRegistry: Works if files were just moved to different subfolders and don't have duplicate names. """ - assert self.library.library_dir + library_dir = unwrap(self.library.library_dir) matches: list[Path] = [] - ignore_patterns = Ignore.get_patterns(self.library.library_dir) - for path in pathlib.Path(str(self.library.library_dir)).glob( + ignore_patterns = Ignore.get_patterns(library_dir) + for path in pathlib.Path(str(library_dir)).glob( f"***/{match_entry.path.name}", flags=PATH_GLOB_FLAGS, exclude=ignore_patterns, @@ -53,7 +53,7 @@ class MissingRegistry: if path.is_dir(): continue if path.name == match_entry.path.name: - new_path = Path(path).relative_to(self.library.library_dir) + new_path = Path(path).relative_to(library_dir) matches.append(new_path) logger.info("[MissingRegistry] Matches", matches=matches) @@ -73,8 +73,8 @@ class MissingRegistry: ) if not self.library.update_entry_path(entry.id, item_matches[0]): try: - match = self.library.get_entry_full_by_path(item_matches[0]) - entry_full = self.library.get_entry_full(entry.id) + match = unwrap(self.library.get_entry_full_by_path(item_matches[0])) + entry_full = unwrap(self.library.get_entry_full(entry.id)) self.library.merge_entries(entry_full, match) except AttributeError: continue diff --git a/src/tagstudio/core/utils/types.py b/src/tagstudio/core/utils/types.py new file mode 100644 index 00000000..d21feb95 --- /dev/null +++ b/src/tagstudio/core/utils/types.py @@ -0,0 +1,14 @@ +# Licensed under the GPL-3.0 License. +# Created for TagStudio: https://github.com/CyanVoxel/TagStudio + +from typing import TypeVar + +T = TypeVar("T") + + +def unwrap(optional: T | None, default: T | None = None) -> T: + if optional is not None: + return optional + if default is not None: + return default + raise ValueError("Expected a value, but got None and no default was provided.") diff --git a/src/tagstudio/qt/controller/components/tag_box_controller.py b/src/tagstudio/qt/controller/components/tag_box_controller.py index 9bb144d8..d07a08a4 100644 --- a/src/tagstudio/qt/controller/components/tag_box_controller.py +++ b/src/tagstudio/qt/controller/components/tag_box_controller.py @@ -10,6 +10,7 @@ from PySide6.QtCore import Signal from tagstudio.core.enums import TagClickActionOption from tagstudio.core.library.alchemy.enums import BrowsingState from tagstudio.core.library.alchemy.models import Tag +from tagstudio.core.utils.types import unwrap from tagstudio.qt.modals.build_tag import BuildTagPanel from tagstudio.qt.view.components.tag_box_view import TagBoxWidgetView from tagstudio.qt.widgets.panel import PanelModal @@ -47,10 +48,9 @@ class TagBoxWidget(TagBoxWidgetView): # due to needing to implement a visitor that turns an AST to a string # So if that exists when you read this, change the following accordingly. current = self.__driver.browsing_history.current - suffix = BrowsingState.from_tag_id( - tag.id, self.__driver.browsing_history.current - ).query - assert suffix is not None + suffix = unwrap( + BrowsingState.from_tag_id(tag.id, self.__driver.browsing_history.current).query + ) self.__driver.update_browsing_state( current.with_search_query( f"{current.query} {suffix}" if current.query else suffix diff --git a/src/tagstudio/qt/helpers/file_opener.py b/src/tagstudio/qt/helpers/file_opener.py index 972d23af..583830c8 100644 --- a/src/tagstudio/qt/helpers/file_opener.py +++ b/src/tagstudio/qt/helpers/file_opener.py @@ -14,6 +14,7 @@ from PySide6.QtCore import Qt from PySide6.QtGui import QMouseEvent from PySide6.QtWidgets import QLabel, QWidget +from tagstudio.core.utils.types import unwrap from tagstudio.qt.helpers.silent_popen import silent_Popen logger = structlog.get_logger(__name__) @@ -148,8 +149,7 @@ class FileOpenerLabel(QLabel): ev (QMouseEvent): The mouse press event. """ if ev.button() == Qt.MouseButton.LeftButton: - assert self.filepath is not None, "File path is not set" - opener = FileOpenerHelper(self.filepath) + opener = FileOpenerHelper(unwrap(self.filepath)) opener.open_explorer() elif ev.button() == Qt.MouseButton.RightButton: # Show context menu diff --git a/src/tagstudio/qt/modals/folders_to_tags.py b/src/tagstudio/qt/modals/folders_to_tags.py index 20855660..6c4a9d93 100644 --- a/src/tagstudio/qt/modals/folders_to_tags.py +++ b/src/tagstudio/qt/modals/folders_to_tags.py @@ -26,6 +26,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 from tagstudio.core.palette import ColorType, get_tag_color +from tagstudio.core.utils.types import unwrap from tagstudio.qt.flowlayout import FlowLayout from tagstudio.qt.translations import Translations @@ -93,8 +94,7 @@ def reverse_tag(library: Library, tag: Tag, items: list[Tag] | None) -> list[Tag parent_tag = None # to avoid subtag unbound error for parent_tag_id in tag.parent_ids: parent_tag = library.get_tag(parent_tag_id) - assert parent_tag is not None - return reverse_tag(library, parent_tag, items) + return reverse_tag(library, unwrap(parent_tag), items) # =========== UI =========== @@ -274,8 +274,7 @@ class TreeItem(QWidget): self.label = QLabel() self.tag_layout.addWidget(self.label) - assert data.tag is not None and parent_tag is not None - self.tag_widget = ModifiedTagWidget(data.tag, parent_tag) + self.tag_widget = ModifiedTagWidget(unwrap(data.tag), unwrap(parent_tag)) self.tag_widget.bg_button.clicked.connect(lambda: self.hide_show()) self.tag_layout.addWidget(self.tag_widget) diff --git a/src/tagstudio/qt/modals/tag_search.py b/src/tagstudio/qt/modals/tag_search.py index ab1d00ca..732c78b8 100644 --- a/src/tagstudio/qt/modals/tag_search.py +++ b/src/tagstudio/qt/modals/tag_search.py @@ -213,11 +213,8 @@ class TagSearchPanel(PanelWidget): logger.info("[TagSearchPanel] Updating Tags") # Remove the "Create & Add" button if one exists - create_button: QPushButton | None = None if self.create_button_in_layout and self.scroll_layout.count(): - create_button = self.scroll_layout.takeAt(self.scroll_layout.count() - 1).widget() # type: ignore - assert create_button is not None - create_button.deleteLater() + self.scroll_layout.takeAt(self.scroll_layout.count() - 1).widget().deleteLater() self.create_button_in_layout = False # Get results for the search query diff --git a/src/tagstudio/qt/ts_qt.py b/src/tagstudio/qt/ts_qt.py index 6c68ec0e..e997cca6 100644 --- a/src/tagstudio/qt/ts_qt.py +++ b/src/tagstudio/qt/ts_qt.py @@ -66,6 +66,7 @@ from tagstudio.core.palette import ColorType, UiColor, get_ui_color from tagstudio.core.query_lang.util import ParsingError from tagstudio.core.ts_core import TagStudioCore from tagstudio.core.utils.refresh_dir import RefreshDirTracker +from tagstudio.core.utils.types import unwrap from tagstudio.core.utils.web import strip_web_protocol from tagstudio.qt.cache_manager import CacheManager from tagstudio.qt.helpers.custom_runnable import CustomRunnable @@ -979,7 +980,6 @@ class QtDriver(DriverMixin, QObject): def add_new_files_callback(self): """Run when user initiates adding new files to the Library.""" - assert self.lib.library_dir tracker = RefreshDirTracker(self.lib) pw = ProgressWidget( @@ -991,7 +991,9 @@ class QtDriver(DriverMixin, QObject): pw.update_label(Translations["library.refresh.scanning_preparing"]) pw.show() - iterator = FunctionIterator(lambda lib=self.lib.library_dir: tracker.refresh_dir(lib)) + iterator = FunctionIterator( + lambda lib=unwrap(self.lib.library_dir): tracker.refresh_dir(lib) # noqa: B008 + ) iterator.value.connect( lambda x: ( pw.update_progress(x + 1), @@ -1534,7 +1536,6 @@ class QtDriver(DriverMixin, QObject): if not self.lib.library_dir: logger.info("Library not loaded") return - assert self.lib.engine if state: self.browsing_history.push(state) diff --git a/src/tagstudio/qt/view/widgets/preview_panel_view.py b/src/tagstudio/qt/view/widgets/preview_panel_view.py index 8bb819d4..6bc961c3 100644 --- a/src/tagstudio/qt/view/widgets/preview_panel_view.py +++ b/src/tagstudio/qt/view/widgets/preview_panel_view.py @@ -19,6 +19,7 @@ from tagstudio.core.enums import Theme from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Entry from tagstudio.core.palette import ColorType, UiColor, get_ui_color +from tagstudio.core.utils.types import unwrap from tagstudio.qt.controller.widgets.preview.preview_thumb_controller import PreviewThumb from tagstudio.qt.translations import Translations from tagstudio.qt.widgets.preview.field_containers import FieldContainers @@ -153,11 +154,9 @@ class PreviewPanelView(QWidget): # One Item Selected elif len(selected) == 1: entry_id = selected[0] - entry: Entry | None = self.lib.get_entry(entry_id) - assert entry is not None + entry: Entry = unwrap(self.lib.get_entry(entry_id)) - assert self.lib.library_dir is not None - filepath: Path = self.lib.library_dir / entry.path + filepath: Path = unwrap(self.lib.library_dir) / entry.path if update_preview: stats: FileAttributeData = self.__thumb.display_file(filepath) diff --git a/src/tagstudio/qt/widgets/collage_icon.py b/src/tagstudio/qt/widgets/collage_icon.py index 63c1ddbf..13930085 100644 --- a/src/tagstudio/qt/widgets/collage_icon.py +++ b/src/tagstudio/qt/widgets/collage_icon.py @@ -14,6 +14,7 @@ from PySide6.QtCore import QObject, Signal from tagstudio.core.library.alchemy.library import Library from tagstudio.core.media_types import MediaCategories +from tagstudio.core.utils.types import unwrap from tagstudio.qt.helpers.file_tester import is_readable_video logger = structlog.get_logger(__name__) @@ -35,10 +36,8 @@ class CollageIconRenderer(QObject): data_only_mode: bool, keep_aspect: bool, ): - entry = self.lib.get_entry(entry_id) - lib_dir = self.lib.library_dir - assert lib_dir is not None and entry is not None - filepath = lib_dir / entry.path + entry = unwrap(self.lib.get_entry(entry_id)) + filepath = unwrap(self.lib.library_dir) / entry.path color: str = "" try: diff --git a/src/tagstudio/qt/widgets/item_thumb.py b/src/tagstudio/qt/widgets/item_thumb.py index 77fc9eab..7b83c0b6 100644 --- a/src/tagstudio/qt/widgets/item_thumb.py +++ b/src/tagstudio/qt/widgets/item_thumb.py @@ -26,6 +26,7 @@ from tagstudio.core.constants import TAG_ARCHIVED, TAG_FAVORITE from tagstudio.core.library.alchemy.enums import ItemType from tagstudio.core.library.alchemy.library import Library from tagstudio.core.media_types import MediaCategories, MediaType +from tagstudio.core.utils.types import unwrap from tagstudio.qt.flowlayout import FlowWidget from tagstudio.qt.helpers.file_opener import FileOpenerHelper from tagstudio.qt.platform_strings import open_file_str, trash_term @@ -538,8 +539,7 @@ class ItemThumb(FlowWidget): if not entry: continue - assert self.lib.library_dir is not None - url = QUrl.fromLocalFile(Path(self.lib.library_dir) / entry.path) + url = QUrl.fromLocalFile(Path(unwrap(self.lib.library_dir)) / entry.path) paths.append(url) mimedata.setUrls(paths) diff --git a/src/tagstudio/qt/widgets/preview/field_containers.py b/src/tagstudio/qt/widgets/preview/field_containers.py index db88dae1..6ed7b1b0 100644 --- a/src/tagstudio/qt/widgets/preview/field_containers.py +++ b/src/tagstudio/qt/widgets/preview/field_containers.py @@ -32,6 +32,7 @@ from tagstudio.core.library.alchemy.fields import ( ) from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Entry, Tag +from tagstudio.core.utils.types import unwrap from tagstudio.qt.controller.components.tag_box_controller import TagBoxWidget from tagstudio.qt.translations import Translations from tagstudio.qt.widgets.datetime_picker import DatetimePicker @@ -110,8 +111,7 @@ class FieldContainers(QWidget): """Update tags and fields from a single Entry source.""" logger.warning("[FieldContainers] Updating Selection", entry_id=entry_id) - entry = self.lib.get_entry_full(entry_id) - assert entry is not None + entry = unwrap(self.lib.get_entry_full(entry_id)) self.cached_entries = [entry] self.update_granular(entry.tags, entry.fields, update_badges) @@ -177,9 +177,8 @@ class FieldContainers(QWidget): "Character" -> "Johnny Bravo", "TV" -> Johnny Bravo" """ - tag_obj = self.lib.get_tag(tag_id) # Get full object + tag_obj = unwrap(self.lib.get_tag(tag_id)) # Get full object if p_ids is None: - assert tag_obj is not None p_ids = tag_obj.parent_ids for p_id in p_ids: @@ -188,8 +187,7 @@ class FieldContainers(QWidget): # If the p_tag has p_tags of its own, recursively link those to the original Tag. if tag_id not in cluster_map[p_id]: cluster_map[p_id].add(tag_id) - p_tag = self.lib.get_tag(p_id) # Get full object - assert p_tag is not None + p_tag = unwrap(self.lib.get_tag(p_id)) # Get full object if p_tag.parent_ids: add_to_cluster( tag_id, diff --git a/src/tagstudio/qt/widgets/preview/file_attributes.py b/src/tagstudio/qt/widgets/preview/file_attributes.py index 26e1ce6e..eb4137d8 100644 --- a/src/tagstudio/qt/widgets/preview/file_attributes.py +++ b/src/tagstudio/qt/widgets/preview/file_attributes.py @@ -23,6 +23,7 @@ from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.ignore import Ignore from tagstudio.core.media_types import MediaCategories from tagstudio.core.palette import ColorType, UiColor, get_ui_color +from tagstudio.core.utils.types import unwrap from tagstudio.qt.helpers.file_opener import FileOpenerHelper, FileOpenerLabel from tagstudio.qt.translations import Translations @@ -156,13 +157,11 @@ class FileAttributes(QWidget): self.dimensions_label.setHidden(True) else: ext = filepath.suffix.lower() - self.library_path = self.library.library_dir display_path = filepath if self.driver.settings.show_filepath == ShowFilepathOption.SHOW_FULL_PATHS: display_path = filepath elif self.driver.settings.show_filepath == ShowFilepathOption.SHOW_RELATIVE_PATHS: - assert self.library_path is not None - display_path = Path(filepath).relative_to(self.library_path) + display_path = Path(filepath).relative_to(unwrap(self.library.library_dir)) elif self.driver.settings.show_filepath == ShowFilepathOption.SHOW_FILENAMES_ONLY: display_path = Path(filepath.name) @@ -215,12 +214,11 @@ class FileAttributes(QWidget): if ext_display: stats_label_text += ext_display - assert self.library.library_dir red = get_ui_color(ColorType.PRIMARY, UiColor.RED) orange = get_ui_color(ColorType.PRIMARY, UiColor.ORANGE) if Ignore.compiled_patterns and not Ignore.compiled_patterns.match( - filepath.relative_to(self.library.library_dir) + filepath.relative_to(unwrap(self.library.library_dir)) ): stats_label_text = ( f"{stats_label_text}" diff --git a/tests/conftest.py b/tests/conftest.py index b63dbb65..a2c97c10 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,6 +18,7 @@ sys.path.insert(0, str(CWD.parent)) from tagstudio.core.constants import THUMB_CACHE_NAME, TS_FOLDER_NAME from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Entry, Tag +from tagstudio.core.utils.types import unwrap from tagstudio.qt.ts_qt import QtDriver @@ -32,22 +33,22 @@ def file_mediatypes_library(): status = lib.open_library(Path(""), ":memory:") assert status.success - assert lib.folder + folder = unwrap(lib.folder) entry1 = Entry( - folder=lib.folder, + folder=folder, path=Path("foo.png"), fields=lib.default_fields, ) entry2 = Entry( - folder=lib.folder, + folder=folder, path=Path("bar.png"), fields=lib.default_fields, ) entry3 = Entry( - folder=lib.folder, + folder=folder, path=Path("baz.apng"), fields=lib.default_fields, ) @@ -83,7 +84,7 @@ def library(request, library_dir: Path): # pyright: ignore lib = Library() status = lib.open_library(library_path, ":memory:") assert status.success - assert lib.folder + folder = unwrap(lib.folder) tag = Tag( name="foo", @@ -112,7 +113,7 @@ def library(request, library_dir: Path): # pyright: ignore # default item with deterministic name entry = Entry( id=1, - folder=lib.folder, + folder=folder, path=Path("foo.txt"), fields=lib.default_fields, ) @@ -120,7 +121,7 @@ def library(request, library_dir: Path): # pyright: ignore entry2 = Entry( id=2, - folder=lib.folder, + folder=folder, path=Path("one/two/bar.md"), fields=lib.default_fields, ) diff --git a/tests/macros/test_dupe_entries.py b/tests/macros/test_dupe_entries.py index 94351425..ecaca5cb 100644 --- a/tests/macros/test_dupe_entries.py +++ b/tests/macros/test_dupe_entries.py @@ -7,22 +7,23 @@ from pathlib import Path from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Entry from tagstudio.core.utils.dupe_files import DupeRegistry +from tagstudio.core.utils.types import unwrap CWD = Path(__file__).parent def test_refresh_dupe_files(library: Library): library.library_dir = Path("/tmp/") - assert library.folder + folder = unwrap(library.folder) entry = Entry( - folder=library.folder, + folder=folder, path=Path("bar/foo.txt"), fields=library.default_fields, ) entry2 = Entry( - folder=library.folder, + folder=folder, path=Path("foo/foo.txt"), fields=library.default_fields, ) diff --git a/tests/macros/test_missing_files.py b/tests/macros/test_missing_files.py index 209090f9..20b34f5c 100644 --- a/tests/macros/test_missing_files.py +++ b/tests/macros/test_missing_files.py @@ -10,6 +10,7 @@ import pytest from tagstudio.core.library.alchemy.enums import BrowsingState from tagstudio.core.library.alchemy.library import Library from tagstudio.core.utils.missing_files import MissingRegistry +from tagstudio.core.utils.types import unwrap CWD = Path(__file__).parent @@ -20,8 +21,7 @@ def test_refresh_missing_files(library: Library): registry = MissingRegistry(library=library) # touch the file `one/two/bar.md` but in wrong location to simulate a moved file - assert library.library_dir - (library.library_dir / "bar.md").touch() + (unwrap(library.library_dir) / "bar.md").touch() # no files actually exist, so it should return all entries assert list(registry.refresh_missing_files()) == [0, 1] diff --git a/tests/macros/test_refresh_dir.py b/tests/macros/test_refresh_dir.py index f692fc7d..f8651fc3 100644 --- a/tests/macros/test_refresh_dir.py +++ b/tests/macros/test_refresh_dir.py @@ -10,6 +10,7 @@ import pytest from tagstudio.core.enums import LibraryPrefs from tagstudio.core.library.alchemy.library import Library from tagstudio.core.utils.refresh_dir import RefreshDirTracker +from tagstudio.core.utils.types import unwrap CWD = Path(__file__).parent @@ -17,14 +18,14 @@ CWD = Path(__file__).parent @pytest.mark.parametrize("exclude_mode", [True, False]) @pytest.mark.parametrize("library", [TemporaryDirectory()], indirect=True) def test_refresh_new_files(library: Library, exclude_mode: bool): - assert library.library_dir + library_dir = unwrap(library.library_dir) # Given library.set_prefs(LibraryPrefs.IS_EXCLUDE_LIST, exclude_mode) library.set_prefs(LibraryPrefs.EXTENSION_LIST, [".md"]) registry = RefreshDirTracker(library=library) library.included_files.clear() - (library.library_dir / "FOO.MD").touch() + (library_dir / "FOO.MD").touch() # Test if the single file was added - list(registry.refresh_dir(library.library_dir, force_internal_tools=True)) + list(registry.refresh_dir(library_dir, force_internal_tools=True)) assert registry.files_not_in_library == [Path("FOO.MD")] diff --git a/tests/qt/test_build_tag_panel.py b/tests/qt/test_build_tag_panel.py index 5d5332d5..3c92b7d1 100644 --- a/tests/qt/test_build_tag_panel.py +++ b/tests/qt/test_build_tag_panel.py @@ -9,6 +9,7 @@ from pytestqt.qtbot import QtBot from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Tag, TagAlias +from tagstudio.core.utils.types import unwrap from tagstudio.qt.modals.build_tag import BuildTagPanel, CustomTableItem from tagstudio.qt.translations import Translations @@ -16,10 +17,8 @@ from tagstudio.qt.translations import Translations def test_build_tag_panel_add_sub_tag_callback( qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag] ): - parent = library.add_tag(generate_tag("xxx", id=123)) - child = library.add_tag(generate_tag("xx", id=124)) - assert child - assert parent + parent = unwrap(library.add_tag(generate_tag("xxx", id=123))) + child = unwrap(library.add_tag(generate_tag("xx", id=124))) panel: BuildTagPanel = BuildTagPanel(library, child) qtbot.addWidget(panel) @@ -32,16 +31,12 @@ def test_build_tag_panel_add_sub_tag_callback( def test_build_tag_panel_remove_subtag_callback( qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag] ): - parent = library.add_tag(generate_tag("xxx", id=123)) - child = library.add_tag(generate_tag("xx", id=124)) - assert child - assert parent + parent = unwrap(library.add_tag(generate_tag("xxx", id=123))) + child = unwrap(library.add_tag(generate_tag("xx", id=124))) library.update_tag(child, {parent.id}, [], []) - child = library.get_tag(child.id) - - assert child + child = unwrap(library.get_tag(child.id)) panel: BuildTagPanel = BuildTagPanel(library, child) qtbot.addWidget(panel) @@ -59,8 +54,7 @@ os.environ["QT_QPA_PLATFORM"] = "offscreen" def test_build_tag_panel_add_alias_callback( qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag] ): - tag = library.add_tag(generate_tag("xxx", id=123)) - assert tag + tag = unwrap(library.add_tag(generate_tag("xxx", id=123))) panel: BuildTagPanel = BuildTagPanel(library, tag) qtbot.addWidget(panel) @@ -73,13 +67,11 @@ def test_build_tag_panel_add_alias_callback( def test_build_tag_panel_remove_alias_callback( qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag] ): - tag: Tag | None = library.add_tag(generate_tag("xxx", id=123)) - assert tag + tag: Tag = unwrap(library.add_tag(generate_tag("xxx", id=123))) library.update_tag(tag, [], {"alias", "alias_2"}, {123, 124}) - tag = library.get_tag(tag.id) - assert tag + tag = unwrap(library.get_tag(tag.id)) assert "alias" in tag.alias_strings assert "alias_2" in tag.alias_strings @@ -87,8 +79,7 @@ def test_build_tag_panel_remove_alias_callback( panel: BuildTagPanel = BuildTagPanel(library, tag) qtbot.addWidget(panel) - alias: TagAlias | None = library.get_alias(tag.id, tag.alias_ids[0]) - assert alias + alias: TagAlias = unwrap(library.get_alias(tag.id, tag.alias_ids[0])) panel.remove_alias_callback(alias.name, alias.id) @@ -100,10 +91,8 @@ def test_build_tag_panel_remove_alias_callback( def test_build_tag_panel_set_parent_tags( qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag] ): - parent = library.add_tag(generate_tag("parent", id=123)) - child = library.add_tag(generate_tag("child", id=124)) - assert parent - assert child + parent = unwrap(library.add_tag(generate_tag("parent", id=123))) + child = unwrap(library.add_tag(generate_tag("child", id=124))) library.add_parent_tag(parent.id, child.id) @@ -119,13 +108,11 @@ def test_build_tag_panel_set_parent_tags( def test_build_tag_panel_add_aliases( qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag] ): - tag: Tag | None = library.add_tag(generate_tag("xxx", id=123)) - assert tag + tag: Tag = unwrap(library.add_tag(generate_tag("xxx", id=123))) library.update_tag(tag, [], {"alias", "alias_2"}, {123, 124}) - tag = library.get_tag(tag.id) - assert tag + tag = unwrap(library.get_tag(tag.id)) assert "alias" in tag.alias_strings assert "alias_2" in tag.alias_strings @@ -159,13 +146,11 @@ def test_build_tag_panel_add_aliases( def test_build_tag_panel_set_aliases( qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag] ): - tag: Tag | None = library.add_tag(generate_tag("xxx", id=123)) - assert tag + tag: Tag = unwrap(library.add_tag(generate_tag("xxx", id=123))) library.update_tag(tag, [], {"alias"}, {123}) - tag = library.get_tag(tag.id) - assert tag + tag = unwrap(library.get_tag(tag.id)) assert len(tag.alias_ids) == 1 @@ -178,14 +163,12 @@ def test_build_tag_panel_set_aliases( def test_build_tag_panel_set_tag(qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag]): - tag = library.add_tag(generate_tag("xxx", id=123)) - assert tag + tag = unwrap(library.add_tag(generate_tag("xxx", id=123))) panel: BuildTagPanel = BuildTagPanel(library, tag) qtbot.addWidget(panel) - assert panel.tag - assert panel.tag.name == "xxx" + assert unwrap(panel.tag).name == "xxx" def test_build_tag_panel_build_tag(qtbot: QtBot, library: Library): @@ -194,5 +177,4 @@ def test_build_tag_panel_build_tag(qtbot: QtBot, library: Library): tag: Tag = panel.build_tag() - assert tag assert tag.name == Translations["tag.new"] diff --git a/tests/qt/test_field_containers.py b/tests/qt/test_field_containers.py index 8ba3d3f3..3090437e 100644 --- a/tests/qt/test_field_containers.py +++ b/tests/qt/test_field_containers.py @@ -5,6 +5,7 @@ from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Entry, Tag +from tagstudio.core.utils.types import unwrap from tagstudio.qt.controller.widgets.preview_panel_controller import PreviewPanel from tagstudio.qt.ts_qt import QtDriver @@ -139,8 +140,7 @@ def test_meta_tag_category(qt_driver: QtDriver, library: Library, entry_full: En match i: case 0: # Check if the container is the Meta Tags category - tag: Tag | None = library.get_tag(2) - assert tag + tag: Tag = unwrap(library.get_tag(2)) assert container.title == f"

{tag.name}

" case 1: # Check if the container is the Tags category @@ -156,8 +156,7 @@ def test_custom_tag_category(qt_driver: QtDriver, library: Library, entry_full: panel = PreviewPanel(library, qt_driver) # Set tag 1000 (foo) as a category - tag: Tag | None = library.get_tag(1000) - assert tag + tag: Tag = unwrap(library.get_tag(1000)) tag.is_category = True library.update_tag( tag, @@ -176,8 +175,7 @@ def test_custom_tag_category(qt_driver: QtDriver, library: Library, entry_full: match i: case 0: # Check if the container is the Meta Tags category - tag_2: Tag | None = library.get_tag(2) - assert tag_2 + tag_2: Tag = unwrap(library.get_tag(2)) assert container.title == f"

{tag_2.name}

" case 1: # Check if the container is the custom "foo" category diff --git a/tests/qt/test_file_path_options.py b/tests/qt/test_file_path_options.py index c7f4be9f..36e5c31f 100644 --- a/tests/qt/test_file_path_options.py +++ b/tests/qt/test_file_path_options.py @@ -18,6 +18,7 @@ from pytestqt.qtbot import QtBot from tagstudio.core.enums import ShowFilepathOption from tagstudio.core.library.alchemy.library import Library, LibraryStatus from tagstudio.core.library.alchemy.models import Entry +from tagstudio.core.utils.types import unwrap from tagstudio.qt.controller.widgets.preview_panel_controller import PreviewPanel from tagstudio.qt.modals.settings_panel import SettingsPanel from tagstudio.qt.ts_qt import QtDriver @@ -76,8 +77,7 @@ def test_file_path_display( entry = library.get_entry(2) assert isinstance(entry, Entry) filename = entry.path - assert library.library_dir is not None - panel._file_attributes_widget.update_stats(filepath=library.library_dir / filename) # pyright: ignore[reportPrivateUsage] + panel._file_attributes_widget.update_stats(filepath=unwrap(library.library_dir) / filename) # pyright: ignore[reportPrivateUsage] # Generate the expected file string. # This is copied directly from the file_attributes.py file diff --git a/tests/qt/test_qt_driver.py b/tests/qt/test_qt_driver.py index 43106dda..508844df 100644 --- a/tests/qt/test_qt_driver.py +++ b/tests/qt/test_qt_driver.py @@ -4,6 +4,7 @@ from tagstudio.core.library.alchemy.enums import BrowsingState, ItemType +from tagstudio.core.utils.types import unwrap from tagstudio.qt.ts_qt import QtDriver from tagstudio.qt.widgets.item_thumb import ItemThumb @@ -23,23 +24,20 @@ def test_browsing_state_update(qt_driver: QtDriver): state = BrowsingState.from_tag_name("foo") qt_driver.update_browsing_state(state) assert len(qt_driver.frame_content) == 1 - entry = qt_driver.lib.get_entry_full(qt_driver.frame_content[0]) - assert entry + entry = unwrap(qt_driver.lib.get_entry_full(qt_driver.frame_content[0])) assert list(entry.tags)[0].name == "foo" # When state is not changed, previous one is still applied qt_driver.update_browsing_state() assert len(qt_driver.frame_content) == 1 - entry = qt_driver.lib.get_entry_full(qt_driver.frame_content[0]) - assert entry + entry = unwrap(qt_driver.lib.get_entry_full(qt_driver.frame_content[0])) assert list(entry.tags)[0].name == "foo" # When state property is changed, previous one is overwritten state = BrowsingState.from_path("*bar.md") qt_driver.update_browsing_state(state) assert len(qt_driver.frame_content) == 1 - entry = qt_driver.lib.get_entry_full(qt_driver.frame_content[0]) - assert entry + entry = unwrap(qt_driver.lib.get_entry_full(qt_driver.frame_content[0])) assert list(entry.tags)[0].name == "bar" diff --git a/tests/test_library.py b/tests/test_library.py index f96d81d6..edade1b2 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -18,81 +18,70 @@ from tagstudio.core.library.alchemy.fields import ( ) from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Entry, Tag +from tagstudio.core.utils.types import unwrap logger = structlog.get_logger() def test_library_add_alias(library: Library, generate_tag: Callable[..., Tag]): - tag = library.add_tag(generate_tag("xxx", id=123)) - assert tag + tag = unwrap(library.add_tag(generate_tag("xxx", id=123))) parent_ids: set[int] = set() alias_ids: set[int] = set() alias_names: set[str] = set() alias_names.add("test_alias") library.update_tag(tag, parent_ids, alias_names, alias_ids) - tag = library.get_tag(tag.id) - assert tag is not None + tag = unwrap(library.get_tag(tag.id)) alias_ids = set(tag.alias_ids) assert len(alias_ids) == 1 def test_library_get_alias(library: Library, generate_tag: Callable[..., Tag]): - tag = library.add_tag(generate_tag("xxx", id=123)) - assert tag + tag = unwrap(library.add_tag(generate_tag("xxx", id=123))) parent_ids: set[int] = set() alias_ids: list[int] = [] alias_names: set[str] = set() alias_names.add("test_alias") library.update_tag(tag, parent_ids, alias_names, alias_ids) - tag = library.get_tag(tag.id) - assert tag is not None + tag = unwrap(library.get_tag(tag.id)) alias_ids = tag.alias_ids - alias = library.get_alias(tag.id, alias_ids[0]) - assert alias is not None + alias = unwrap(library.get_alias(tag.id, alias_ids[0])) assert alias.name == "test_alias" def test_library_update_alias(library: Library, generate_tag: Callable[..., Tag]): - tag: Tag | None = library.add_tag(generate_tag("xxx", id=123)) - assert tag is not None + tag: Tag = unwrap(library.add_tag(generate_tag("xxx", id=123))) parent_ids: set[int] = set() alias_ids: list[int] = [] alias_names: set[str] = set() alias_names.add("test_alias") library.update_tag(tag, parent_ids, alias_names, alias_ids) - tag = library.get_tag(tag.id) - assert tag is not None + tag = unwrap(library.get_tag(tag.id)) alias_ids = tag.alias_ids - alias = library.get_alias(tag.id, alias_ids[0]) - assert alias is not None + alias = unwrap(library.get_alias(tag.id, alias_ids[0])) assert alias.name == "test_alias" alias_names.remove("test_alias") alias_names.add("alias_update") library.update_tag(tag, parent_ids, alias_names, alias_ids) - tag = library.get_tag(tag.id) - assert tag is not None + tag = unwrap(library.get_tag(tag.id)) assert len(tag.alias_ids) == 1 - alias = library.get_alias(tag.id, tag.alias_ids[0]) - assert alias is not None + alias = unwrap(library.get_alias(tag.id, tag.alias_ids[0])) assert alias.name == "alias_update" @pytest.mark.parametrize("library", [TemporaryDirectory()], indirect=True) def test_library_add_file(library: Library): """Check Entry.path handling for insert vs lookup""" - assert library.folder is not None - entry = Entry( path=Path("bar.txt"), - folder=library.folder, + folder=unwrap(library.folder), fields=library.default_fields, ) @@ -103,30 +92,26 @@ def test_library_add_file(library: Library): def test_create_tag(library: Library, generate_tag: Callable[..., Tag]): # tag already exists - assert not library.add_tag(generate_tag("foo", id=1000)) + assert library.add_tag(generate_tag("foo", id=1000)) is None # new tag name - tag = library.add_tag(generate_tag("xxx", id=123)) - assert tag + tag = unwrap(library.add_tag(generate_tag("xxx", id=123))) assert tag.id == 123 - tag_inc = library.add_tag(generate_tag("yyy")) - assert tag_inc is not None + tag_inc = unwrap(library.add_tag(generate_tag("yyy"))) assert tag_inc.id > 1000 def test_tag_self_parent(library: Library, generate_tag: Callable[..., Tag]): # tag already exists - assert not library.add_tag(generate_tag("foo", id=1000)) + assert library.add_tag(generate_tag("foo", id=1000)) is None # new tag name - tag = library.add_tag(generate_tag("xxx", id=123)) - assert tag + tag = unwrap(library.add_tag(generate_tag("xxx", id=123))) assert tag.id == 123 library.update_tag(tag, {tag.id}, [], []) - tag = library.get_tag(tag.id) - assert tag is not None + tag = unwrap(library.get_tag(tag.id)) assert len(tag.parent_ids) == 0 @@ -153,15 +138,13 @@ def test_tag_search(library: Library): def test_get_entry(library: Library, entry_min: Entry): - assert entry_min.id - result = library.get_entry_full(entry_min.id) - assert result + result = unwrap(library.get_entry_full(unwrap(entry_min.id))) assert len(result.tags) == 1 def test_entries_count(library: Library): - assert library.folder is not None - entries = [Entry(path=Path(f"{x}.txt"), folder=library.folder, fields=[]) for x in range(10)] + folder = unwrap(library.folder) + entries = [Entry(path=Path(f"{x}.txt"), folder=folder, fields=[]) for x in range(10)] new_ids = library.add_entries(entries) assert len(new_ids) == 10 @@ -173,29 +156,21 @@ def test_entries_count(library: Library): def test_parents_add(library: Library, generate_tag: Callable[..., Tag]): # Given - tag: Tag | None = library.tags[0] - assert tag.id is not None + tag: Tag = library.tags[0] - parent_tag: Tag | None = generate_tag("parent_tag_01") - assert parent_tag - parent_tag = library.add_tag(parent_tag) - assert parent_tag is not None - assert parent_tag.id is not None + parent_tag: Tag = generate_tag("parent_tag_01") + parent_tag = unwrap(library.add_tag(parent_tag)) # When - assert library.add_parent_tag(tag.id, parent_tag.id) + assert library.add_parent_tag(tag.id, unwrap(parent_tag.id)) # Then - assert tag.id is not None - tag = library.get_tag(tag.id) - assert tag is not None + tag = unwrap(library.get_tag(unwrap(tag.id))) assert tag.parent_ids def test_remove_tag(library: Library, generate_tag: Callable[..., Tag]): - tag = library.add_tag(generate_tag("food", id=123)) - - assert tag + tag = unwrap(library.add_tag(generate_tag("food", id=123))) tag_count = len(library.tags) @@ -311,9 +286,8 @@ def test_update_entry_with_multiple_identical_fields(library: Library, entry_ful def test_mirror_entry_fields(library: Library, entry_full: Entry): # new entry - assert library.folder is not None target_entry = Entry( - folder=library.folder, + folder=unwrap(library.folder), path=Path("xxx"), fields=[ TextField( @@ -328,15 +302,13 @@ def test_mirror_entry_fields(library: Library, entry_full: Entry): entry_id = library.add_entries([target_entry])[0] # get new entry from library - new_entry = library.get_entry_full(entry_id) - assert new_entry is not None + new_entry = unwrap(library.get_entry_full(entry_id)) # mirror fields onto new entry library.mirror_entry_fields(new_entry, entry_full) # get new entry from library again - entry = library.get_entry_full(entry_id) - assert entry is not None + entry = unwrap(library.get_entry_full(entry_id)) # make sure fields are there after getting it from the library again assert len(entry.fields) == 2 @@ -347,17 +319,14 @@ def test_mirror_entry_fields(library: Library, entry_full: Entry): def test_merge_entries(library: Library): - assert library.folder is not None + folder = unwrap(library.folder) - tag_0: Tag | None = library.add_tag(Tag(id=1010, name="tag_0")) - assert tag_0 is not None - tag_1: Tag | None = library.add_tag(Tag(id=1011, name="tag_1")) - assert tag_1 is not None - tag_2: Tag | None = library.add_tag(Tag(id=1012, name="tag_2")) - assert tag_2 is not None + tag_0: Tag = unwrap(library.add_tag(Tag(id=1010, name="tag_0"))) + tag_1: Tag = unwrap(library.add_tag(Tag(id=1011, name="tag_1"))) + tag_2: Tag = unwrap(library.add_tag(Tag(id=1012, name="tag_2"))) a = Entry( - folder=library.folder, + folder=folder, path=Path("a"), fields=[ TextField(type_key=_FieldID.AUTHOR.name, value="Author McAuthorson", position=0), @@ -365,7 +334,7 @@ def test_merge_entries(library: Library): ], ) b = Entry( - folder=library.folder, + folder=folder, path=Path("b"), fields=[TextField(type_key=_FieldID.NOTES.name, value="test note", position=1)], ) @@ -374,17 +343,14 @@ def test_merge_entries(library: Library): library.add_tags_to_entries(ids[0], [tag_0.id, tag_2.id]) library.add_tags_to_entries(ids[1], [tag_1.id]) - entry_a: Entry | None = library.get_entry_full(ids[0]) - assert entry_a is not None - entry_b: Entry | None = library.get_entry_full(ids[1]) - assert entry_b is not None + entry_a: Entry = unwrap(library.get_entry_full(ids[0])) + entry_b: Entry = unwrap(library.get_entry_full(ids[1])) assert library.merge_entries(entry_a, entry_b) assert not library.has_path_entry(Path("a")) assert library.has_path_entry(Path("b")) - entry_b_merged = library.get_entry_full(ids[1]) - assert entry_b_merged + entry_b_merged = unwrap(library.get_entry_full(ids[1])) fields = [field.value for field in entry_b_merged.fields] assert "Author McAuthorson" in fields