refactor: unwrap instead of assert not None (#1068)

* refactor: unwrap instead of assert not None

* fix: address review feedback
This commit is contained in:
Jann Stute
2025-08-28 22:27:49 +02:00
committed by GitHub
parent 12e074b71d
commit e551359845
23 changed files with 148 additions and 197 deletions

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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.")

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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:

View File

@@ -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)

View File

@@ -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,

View File

@@ -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}"

View File

@@ -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,
)

View File

@@ -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,
)

View File

@@ -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]

View File

@@ -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")]

View File

@@ -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"]

View File

@@ -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"<h4>{tag.name}</h4>"
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"<h4>{tag_2.name}</h4>"
case 1:
# Check if the container is the custom "foo" category

View File

@@ -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

View File

@@ -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"

View File

@@ -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