diff --git a/README.md b/README.md index 4ec61240..71d1230c 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,12 @@ _Skip these steps if launching from the .sh script on Linux/macOS._ - Linux/macOS: `source .venv/bin/activate` 3. Install the required packages: - `pip install -r requirements.txt` + +- required to run the app: `pip install -r requirements.txt` +- required to develop: `pip install -r requirements-dev.txt` + + +To run all the tests use `python -m pytest tests/` from the `tagstudio` folder. _Learn more about setting up a virtual environment [here](https://docs.python.org/3/tutorial/venv.html)._ diff --git a/requirements-dev.txt b/requirements-dev.txt index 38017040..7b90e16c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,5 @@ ruff==0.4.2 pre-commit==3.7.0 +pytest==8.2.0 Pyinstaller==6.6.0 mypy==1.10.0 diff --git a/tagstudio/src/core/constants.py b/tagstudio/src/core/constants.py new file mode 100644 index 00000000..9e4eea76 --- /dev/null +++ b/tagstudio/src/core/constants.py @@ -0,0 +1,136 @@ +VERSION: str = "9.2.1" # Major.Minor.Patch +VERSION_BRANCH: str = "Pre-Release" # 'Alpha', 'Beta', or '' for Full Release + +# The folder & file names where TagStudio keeps its data relative to a library. +TS_FOLDER_NAME: str = ".TagStudio" +BACKUP_FOLDER_NAME: str = "backups" +COLLAGE_FOLDER_NAME: str = "collages" +LIBRARY_FILENAME: str = "ts_library.json" + +# TODO: Turn this whitelist into a user-configurable blacklist. +IMAGE_TYPES: list[str] = [ + "png", + "jpg", + "jpeg", + "jpg_large", + "jpeg_large", + "jfif", + "gif", + "tif", + "tiff", + "heic", + "heif", + "webp", + "bmp", + "svg", + "avif", + "apng", + "jp2", + "j2k", + "jpg2", +] +VIDEO_TYPES: list[str] = [ + "mp4", + "webm", + "mov", + "hevc", + "mkv", + "avi", + "wmv", + "flv", + "gifv", + "m4p", + "m4v", + "3gp", +] +AUDIO_TYPES: list[str] = [ + "mp3", + "mp4", + "mpeg4", + "m4a", + "aac", + "wav", + "flac", + "alac", + "wma", + "ogg", + "aiff", +] +DOC_TYPES: list[str] = ["txt", "rtf", "md", "doc", "docx", "pdf", "tex", "odt", "pages"] +PLAINTEXT_TYPES: list[str] = [ + "txt", + "md", + "css", + "html", + "xml", + "json", + "js", + "ts", + "ini", + "htm", + "csv", + "php", + "sh", + "bat", +] +SPREADSHEET_TYPES: list[str] = ["csv", "xls", "xlsx", "numbers", "ods"] +PRESENTATION_TYPES: list[str] = ["ppt", "pptx", "key", "odp"] +ARCHIVE_TYPES: list[str] = ["zip", "rar", "tar", "tar.gz", "tgz", "7z"] +PROGRAM_TYPES: list[str] = ["exe", "app"] +SHORTCUT_TYPES: list[str] = ["lnk", "desktop", "url"] + +ALL_FILE_TYPES: list[str] = ( + IMAGE_TYPES + + VIDEO_TYPES + + AUDIO_TYPES + + DOC_TYPES + + SPREADSHEET_TYPES + + PRESENTATION_TYPES + + ARCHIVE_TYPES + + PROGRAM_TYPES + + SHORTCUT_TYPES +) + +BOX_FIELDS = ["tag_box", "text_box"] +TEXT_FIELDS = ["text_line", "text_box"] +DATE_FIELDS = ["datetime"] + +TAG_COLORS = [ + "", + "black", + "dark gray", + "gray", + "light gray", + "white", + "light pink", + "pink", + "red", + "red orange", + "orange", + "yellow orange", + "yellow", + "lime", + "light green", + "mint", + "green", + "teal", + "cyan", + "light blue", + "blue", + "blue violet", + "violet", + "purple", + "lavender", + "berry", + "magenta", + "salmon", + "auburn", + "dark brown", + "brown", + "light brown", + "blonde", + "peach", + "warm gray", + "cool gray", + "olive", +] diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library.py index c74b8d88..5d09eaa5 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library.py @@ -22,9 +22,15 @@ import ujson from src.core.json_typing import JsonCollation, JsonEntry, JsonLibary, JsonTag -from src.core import ts_core from src.core.utils.str import strip_punctuation from src.core.utils.web import strip_web_protocol +from src.core.constants import ( + BACKUP_FOLDER_NAME, + COLLAGE_FOLDER_NAME, + TEXT_FIELDS, + TS_FOLDER_NAME, + VERSION, +) TYPE = ["file", "meta", "alt", "mask"] @@ -444,8 +450,8 @@ def create_library(self, path) -> int: path = os.path.normpath(path).rstrip("\\") # If '.TagStudio' is included in the path, trim the path up to it. - if ts_core.TS_FOLDER_NAME in path: - path = path.split(ts_core.TS_FOLDER_NAME)[0] + if TS_FOLDER_NAME in path: + path = path.split(TS_FOLDER_NAME)[0] try: self.clear_internal_vars() @@ -462,12 +468,12 @@ def create_library(self, path) -> int: def verify_ts_folders(self) -> None: """Verifies/creates folders required by TagStudio.""" - full_ts_path = os.path.normpath(f"{self.library_dir}/{ts_core.TS_FOLDER_NAME}") + full_ts_path = os.path.normpath(f"{self.library_dir}/{TS_FOLDER_NAME}") full_backup_path = os.path.normpath( - f"{self.library_dir}/{ts_core.TS_FOLDER_NAME}/{ts_core.BACKUP_FOLDER_NAME}" + f"{self.library_dir}/{TS_FOLDER_NAME}/{BACKUP_FOLDER_NAME}" ) full_collage_path = os.path.normpath( - f"{self.library_dir}/{ts_core.TS_FOLDER_NAME}/{ts_core.COLLAGE_FOLDER_NAME}" + f"{self.library_dir}/{TS_FOLDER_NAME}/{COLLAGE_FOLDER_NAME}" ) if not os.path.isdir(full_ts_path): @@ -505,17 +511,13 @@ def open_library(self, path: str) -> int: path = os.path.normpath(path).rstrip("\\") # If '.TagStudio' is included in the path, trim the path up to it. - if ts_core.TS_FOLDER_NAME in path: - path = path.split(ts_core.TS_FOLDER_NAME)[0] + if TS_FOLDER_NAME in path: + path = path.split(TS_FOLDER_NAME)[0] - if os.path.exists( - os.path.normpath(f"{path}/{ts_core.TS_FOLDER_NAME}/ts_library.json") - ): + if os.path.exists(os.path.normpath(f"{path}/{TS_FOLDER_NAME}/ts_library.json")): try: with open( - os.path.normpath( - f"{path}/{ts_core.TS_FOLDER_NAME}/ts_library.json" - ), + os.path.normpath(f"{path}/{TS_FOLDER_NAME}/ts_library.json"), "r", encoding="utf-8", ) as file: @@ -724,11 +726,9 @@ def open_library(self, path: str) -> int: # If the Library is loaded, continue other processes. if return_code == 1: if not os.path.exists( - os.path.normpath(f"{self.library_dir}/{ts_core.TS_FOLDER_NAME}") + os.path.normpath(f"{self.library_dir}/{TS_FOLDER_NAME}") ): - os.makedirs( - os.path.normpath(f"{self.library_dir}/{ts_core.TS_FOLDER_NAME}") - ) + os.makedirs(os.path.normpath(f"{self.library_dir}/{TS_FOLDER_NAME}")) self._map_filenames_to_entry_ids() @@ -775,7 +775,7 @@ def to_json(self): """ file_to_save: JsonLibary = { - "ts-version": ts_core.VERSION, + "ts-version": VERSION, "ignored_extensions": [], "tags": [], "collations": [], @@ -813,7 +813,7 @@ def save_library_to_disk(self): self.verify_ts_folders() with open( - os.path.normpath(f"{self.library_dir}/{ts_core.TS_FOLDER_NAME}/{filename}"), + os.path.normpath(f"{self.library_dir}/{TS_FOLDER_NAME}/{filename}"), "w", encoding="utf-8", ) as outfile: @@ -842,7 +842,7 @@ def save_library_backup_to_disk(self) -> str: self.verify_ts_folders() with open( os.path.normpath( - f"{self.library_dir}/{ts_core.TS_FOLDER_NAME}/{ts_core.BACKUP_FOLDER_NAME}/{filename}" + f"{self.library_dir}/{TS_FOLDER_NAME}/{BACKUP_FOLDER_NAME}/{filename}" ), "w", encoding="utf-8", @@ -899,13 +899,13 @@ def refresh_dir(self) -> Generator: # Scans the directory for files, keeping track of: # - Total file count # - Files without library entries - # for type in ts_core.TYPES: + # for type in TYPES: start_time = time.time() for f in glob.glob(self.library_dir + "/**/*", recursive=True): # p = Path(os.path.normpath(f)) if ( "$RECYCLE.BIN" not in f - and ts_core.TS_FOLDER_NAME not in f + and TS_FOLDER_NAME not in f and "tagstudio_thumbs" not in f and not os.path.isdir(f) ): @@ -2121,7 +2121,7 @@ def add_field_to_entry(self, entry_id: int, field_id: int) -> None: # entry = self.entries[entry_index] entry = self.get_entry(entry_id) field_type = self.get_field_obj(field_id)["type"] - if field_type in ts_core.TEXT_FIELDS: + if field_type in TEXT_FIELDS: entry.fields.append({int(field_id): ""}) elif field_type == "tag_box": entry.fields.append({int(field_id): []}) diff --git a/tagstudio/src/core/ts_core.py b/tagstudio/src/core/ts_core.py index 5e4f2725..8958b01f 100644 --- a/tagstudio/src/core/ts_core.py +++ b/tagstudio/src/core/ts_core.py @@ -8,143 +8,7 @@ import os from src.core.library import Entry, Library - -VERSION: str = "9.2.1" # Major.Minor.Patch -VERSION_BRANCH: str = "Pre-Release" # 'Alpha', 'Beta', or '' for Full Release - -# The folder & file names where TagStudio keeps its data relative to a library. -TS_FOLDER_NAME: str = ".TagStudio" -BACKUP_FOLDER_NAME: str = "backups" -COLLAGE_FOLDER_NAME: str = "collages" -LIBRARY_FILENAME: str = "ts_library.json" - -# TODO: Turn this whitelist into a user-configurable blacklist. -IMAGE_TYPES: list[str] = [ - "png", - "jpg", - "jpeg", - "jpg_large", - "jpeg_large", - "jfif", - "gif", - "tif", - "tiff", - "heic", - "heif", - "webp", - "bmp", - "svg", - "avif", - "apng", - "jp2", - "j2k", - "jpg2", -] -VIDEO_TYPES: list[str] = [ - "mp4", - "webm", - "mov", - "hevc", - "mkv", - "avi", - "wmv", - "flv", - "gifv", - "m4p", - "m4v", - "3gp", -] -AUDIO_TYPES: list[str] = [ - "mp3", - "mp4", - "mpeg4", - "m4a", - "aac", - "wav", - "flac", - "alac", - "wma", - "ogg", - "aiff", -] -DOC_TYPES: list[str] = ["txt", "rtf", "md", "doc", "docx", "pdf", "tex", "odt", "pages"] -PLAINTEXT_TYPES: list[str] = [ - "txt", - "md", - "css", - "html", - "xml", - "json", - "js", - "ts", - "ini", - "htm", - "csv", - "php", - "sh", - "bat", -] -SPREADSHEET_TYPES: list[str] = ["csv", "xls", "xlsx", "numbers", "ods"] -PRESENTATION_TYPES: list[str] = ["ppt", "pptx", "key", "odp"] -ARCHIVE_TYPES: list[str] = ["zip", "rar", "tar", "tar.gz", "tgz", "7z"] -PROGRAM_TYPES: list[str] = ["exe", "app"] -SHORTCUT_TYPES: list[str] = ["lnk", "desktop", "url"] - -ALL_FILE_TYPES: list[str] = ( - IMAGE_TYPES - + VIDEO_TYPES - + AUDIO_TYPES - + DOC_TYPES - + SPREADSHEET_TYPES - + PRESENTATION_TYPES - + ARCHIVE_TYPES - + PROGRAM_TYPES - + SHORTCUT_TYPES -) - -BOX_FIELDS = ["tag_box", "text_box"] -TEXT_FIELDS = ["text_line", "text_box"] -DATE_FIELDS = ["datetime"] - -TAG_COLORS = [ - "", - "black", - "dark gray", - "gray", - "light gray", - "white", - "light pink", - "pink", - "red", - "red orange", - "orange", - "yellow orange", - "yellow", - "lime", - "light green", - "mint", - "green", - "teal", - "cyan", - "light blue", - "blue", - "blue violet", - "violet", - "purple", - "lavender", - "berry", - "magenta", - "salmon", - "auburn", - "dark brown", - "brown", - "light brown", - "blonde", - "peach", - "warm gray", - "cool gray", - "olive", -] +from src.core.constants import TS_FOLDER_NAME, TEXT_FIELDS class TagStudioCore: diff --git a/tagstudio/src/qt/modals/build_tag.py b/tagstudio/src/qt/modals/build_tag.py index fe84c463..1af34664 100644 --- a/tagstudio/src/qt/modals/build_tag.py +++ b/tagstudio/src/qt/modals/build_tag.py @@ -20,7 +20,7 @@ from src.core.library import Library, Tag from src.core.palette import ColorType, get_tag_color -from src.core.ts_core import TAG_COLORS +from src.core.constants import TAG_COLORS from src.qt.widgets.panel import PanelWidget, PanelModal from src.qt.widgets.tag import TagWidget from src.qt.modals.tag_search import TagSearchPanel diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 718268e7..5f3de6f8 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -49,9 +49,9 @@ from src.core.enums import SettingItems from src.core.library import ItemType -from src.core.ts_core import ( +from src.core.ts_core import TagStudioCore +from src.core.constants import ( PLAINTEXT_TYPES, - TagStudioCore, TAG_COLORS, DATE_FIELDS, TEXT_FIELDS, diff --git a/tagstudio/src/qt/widgets/collage_icon.py b/tagstudio/src/qt/widgets/collage_icon.py index 6984110f..409e7fed 100644 --- a/tagstudio/src/qt/widgets/collage_icon.py +++ b/tagstudio/src/qt/widgets/collage_icon.py @@ -24,7 +24,7 @@ ) from src.core.library import Library -from src.core.ts_core import DOC_TYPES, VIDEO_TYPES, IMAGE_TYPES +from src.core.constants import DOC_TYPES, VIDEO_TYPES, IMAGE_TYPES ERROR = f"[ERROR]" diff --git a/tagstudio/src/qt/widgets/item_thumb.py b/tagstudio/src/qt/widgets/item_thumb.py index 01964428..bcd0283d 100644 --- a/tagstudio/src/qt/widgets/item_thumb.py +++ b/tagstudio/src/qt/widgets/item_thumb.py @@ -23,8 +23,9 @@ QCheckBox, ) + from src.core.library import ItemType, Library, Entry -from src.core.ts_core import AUDIO_TYPES, VIDEO_TYPES, IMAGE_TYPES +from src.core.constants import AUDIO_TYPES, VIDEO_TYPES, IMAGE_TYPES from src.qt.flowlayout import FlowWidget from src.qt.helpers.file_opener import FileOpenerHelper from src.qt.widgets.thumb_renderer import ThumbRenderer diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index 92a69d18..bd6cf3b5 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -29,7 +29,7 @@ from src.core.enums import SettingItems, Theme from src.core.library import Entry, ItemType, Library -from src.core.ts_core import VIDEO_TYPES, IMAGE_TYPES +from src.core.constants import VIDEO_TYPES, IMAGE_TYPES from src.qt.helpers.file_opener import FileOpenerLabel, FileOpenerHelper, open_file from src.qt.modals.add_field import AddFieldModal from src.qt.widgets.thumb_renderer import ThumbRenderer diff --git a/tagstudio/src/qt/widgets/thumb_renderer.py b/tagstudio/src/qt/widgets/thumb_renderer.py index 641c6171..f3d5be64 100644 --- a/tagstudio/src/qt/widgets/thumb_renderer.py +++ b/tagstudio/src/qt/widgets/thumb_renderer.py @@ -24,7 +24,7 @@ from PIL.Image import DecompressionBombError from PySide6.QtCore import QObject, Signal, QSize from PySide6.QtGui import QPixmap -from src.core.ts_core import PLAINTEXT_TYPES, VIDEO_TYPES, IMAGE_TYPES +from src.core.constants import PLAINTEXT_TYPES, VIDEO_TYPES, IMAGE_TYPES ImageFile.LOAD_TRUNCATED_IMAGES = True diff --git a/tagstudio/tests/core/test_tags.py b/tagstudio/tests/core/test_tags.py index f3cd5791..e8e48754 100644 --- a/tagstudio/tests/core/test_tags.py +++ b/tagstudio/tests/core/test_tags.py @@ -1,18 +1,18 @@ from src.core.library import Tag -class TestTags: - def test_construction(self): - tag = Tag( - id=1, - name="Tag Name", - shorthand="TN", - aliases=["First A", "Second A"], - subtags_ids=[2, 3, 4], - color="", - ) - assert tag +def test_construction(): + tag = Tag( + id=1, + name="Tag Name", + shorthand="TN", + aliases=["First A", "Second A"], + subtags_ids=[2, 3, 4], + color="", + ) + assert tag - def test_empty_construction(self): - tag = Tag(id=1, name="", shorthand="", aliases=[], subtags_ids=[], color="") - assert tag + +def test_empty_construction(): + tag = Tag(id=1, name="", shorthand="", aliases=[], subtags_ids=[], color="") + assert tag