Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/style.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!")
Expand Down
78 changes: 27 additions & 51 deletions src/tagstudio/core/library/alchemy/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from pathlib import Path
from typing import TYPE_CHECKING

import sqlalchemy
import structlog
from humanfriendly import format_timespan # pyright: ignore[reportUnknownVariableType]
from sqlalchemy import (
Expand Down Expand Up @@ -669,37 +668,32 @@ def entries_count(self) -> int:
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]:
def all_entries(self, 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),
)
with Session(self.engine) as session:
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()
stmt = stmt.distinct()

entries = session.execute(stmt).scalars()
if with_joins:
entries = entries.unique()
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)
for entry in entries:
yield entry
session.expunge(entry)

@property
def tags(self) -> list[Tag]:
Expand Down Expand Up @@ -1751,30 +1745,12 @@ def get_version(self, key: str) -> int:
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."""
Expand Down
44 changes: 31 additions & 13 deletions src/tagstudio/core/library/alchemy/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pathlib import Path
from typing import override

import sqlalchemy
import structlog
import ujson
from sqlalchemy import Engine, and_, delete, select, text, update
Expand All @@ -21,7 +22,7 @@
)
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
Expand All @@ -46,15 +47,12 @@ def run(cls, session: Session, library_dir: Path, fmt_log: LoggingMethod) -> Non

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.
Expand Down Expand Up @@ -84,6 +82,8 @@ def required(self) -> bool:
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)
Expand All @@ -102,9 +102,6 @@ def run(self):
MigrationTo300, # changes: deletes folders
]
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
Expand Down Expand Up @@ -137,6 +134,28 @@ def run(self):
"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
# NOTE: The "Preferences" table has been depreciated as of TagStudio 9.5.4
# and is set to be removed in a future release.
Comment thread
Computerdores marked this conversation as resolved.
Outdated
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.

Expand Down Expand Up @@ -237,11 +256,10 @@ def run(cls, session: Session, library_dir: Path, fmt_log: LoggingMethod):
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()
Expand Down
Loading