mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-07-12 17:01:59 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab3b1f2f17 | |||
| 5e1d4a1190 | |||
| aa2d9d4815 | |||
| 69dce93af5 |
@@ -63,7 +63,8 @@ jobs:
|
||||
- .github/workflows/checks_python.yml
|
||||
- pyproject.toml
|
||||
- uv.lock
|
||||
- '**.pyi?'
|
||||
- '**.py'
|
||||
- '**.pyi'
|
||||
pyright:
|
||||
- .github/actions/setup-python/action.yml
|
||||
pytest:
|
||||
|
||||
@@ -9,7 +9,7 @@ JSON_FILENAME: str = "ts_library.json"
|
||||
|
||||
DB_VERSION_CURRENT_KEY: str = "CURRENT"
|
||||
DB_VERSION_INITIAL_KEY: str = "INITIAL"
|
||||
DB_VERSION: int = 202
|
||||
DB_VERSION: int = 203
|
||||
|
||||
TAG_CHILDREN_QUERY = text("""
|
||||
WITH RECURSIVE ChildTags AS (
|
||||
|
||||
@@ -18,7 +18,6 @@ from datetime import UTC, datetime
|
||||
from os import makedirs
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from uuid import uuid4
|
||||
|
||||
import sqlalchemy
|
||||
import structlog
|
||||
@@ -95,7 +94,6 @@ from tagstudio.core.library.alchemy.fields import (
|
||||
from tagstudio.core.library.alchemy.joins import TagEntry, TagParent
|
||||
from tagstudio.core.library.alchemy.models import (
|
||||
Entry,
|
||||
Folder,
|
||||
Namespace,
|
||||
Tag,
|
||||
TagAlias,
|
||||
@@ -234,7 +232,6 @@ class Library:
|
||||
|
||||
library_dir: Path | None = None
|
||||
engine: Engine | None = None
|
||||
folder: Folder | None = None
|
||||
included_files: set[Path] = set()
|
||||
|
||||
def __init__(self) -> None:
|
||||
@@ -259,7 +256,6 @@ class Library:
|
||||
"""Migrate JSON library data to the SQLite database."""
|
||||
logger.info("Starting Library Conversion...")
|
||||
start_time = time.time()
|
||||
folder: Folder = Folder(path=self.library_dir, uuid=str(uuid4()))
|
||||
|
||||
# Tags
|
||||
for tag in json_lib.tags:
|
||||
@@ -312,7 +308,6 @@ class Library:
|
||||
[
|
||||
Entry(
|
||||
path=entry.path / entry.filename,
|
||||
folder=folder,
|
||||
fields=[],
|
||||
id=entry.id + 1, # NOTE: JSON IDs start at 0 instead of 1
|
||||
date_added=datetime.now(),
|
||||
@@ -479,16 +474,6 @@ class Library:
|
||||
session.add(Version(key=DB_VERSION_CURRENT_KEY, value=DB_VERSION))
|
||||
session.flush()
|
||||
|
||||
# add folder for current path
|
||||
folder = Folder(
|
||||
path=library_dir,
|
||||
uuid=str(uuid4()),
|
||||
)
|
||||
session.add(folder)
|
||||
session.expunge(folder)
|
||||
session.flush()
|
||||
self.folder = folder
|
||||
|
||||
# Generate default .ts_ignore file
|
||||
try:
|
||||
ts_ignore_template = (
|
||||
@@ -584,6 +569,7 @@ class Library:
|
||||
(self.__apply_db200_migration, 200, None), # changes: field tables
|
||||
(self.__apply_db201_migration, 201, 200), # changes: field tables
|
||||
(self.__apply_db202_migration, 202, None), # changes: tag_parents
|
||||
(self.__apply_db203_migration, 203, None), # changes: deletes folders
|
||||
]
|
||||
for migration, v, iv in migrations:
|
||||
if loaded_db_version < v and (iv is None or initial_db_version < iv):
|
||||
@@ -601,22 +587,6 @@ class Library:
|
||||
)
|
||||
logger.info(f"[Library] Library migrated to DB version {DB_VERSION}")
|
||||
|
||||
with Session(self.engine) as session:
|
||||
# TODO: the folder logic has no use and was never finished, remove it
|
||||
# check if folder matching current path exists already
|
||||
# NOTE: this has been causing new Folders to be created when the library is moved, since
|
||||
# its introduction
|
||||
self.folder = session.scalar(select(Folder).where(Folder.path == library_dir))
|
||||
if not self.folder:
|
||||
folder = Folder(
|
||||
path=library_dir,
|
||||
uuid=str(uuid4()),
|
||||
)
|
||||
session.add(folder)
|
||||
session.expunge(folder)
|
||||
session.commit()
|
||||
self.folder = folder
|
||||
|
||||
# everything is fine, set the library path
|
||||
self.library_dir = library_dir
|
||||
return LibraryStatus(success=True, library_path=library_dir)
|
||||
@@ -906,6 +876,42 @@ class Library:
|
||||
session.flush()
|
||||
logger.info("[Library][Migration][202] Verified TagParent table data")
|
||||
|
||||
def __apply_db203_migration(self, session: Session, library_dir: Path):
|
||||
## remove folder_id column from entries table
|
||||
# create new table in the desired scheme (without folder_id column)
|
||||
session.execute(
|
||||
text("""
|
||||
CREATE TABLE entries_new (
|
||||
id INTEGER NOT NULL,
|
||||
path VARCHAR NOT NULL,
|
||||
suffix VARCHAR NOT NULL,
|
||||
date_created DATETIME,
|
||||
date_modified DATETIME,
|
||||
date_added DATETIME, filename TEXT NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE (path)
|
||||
)
|
||||
""")
|
||||
)
|
||||
session.flush()
|
||||
# transfer data to new table
|
||||
session.execute(
|
||||
text("""
|
||||
INSERT INTO entries_new (id, path, suffix, date_created, date_modified, date_added)
|
||||
SELECT id, path, suffix, date_created, date_modified, date_added
|
||||
FROM entries
|
||||
""")
|
||||
)
|
||||
# delete old table
|
||||
session.execute(text("DROP TABLE entries"))
|
||||
# rename new table to old table
|
||||
session.execute(text("ALTER TABLE entries_new RENAME TO entries"))
|
||||
session.flush()
|
||||
|
||||
## drop table "folders"
|
||||
session.execute(text("DROP TABLE folders"))
|
||||
session.flush()
|
||||
|
||||
@property
|
||||
def field_templates(self) -> Sequence[BaseFieldTemplate]:
|
||||
with Session(self.engine) as session:
|
||||
|
||||
@@ -182,23 +182,11 @@ class Tag(Base):
|
||||
return self.name >= other.name
|
||||
|
||||
|
||||
class Folder(Base):
|
||||
__tablename__ = "folders"
|
||||
|
||||
# TODO - implement this
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
path: Mapped[Path] = mapped_column(PathType, unique=True)
|
||||
uuid: Mapped[str] = mapped_column(unique=True)
|
||||
|
||||
|
||||
class Entry(Base):
|
||||
__tablename__ = "entries"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
|
||||
folder_id: Mapped[int] = mapped_column(ForeignKey("folders.id"))
|
||||
folder: Mapped[Folder] = relationship("Folder")
|
||||
|
||||
path: Mapped[Path] = mapped_column(PathType, unique=True)
|
||||
filename: Mapped[str] = mapped_column()
|
||||
suffix: Mapped[str] = mapped_column()
|
||||
@@ -235,7 +223,6 @@ class Entry(Base):
|
||||
def __init__(
|
||||
self,
|
||||
path: Path,
|
||||
folder: Folder,
|
||||
fields: list[BaseField],
|
||||
id: int | None = None,
|
||||
date_created: dt | None = None,
|
||||
@@ -244,7 +231,6 @@ class Entry(Base):
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.path = path
|
||||
self.folder = folder
|
||||
self.id = id # pyright: ignore[reportAttributeAccessIssue]
|
||||
self.filename = path.name
|
||||
self.suffix = path.suffix.lstrip(".").lower()
|
||||
|
||||
@@ -16,7 +16,6 @@ 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, ignore_to_glob
|
||||
from tagstudio.core.utils.silent_subprocess import silent_run # pyright: ignore
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
@@ -41,7 +40,6 @@ class RefreshTracker:
|
||||
entries = [
|
||||
Entry(
|
||||
path=entry_path,
|
||||
folder=unwrap(self.library.folder),
|
||||
fields=[],
|
||||
date_added=dt.now(),
|
||||
)
|
||||
|
||||
@@ -20,7 +20,6 @@ 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.thumb_grid_layout import ThumbGridLayout
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
@@ -36,22 +35,18 @@ def file_mediatypes_library():
|
||||
|
||||
status = lib.open_library(Path(""), in_memory=True)
|
||||
assert status.success
|
||||
folder = unwrap(lib.folder)
|
||||
|
||||
entry1 = Entry(
|
||||
folder=folder,
|
||||
path=Path("foo.png"),
|
||||
fields=[TextField(name="Title", value="I'm a Test Title")],
|
||||
)
|
||||
|
||||
entry2 = Entry(
|
||||
folder=folder,
|
||||
path=Path("bar.png"),
|
||||
fields=[TextField(name="Title", value="I'm a Test Title")],
|
||||
)
|
||||
|
||||
entry3 = Entry(
|
||||
folder=folder,
|
||||
path=Path("baz.apng"),
|
||||
fields=[TextField(name="Title", value="I'm a Test Title")],
|
||||
)
|
||||
@@ -87,7 +82,6 @@ def library(request, library_dir: Path): # pyright: ignore
|
||||
lib = Library()
|
||||
status = lib.open_library(library_path, in_memory=True)
|
||||
assert status.success
|
||||
folder = unwrap(lib.folder)
|
||||
|
||||
tag = Tag(
|
||||
name="foo",
|
||||
@@ -116,7 +110,6 @@ def library(request, library_dir: Path): # pyright: ignore
|
||||
# default item with deterministic name
|
||||
entry = Entry(
|
||||
id=1,
|
||||
folder=folder,
|
||||
path=Path("foo.txt"),
|
||||
fields=[TextField(name="Title", value="I'm a Test Title")],
|
||||
)
|
||||
@@ -124,7 +117,6 @@ def library(request, library_dir: Path): # pyright: ignore
|
||||
|
||||
entry2 = Entry(
|
||||
id=2,
|
||||
folder=folder,
|
||||
path=Path("one/two/bar.md"),
|
||||
fields=[TextField(name="Title", value="I'm a Test Title")],
|
||||
)
|
||||
|
||||
Binary file not shown.
@@ -8,25 +8,21 @@ from tagstudio.core.library.alchemy.fields import BaseField, TextField
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry
|
||||
from tagstudio.core.library.alchemy.registries.dupe_files_registry import DupeFilesRegistry
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
|
||||
|
||||
def test_refresh_dupe_files(library: Library):
|
||||
library.library_dir = Path("/tmp/")
|
||||
folder = unwrap(library.folder)
|
||||
|
||||
fields: list[BaseField] = [TextField(name="Title", value="I'm a Test Title")]
|
||||
|
||||
entry = Entry(
|
||||
folder=folder,
|
||||
path=Path("bar/foo.txt"),
|
||||
fields=fields,
|
||||
)
|
||||
|
||||
entry2 = Entry(
|
||||
folder=folder,
|
||||
path=Path("foo/foo.txt"),
|
||||
fields=fields,
|
||||
)
|
||||
|
||||
+1
-10
@@ -77,7 +77,6 @@ def test_library_add_file(library: Library):
|
||||
"""Check Entry.path handling for insert vs lookup"""
|
||||
entry = Entry(
|
||||
path=Path("bar.txt"),
|
||||
folder=unwrap(library.folder),
|
||||
fields=[TextField(name="Title", value="I'm a Test Title")],
|
||||
)
|
||||
|
||||
@@ -139,8 +138,7 @@ def test_get_entry(library: Library, entry_min: Entry):
|
||||
|
||||
|
||||
def test_entries_count(library: Library):
|
||||
folder = unwrap(library.folder)
|
||||
entries = [Entry(path=Path(f"{x}.txt"), folder=folder, fields=[]) for x in range(10)]
|
||||
entries = [Entry(path=Path(f"{x}.txt"), fields=[]) for x in range(10)]
|
||||
new_ids = library.add_entries(entries)
|
||||
assert len(new_ids) == 10
|
||||
|
||||
@@ -254,7 +252,6 @@ def test_update_entry_with_multiple_identical_text_fields(library: Library, entr
|
||||
def test_mirror_entry_fields(library: Library):
|
||||
# Create and add entries with fields
|
||||
entry_a = Entry(
|
||||
folder=unwrap(library.folder),
|
||||
path=Path("title_and_date.txt"),
|
||||
fields=[
|
||||
TextField(name="Title", value="I'm a Test Title"),
|
||||
@@ -262,7 +259,6 @@ def test_mirror_entry_fields(library: Library):
|
||||
],
|
||||
)
|
||||
entry_b = Entry(
|
||||
folder=unwrap(library.folder),
|
||||
path=Path("notes.txt"),
|
||||
fields=[
|
||||
TextField(name="Notes", value="These are my notes.\nNo peeking!", is_multiline=True),
|
||||
@@ -270,7 +266,6 @@ def test_mirror_entry_fields(library: Library):
|
||||
],
|
||||
)
|
||||
entry_c = Entry(
|
||||
folder=unwrap(library.folder),
|
||||
path=Path("date_published.txt"),
|
||||
fields=[
|
||||
DatetimeField(name="Date Published", value="2000-01-01 12:00:00"),
|
||||
@@ -319,14 +314,11 @@ def test_mirror_entry_fields(library: Library):
|
||||
|
||||
|
||||
def test_merge_entries(library: Library):
|
||||
folder = unwrap(library.folder)
|
||||
|
||||
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")))
|
||||
|
||||
entry_a = Entry(
|
||||
folder=folder,
|
||||
path=Path("a"),
|
||||
fields=[
|
||||
TextField(name="Author", value="Author McAuthorson"),
|
||||
@@ -334,7 +326,6 @@ def test_merge_entries(library: Library):
|
||||
],
|
||||
)
|
||||
entry_b = Entry(
|
||||
folder=folder,
|
||||
path=Path("b"),
|
||||
fields=[TextField(name="Notes", value="test note", is_multiline=True)],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user