mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-08-19 10:32:35 +02:00
refactor(migrations): better library decoupling (#1482)
* refactor: minor improvement of version check * refactor: don't use Library._all_entries in migrations * chore: ruff format * refactor: get_version split * fix: typo * doc: update note on deprecation of preferences table
This commit is contained in:
+2
-1
@@ -176,7 +176,8 @@ Observe the following key aspects of the example below:
|
||||
def _connect_callbacks(self):
|
||||
self.layout().button.clicked.connect(self._button_click_callback)
|
||||
self.layout().color_dropdown.currentIndexChanged.connect(
|
||||
lambda idx: self._color_dropdown_callback(self.color_dropdown.itemData(idx)))
|
||||
lambda idx: self._color_dropdown_callback(self.color_dropdown.itemData(idx))
|
||||
)
|
||||
|
||||
def _button_click_callback(self):
|
||||
print("Button was clicked!")
|
||||
|
||||
@@ -14,7 +14,6 @@ from os import makedirs
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import sqlalchemy
|
||||
import structlog
|
||||
from humanfriendly import format_timespan # pyright: ignore[reportUnknownVariableType]
|
||||
from sqlalchemy import (
|
||||
@@ -669,37 +668,32 @@ class Library:
|
||||
with Session(self.engine) as session:
|
||||
return unwrap(session.scalar(select(func.count(Entry.id))))
|
||||
|
||||
@staticmethod
|
||||
def _all_entries(session: Session, with_joins: bool = False) -> Iterator[Entry]:
|
||||
"""Load entries without joins."""
|
||||
stmt = select(Entry)
|
||||
if with_joins:
|
||||
# load Entry with all joins and all tags
|
||||
stmt = (
|
||||
stmt.outerjoin(Entry.text_fields)
|
||||
.outerjoin(Entry.datetime_fields)
|
||||
.outerjoin(Entry.tags)
|
||||
)
|
||||
stmt = stmt.options(
|
||||
contains_eager(Entry.text_fields),
|
||||
contains_eager(Entry.datetime_fields),
|
||||
contains_eager(Entry.tags),
|
||||
)
|
||||
|
||||
stmt = stmt.distinct()
|
||||
|
||||
entries = session.execute(stmt).scalars()
|
||||
if with_joins:
|
||||
entries = entries.unique()
|
||||
|
||||
for entry in entries:
|
||||
yield entry
|
||||
session.expunge(entry)
|
||||
|
||||
def all_entries(self, with_joins: bool = False) -> Iterator[Entry]:
|
||||
"""Load entries without joins."""
|
||||
with Session(self.engine) as session:
|
||||
yield from Library._all_entries(session, with_joins)
|
||||
stmt = select(Entry)
|
||||
if with_joins:
|
||||
# load Entry with all joins and all tags
|
||||
stmt = (
|
||||
stmt.outerjoin(Entry.text_fields)
|
||||
.outerjoin(Entry.datetime_fields)
|
||||
.outerjoin(Entry.tags)
|
||||
)
|
||||
stmt = stmt.options(
|
||||
contains_eager(Entry.text_fields),
|
||||
contains_eager(Entry.datetime_fields),
|
||||
contains_eager(Entry.tags),
|
||||
)
|
||||
|
||||
stmt = stmt.distinct()
|
||||
|
||||
entries = session.execute(stmt).scalars()
|
||||
if with_joins:
|
||||
entries = entries.unique()
|
||||
|
||||
for entry in entries:
|
||||
yield entry
|
||||
session.expunge(entry)
|
||||
|
||||
@property
|
||||
def tags(self) -> list[Tag]:
|
||||
@@ -1777,30 +1771,12 @@ class Library:
|
||||
Args:
|
||||
key(str): The key for the name of the version type to set.
|
||||
"""
|
||||
return Library._get_version(self.engine, key)
|
||||
|
||||
@staticmethod
|
||||
def _get_version(engine, key: str) -> int:
|
||||
with Session(engine) as session:
|
||||
engine = sqlalchemy.inspect(engine)
|
||||
try:
|
||||
# "Version" table added in DB_VERSION 101
|
||||
if engine and engine.has_table("versions"):
|
||||
version = session.scalar(select(Version).where(Version.key == key))
|
||||
assert version
|
||||
return version.value
|
||||
# NOTE: The "Preferences" table has been depreciated as of TagStudio 9.5.4
|
||||
# and is set to be removed in a future release.
|
||||
else:
|
||||
return int(
|
||||
unwrap(
|
||||
session.scalar(
|
||||
text("SELECT value FROM preferences WHERE key == 'DB_VERSION'")
|
||||
)
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
with Session(self.engine) as session:
|
||||
version = session.scalar(select(Version).where(Version.key == key))
|
||||
if version is None:
|
||||
logger.info(f"[Library] Couldn't get version of type '{key}'")
|
||||
return 0
|
||||
return version.value
|
||||
|
||||
def mirror_entry_fields(self, entries: list[Entry]) -> None:
|
||||
"""Mirror fields among multiple Entry items."""
|
||||
|
||||
@@ -6,6 +6,7 @@ from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import override
|
||||
|
||||
import sqlalchemy
|
||||
import structlog
|
||||
import ujson
|
||||
from sqlalchemy import Engine, and_, delete, select, text, update
|
||||
@@ -21,7 +22,7 @@ from tagstudio.core.library.alchemy.constants import (
|
||||
)
|
||||
from tagstudio.core.library.alchemy.fields import LEGACY_FIELD_MAP, DatetimeField, TextField
|
||||
from tagstudio.core.library.alchemy.joins import TagParent
|
||||
from tagstudio.core.library.alchemy.models import Tag, TagColorGroup, Version
|
||||
from tagstudio.core.library.alchemy.models import Entry, Tag, TagColorGroup, Version
|
||||
from tagstudio.core.library.ignore import migrate_ext_list
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.qt.translations import Translations
|
||||
@@ -46,15 +47,12 @@ class DBMigration:
|
||||
|
||||
class DBMigrations:
|
||||
def __init__(self, library_dir: Path, engine: Engine) -> None:
|
||||
# TODO: Remove local import and don't make calls to private methods.
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
|
||||
self.library_dir = library_dir
|
||||
self.engine = engine
|
||||
|
||||
# Don't check DB version when creating new library
|
||||
self.loaded_db_version = Library._get_version(engine, DB_VERSION_CURRENT_KEY)
|
||||
self.initial_db_version = Library._get_version(engine, DB_VERSION_INITIAL_KEY)
|
||||
self.loaded_db_version = self._get_version(DB_VERSION_CURRENT_KEY)
|
||||
self.initial_db_version = self._get_version(DB_VERSION_INITIAL_KEY)
|
||||
|
||||
# ======================== Library Database Version Checking =======================
|
||||
# DB_VERSION 6 is the first supported SQLite DB version.
|
||||
@@ -84,6 +82,8 @@ class DBMigrations:
|
||||
return self.loaded_db_version < DB_VERSION
|
||||
|
||||
def run(self):
|
||||
if not self.required:
|
||||
return
|
||||
|
||||
# migrate DB step by step from one version to the next
|
||||
# (migration_method, db_version, initial_db_version)
|
||||
@@ -103,9 +103,6 @@ class DBMigrations:
|
||||
MigrationTo400, # changes: add category_exclusions
|
||||
]
|
||||
with Session(self.engine) as session:
|
||||
if self.loaded_db_version > DB_VERSION:
|
||||
return
|
||||
|
||||
for migration in migrations:
|
||||
if self.loaded_db_version < migration.version and (
|
||||
migration.initial_version is None
|
||||
@@ -138,6 +135,27 @@ class DBMigrations:
|
||||
"Ran all migrations, but the DB is still not on the newest version"
|
||||
)
|
||||
|
||||
def _get_version(self, key: str) -> int:
|
||||
with Session(self.engine) as session:
|
||||
inspector = sqlalchemy.inspect(self.engine)
|
||||
try:
|
||||
# "Version" table added in DB_VERSION 101
|
||||
if inspector and inspector.has_table("versions"):
|
||||
version = session.scalar(select(Version).where(Version.key == key))
|
||||
assert version
|
||||
return version.value
|
||||
# "Preferences" table deprecated in TagStudio 9.5.4
|
||||
else:
|
||||
return int(
|
||||
unwrap(
|
||||
session.scalar(
|
||||
text("SELECT value FROM preferences WHERE key == 'DB_VERSION'")
|
||||
)
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
def _set_version(self, session: Session, key: str, value: int) -> None:
|
||||
"""Set a version value to the DB.
|
||||
|
||||
@@ -238,11 +256,10 @@ class MigrationTo9(DBMigration):
|
||||
session.flush()
|
||||
logger.info(fmt_log("Added filename column to entries table"))
|
||||
|
||||
# TODO: Remove local import and don't make calls to private methods.
|
||||
# Populate the new filename column.
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
|
||||
for entry in Library._all_entries(session):
|
||||
# TODO: this could still break in the future through changes to the definition of Entry
|
||||
entries = session.execute(select(Entry).distinct()).scalars()
|
||||
for entry in entries:
|
||||
entry.filename = entry.path.name
|
||||
session.merge(entry)
|
||||
session.flush()
|
||||
|
||||
Reference in New Issue
Block a user