refactor: media types rework, dynamically load preview renderers (#1498)

* feat!: establish new MediaType system

* further additions and changes

* begin making renderers classes, improve text thumbnails

* continue renderer work

* convert most remaining thumbnail renderers to classes

* feat: dynamically load preview renderers

* refactor: use individual ext registration, continued progress

* refactor: finish converting old types to new registration system

* chore: alphabetize lists

* fix: only use search context for searches

* refactor: remove legacy filetype equivalent usages

* feat: add aliases for type groups, used in search

* refactor: use new MediaTypes system in archive renderers

* fix: add missing types

* refactor: remove last uses of legacy media type system

* refactor: move media type search context init to new file

* fix: fix import formatting, add missing aliases

* chore: remove leftover logging

* chore: small tweaks and comments

* refactor: wrap filetype init in function, call in driver

* docs: add and update docstrings

* fix: use all intended fallback icons for each media type

* fix: fix misc issues

* tests: add test cases for media types

* refactor: rename register_all to register_types

* refactor: wrap preview renderer type registrations in methods
This commit is contained in:
Travis Abendshien
2026-09-07 16:06:56 -07:00
committed by GitHub
parent 3e9296c1db
commit 658b7840c4
51 changed files with 2837 additions and 1347 deletions
+19 -1
View File
@@ -1,6 +1,9 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
# pyright: reportPrivateUsage=false
# pyright: reportUnusedFunction=false
import sys
from collections.abc import Callable, Generator
@@ -13,6 +16,7 @@ from PySide6.QtWidgets import QScrollArea
from pytestqt.qtbot import QtBot
from tagstudio.core.library.alchemy.fields import TextField
from tagstudio.core.media_types import MediaTypes
CWD = Path(__file__).parent
# this needs to be above `src` imports
@@ -148,11 +152,25 @@ def entry_full(library: Library):
@pytest.fixture(autouse=True)
def _init_qtbot(qtbot: QtBot): # pyright: ignore[reportUnusedFunction]
def _init_qtbot(qtbot: QtBot):
"""Ensures that a QtBot is initialized for all subsequent tests, regardless of order."""
return qtbot
@pytest.fixture(autouse=True)
def _reset_media_types():
"""Snapshot the MediaTypes state before each test, then restore it after."""
pre_snapshop = MediaTypes._snapshot()
yield
post_snapshop = MediaTypes._snapshot()
if pre_snapshop != post_snapshop:
MediaTypes._restore(pre_snapshop)
assert pre_snapshop == MediaTypes._snapshot(), "The MediaTypes state was not restored!"
@pytest.fixture
def qt_driver(library: Library, library_dir: Path):
class Args:
+85
View File
@@ -0,0 +1,85 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: MIT
from pytestqt.exceptions import pytest
from tagstudio.core.media_types import MediaTypes
def test_register_and_contains():
MediaTypes.register("zzztest.basic", ".zzzfoo", "SEARCH")
assert MediaTypes.contains("zzztest.basic", ".zzzfoo", "SEARCH")
assert not MediaTypes.contains("zzztest.basic", ".zzzfoo", "RENDER")
def test_additive_register():
MediaTypes.register("zzztest.additive", ".zzzfoo", "SEARCH")
MediaTypes.register("zzztest.additive", ".zzzbar", "SEARCH")
assert MediaTypes.contains("zzztest.additive", ".zzzfoo", "SEARCH")
assert MediaTypes.contains("zzztest.additive", ".zzzbar", "SEARCH")
def test_contains_missing_group_raises_error():
with pytest.raises(AttributeError, match=r"is not registered"):
MediaTypes.contains("zzztest.does_not_exist", ".zzzfoo", "SEARCH")
def test_dot_notation_chains_to_parents():
MediaTypes.register("zzztest.chain.parent.child", ".zzzchild", "SEARCH")
assert MediaTypes.contains("zzztest.chain.parent.child", ".zzzchild", "SEARCH")
assert MediaTypes.contains("zzztest.chain.parent", ".zzzchild", "SEARCH")
assert MediaTypes.contains("zzztest.chain", ".zzzchild", "SEARCH")
def test_explicit_chain_group():
MediaTypes.chain_group("zzztest.composite", ["zzztest.composite_child"])
MediaTypes.register("zzztest.composite_child", ".zzzcomposite", "SEARCH")
assert MediaTypes.contains("zzztest.composite", ".zzzcomposite", "SEARCH")
def test_equivalent_extensions():
MediaTypes.register("zzztest.equiv", [".zzzone", ".zzztwo"], "SEARCH")
assert MediaTypes.get_equivalent_exts(".zzzone") == {".zzzone", ".zzztwo"}
assert MediaTypes.get_equivalent_exts(".zzztwo") == {".zzzone", ".zzztwo"}
assert MediaTypes.contains("zzztest.equiv", ".zzzone", "SEARCH")
assert MediaTypes.contains("zzztest.equiv", ".zzztwo", "SEARCH")
def test_get_equivalent_exts_defaults_to_itself():
assert MediaTypes.get_equivalent_exts(".zzzunregistered") == {".zzzunregistered"}
def test_find():
MediaTypes.register("zzztest.find_a", ".zzzfind", "SEARCH")
MediaTypes.register("zzztest.find_b", ".zzzfind", "RENDER")
search_keys = {group.key for group in MediaTypes.find(".zzzfind", "SEARCH")}
render_keys = {group.key for group in MediaTypes.find(".zzzfind", "RENDER")}
assert "zzztest.find_a" in search_keys
assert "zzztest.find_a" not in render_keys
assert "zzztest.find_b" in render_keys
assert "zzztest.find_b" not in search_keys
def test_add_name_aliases_and_lookup():
MediaTypes.register("zzztest.alias_target", ".zzzalias", "SEARCH")
MediaTypes.add_name_aliases("zzztest.alias_target", ["ZZZ Test Group", "zzztest"])
assert MediaTypes.get_group_key_from_name("ZZZ Test Group") == "zzztest.alias_target"
assert MediaTypes.get_group_key_from_name("zzz test group", case_sensitive=False) == (
"zzztest.alias_target"
)
assert (
MediaTypes.get_group_key_from_name(
"zzztestGroup", case_sensitive=False, ignore_whitespace=True
)
== "zzztest.alias_target"
)
assert MediaTypes.get_group_key_from_name("Not A Real Alias") is None
+1 -1
View File
@@ -9,7 +9,7 @@ from unittest.mock import Mock
import pytest
from PySide6.QtCore import Qt
from tagstudio.qt.app_settings import Theme
from tagstudio.core.enums import Theme
@pytest.mark.parametrize(