fix: combine and check (most) built-in tag data from JSON

Known issue: Tag colors from built-in JSON tags are not updated. This can be seen in the failing test.
This commit is contained in:
Travis Abendshien
2025-01-11 03:25:33 -08:00
parent 5e1e4c57a7
commit 114bc0437e
4 changed files with 64 additions and 47 deletions

View File

@@ -42,6 +42,8 @@ from src.core.library.json.library import Library as JsonLibrary # type: ignore
from ...constants import (
BACKUP_FOLDER_NAME,
LEGACY_TAG_FIELD_IDS,
RESERVED_TAG_END,
RESERVED_TAG_START,
TAG_ARCHIVED,
TAG_FAVORITE,
TAG_META,
@@ -105,7 +107,7 @@ def get_default_tags() -> tuple[Tag, ...]:
# The difference in the number of default JSON tags vs default tags in the current version.
DEFAULT_TAG_DIFF: int = len(get_default_tags()) - 2
DEFAULT_TAG_DIFF: int = len(get_default_tags()) - len([TAG_ARCHIVED, TAG_FAVORITE])
@dataclass(frozen=True)
@@ -174,25 +176,30 @@ class Library:
# Tags
for tag in json_lib.tags:
if tag.id == TAG_ARCHIVED or tag.id == TAG_FAVORITE:
# Update built-in
pass
new_tag = Tag(
id=tag.id,
name=tag.name,
shorthand=tag.shorthand,
color=TagColor.get_color_from_str(tag.color),
)
if tag.id in range(RESERVED_TAG_START, RESERVED_TAG_END + 1):
self.update_tag(new_tag) # NOTE: This just calls add_tag??
else:
self.add_tag(
Tag(
id=tag.id,
name=tag.name,
shorthand=tag.shorthand,
color=TagColor.get_color_from_str(tag.color),
)
)
self.add_tag(new_tag)
# Tag Aliases
for tag in json_lib.tags:
for alias in tag.aliases:
if not alias:
break
self.add_alias(name=alias, tag_id=tag.id)
# Only add new (user-created) aliases to the default tags.
# This prevents pre-existing built-in aliases from being added as duplicates.
if tag.id in range(RESERVED_TAG_START, RESERVED_TAG_END + 1):
for dt in get_default_tags():
if dt.id == tag.id and alias not in dt.alias_strings:
self.add_alias(name=alias, tag_id=tag.id)
else:
self.add_alias(name=alias, tag_id=tag.id)
# Parent Tags (Previously known as "Subtags" in JSON)
for tag in json_lib.tags:
@@ -279,13 +286,14 @@ class Library:
with Session(self.engine) as session:
make_tables(self.engine)
tags = get_default_tags()
try:
session.add_all(tags)
session.commit()
except IntegrityError:
# default tags may exist already
session.rollback()
# Add default tags to new libraries only.
if is_new:
tags = get_default_tags()
try:
session.add_all(tags)
session.commit()
except IntegrityError:
session.rollback()
# dont check db version when creating new library
if not is_new:

View File

@@ -58,6 +58,7 @@ class BuildTagPanel(PanelWidget):
def __init__(self, library: Library, tag: Tag | None = None):
super().__init__()
self.lib = library
self.tag: Tag # NOTE: This gets set at the end of the init.
self.setMinimumSize(300, 400)
self.root_layout = QVBoxLayout(self)

View File

@@ -121,7 +121,7 @@ class TagDatabasePanel(PanelWidget):
row.setSpacing(3)
if tag.id in range(RESERVED_TAG_START, RESERVED_TAG_END):
tag_widget = TagWidget(tag, has_edit=False, has_remove=False)
tag_widget = TagWidget(tag, has_edit=True, has_remove=False)
else:
tag_widget = TagWidget(tag, has_edit=True, has_remove=True)
@@ -151,9 +151,6 @@ class TagDatabasePanel(PanelWidget):
self.update_tags()
def edit_tag(self, tag: Tag):
if tag.id in range(RESERVED_TAG_START, RESERVED_TAG_END):
return
build_tag_panel = BuildTagPanel(self.lib, tag=tag)
self.edit_modal = PanelModal(

View File

@@ -19,14 +19,15 @@ from PySide6.QtWidgets import (
)
from sqlalchemy import select
from sqlalchemy.orm import Session
from src.core.constants import LEGACY_TAG_FIELD_IDS, RESERVED_TAG_END, TAG_META, TS_FOLDER_NAME
from src.core.constants import LEGACY_TAG_FIELD_IDS, TS_FOLDER_NAME
from src.core.enums import LibraryPrefs
from src.core.library.alchemy.enums import TagColor
from src.core.library.alchemy.joins import TagParent
from src.core.library.alchemy.library import DEFAULT_TAG_DIFF
from src.core.library.alchemy.library import TAG_ARCHIVED, TAG_FAVORITE, TAG_META
from src.core.library.alchemy.library import Library as SqliteLibrary
from src.core.library.alchemy.models import Entry, TagAlias
from src.core.library.json.library import Library as JsonLibrary # type: ignore
from src.core.library.json.library import Tag as JsonTag # type: ignore
from src.qt.helpers.custom_runnable import CustomRunnable
from src.qt.helpers.function_iterator import FunctionIterator
from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper
@@ -310,6 +311,7 @@ class JsonMigrationModal(QObject):
# Open the JSON Library
self.json_lib = JsonLibrary()
self.json_lib.open_library(self.path)
self.update_json_builtins()
# Update JSON UI
self.update_json_entry_count(len(self.json_lib.entries))
@@ -320,6 +322,28 @@ class JsonMigrationModal(QObject):
self.migration_progress(skip_ui=skip_ui)
self.is_migration_initialized = True
def update_json_builtins(self):
"""Updates the built-in JSON values to include any future changes or additions.
Used to preserve user-modified built-in tags and to
match values between JSON and SQL during parity checking.
"""
# v9.5.0: Add "Meta Tags" tag and parent that to "Archived" and "Favorite".
meta_tags: JsonTag = JsonTag(TAG_META, "Meta Tags", "", ["Meta", "Meta Tag"], [], "")
logger.warning(self.json_lib.tags)
# self.json_lib.add_tag_to_library(meta_tags)
self.json_lib.tags.append(meta_tags)
self.json_lib._map_tag_id_to_index(meta_tags, len(self.json_lib.tags) - 1)
logger.warning(self.json_lib.tags)
archived_tag: JsonTag = self.json_lib.get_tag(TAG_ARCHIVED)
archived_tag.subtag_ids.append(TAG_META)
self.json_lib.update_tag(archived_tag)
favorite_tag: JsonTag = self.json_lib.get_tag(TAG_FAVORITE)
favorite_tag.subtag_ids.append(TAG_META)
self.json_lib.update_tag(favorite_tag)
def migration_progress(self, skip_ui: bool = False):
"""Initialize the progress bar and iterator for the library migration."""
pb = QProgressDialog(
@@ -415,7 +439,7 @@ class JsonMigrationModal(QObject):
)
self.update_sql_value(
self.tags_row,
(len(self.sql_lib.tags) - DEFAULT_TAG_DIFF),
len(self.sql_lib.tags),
self.old_tag_count,
)
self.update_sql_value(
@@ -595,9 +619,6 @@ class JsonMigrationModal(QObject):
with Session(self.sql_lib.engine) as session:
for tag in self.sql_lib.tags:
# NOTE: Don't check subtag parity for built-in tags.
if tag.id in range(0, RESERVED_TAG_END + 1):
break
tag_id = tag.id # Tag IDs start at 0
sql_parent_tags = set(
session.scalars(select(TagParent.child_id).where(TagParent.parent_id == tag.id))
@@ -639,10 +660,6 @@ class JsonMigrationModal(QObject):
with Session(self.sql_lib.engine) as session:
for tag in self.sql_lib.tags:
# NOTE: Do not check alias parity for built-in tags added
# after "Favorite" and "Archived".
if tag.id in range(TAG_META, RESERVED_TAG_END + 1):
break
tag_id = tag.id # Tag IDs start at 0
sql_aliases = set(
session.scalars(select(TagAlias.name).where(TagAlias.tag_id == tag.id))
@@ -675,13 +692,14 @@ class JsonMigrationModal(QObject):
sql_shorthand: str = None
json_shorthand: str = None
def sanitize(value):
"""Return value or convert a "not" value into None."""
return value if value else None
for tag in self.sql_lib.tags:
# NOTE: Don't check shorthand parity for built-in tags.
if tag.id in range(0, RESERVED_TAG_END + 1):
break
tag_id = tag.id # Tag IDs start at 0
sql_shorthand = tag.shorthand
json_shorthand = 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]",
@@ -690,11 +708,7 @@ class JsonMigrationModal(QObject):
sql_shorthand=sql_shorthand,
)
if not (
sql_shorthand is not None
and json_shorthand is not None
and (sql_shorthand == json_shorthand)
):
if sql_shorthand != json_shorthand:
self.discrepancies.append(
f"[Shorthand Parity][Tag ID: {tag_id}]:"
f"\nOLD (JSON):{json_shorthand}\nNEW (SQL):{sql_shorthand}"
@@ -711,9 +725,6 @@ class JsonMigrationModal(QObject):
json_color: str = None
for tag in self.sql_lib.tags:
# NOTE: Don't check tag color parity for built-in tags.
if tag.id in range(0, RESERVED_TAG_END + 1):
break
tag_id = tag.id # Tag IDs start at 0
sql_color = tag.color.name
json_color = (