Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
Icosahunter authored May 17, 2024
2 parents 34e76d8 + ac9dd58 commit 0641eb4
Show file tree
Hide file tree
Showing 23 changed files with 194 additions and 131 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/mypy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: MyPy

on: [ push, pull_request ]


jobs:
mypy:
name: Run MyPy
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
# pyside 6.6.3 has some issue in their .pyi files
pip install PySide6==6.6.2
pip install -r requirements.txt
pip install mypy==1.10.0
- name: Run MyPy
run: |
cd tagstudio
mkdir -p .mypy_cache
mypy --install-types --non-interactive
mypy --config-file ../pyproject.toml .
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
[tool.ruff]
exclude = ["main_window.py", "home_ui.py", "resources.py", "resources_rc.py"]

[tool.mypy]
strict_optional = false
disable_error_code = ["union-attr", "annotation-unchecked", "import-untyped"]
explicit_package_bases = true
warn_unused_ignores = true
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
ruff==0.4.2
pre-commit==3.7.0
Pyinstaller==6.6.0
mypy==1.10.0
1 change: 1 addition & 0 deletions tagstudio/src/cli/ts_cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# type: ignore
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
Expand Down
2 changes: 1 addition & 1 deletion tagstudio/src/core/field_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __repr__(self) -> str:

def to_compressed_obj(self) -> dict:
"""An alternative to __dict__ that only includes fields containing non-default data."""
obj = {}
obj: dict = {}
# All Field fields (haha) are mandatory, so no value checks are done.
obj["id"] = self.id
obj["name"] = self.name
Expand Down
1 change: 1 addition & 0 deletions tagstudio/src/core/json_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class JsonLibary(TypedDict("", {"ts-version": str})):
fields: list # TODO
macros: "list[JsonMacro]"
entries: "list[JsonEntry]"
ignored_extensions: list[str]


class JsonBase(TypedDict):
Expand Down
75 changes: 41 additions & 34 deletions tagstudio/src/core/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@

import datetime
import glob
import json
import logging
import os
import sys
import time
import traceback
import typing
import xml.etree.ElementTree as ET
from enum import Enum
from pathlib import Path
from typing import cast, Generator

from typing_extensions import Self

import ujson
from pathlib import Path

Expand Down Expand Up @@ -45,7 +50,7 @@ def __init__(
self.id = int(id)
self.filename = Path(filename)
self.path = Path(path)
self.fields = fields
self.fields: list[dict] = fields
self.type = None

# Optional Fields ======================================================
Expand Down Expand Up @@ -78,6 +83,7 @@ def __repr__(self) -> str:
return self.__str__()

def __eq__(self, __value: object) -> bool:
__value = cast(Self, object)
return (
int(self.id) == int(__value.id)
and self.filename == __value.filename
Expand Down Expand Up @@ -124,18 +130,16 @@ def remove_tag(self, library: "Library", tag_id: int, field_index=-1):
)
t.remove(tag_id)
elif field_index < 0:
t: list[int] = library.get_field_attr(f, "content")
t = library.get_field_attr(f, "content")
while tag_id in t:
t.remove(tag_id)

def add_tag(
self, library: "Library", tag_id: int, field_id: int, field_index: int = None
self, library: "Library", tag_id: int, field_id: int, field_index: int = -1
):
# field_index: int = -1
# if self.fields:
# if field_index != -1:
# logging.info(f'[LIBRARY] ADD TAG to E:{self.id}, F-DI:{field_id}, F-INDEX:{field_index}')
field_index = -1 if field_index is None else field_index
for i, f in enumerate(self.fields):
if library.get_field_attr(f, "id") == field_id:
field_index = i
Expand Down Expand Up @@ -178,7 +182,7 @@ def __init__(
self.shorthand = shorthand
self.aliases = aliases
# Ensures no duplicates while retaining order.
self.subtag_ids = []
self.subtag_ids: list[int] = []
for s in subtags_ids:
if int(s) not in self.subtag_ids:
self.subtag_ids.append(int(s))
Expand Down Expand Up @@ -271,6 +275,7 @@ def __repr__(self) -> str:
return self.__str__()

def __eq__(self, __value: object) -> bool:
__value = cast(Self, __value)
return int(self.id) == int(__value.id_) and self.fields == __value.fields

def compressed_dict(self) -> JsonCollation:
Expand Down Expand Up @@ -324,7 +329,7 @@ def __init__(self) -> None:
self.files_not_in_library: list[str] = []
self.missing_files: list[str] = []
self.fixed_files: list[str] = [] # TODO: Get rid of this.
self.missing_matches = {}
self.missing_matches: dict = {}
# Duplicate Files
# Defined by files that are exact or similar copies to others. Generated by DupeGuru.
# (Filepath, Matched Filepath, Match Percentage)
Expand Down Expand Up @@ -375,7 +380,7 @@ def __init__(self) -> None:
# Tag(id=1, name='Favorite', shorthand='', aliases=['Favorited, Favorites, Likes, Liked, Loved'], subtags_ids=[], color='yellow'),
# ]

self.default_fields = [
self.default_fields: list[dict] = [
{"id": 0, "name": "Title", "type": "text_line"},
{"id": 1, "name": "Author", "type": "text_line"},
{"id": 2, "name": "Artist", "type": "text_line"},
Expand Down Expand Up @@ -490,8 +495,8 @@ def open_library(self, path: str | Path) -> int:
path / ts_core.TS_FOLDER_NAME / "ts_library.json",
"r",
encoding="utf-8",
) as f:
json_dump: JsonLibary = ujson.load(f)
) as file:
json_dump: JsonLibary = ujson.load(file)
self.library_dir = Path(path)
self.verify_ts_folders()
major, minor, patch = json_dump["ts-version"].split(".")
Expand Down Expand Up @@ -569,7 +574,7 @@ def open_library(self, path: str | Path) -> int:

filename = entry.get("filename", "")
e_path = entry.get("path", "")
fields = []
fields: list = []
if "fields" in entry:
# Cast JSON str keys to ints

Expand Down Expand Up @@ -667,14 +672,14 @@ def open_library(self, path: str | Path) -> int:
self._next_collation_id = id + 1

title = collation.get("title", "")
e_ids_and_pages = collation.get("e_ids_and_pages", "")
sort_order = collation.get("sort_order", [])
cover_id = collation.get("cover_id", [])
e_ids_and_pages = collation.get("e_ids_and_pages", [])
sort_order = collation.get("sort_order", "")
cover_id = collation.get("cover_id", -1)

c = Collation(
id=id,
title=title,
e_ids_and_pages=e_ids_and_pages,
e_ids_and_pages=e_ids_and_pages, # type: ignore
sort_order=sort_order,
cover_id=cover_id,
)
Expand Down Expand Up @@ -823,33 +828,33 @@ def clear_internal_vars(self):
self.is_legacy_library = False

self.entries.clear()
self._next_entry_id: int = 0
self._next_entry_id = 0
# self.filtered_entries.clear()
self._entry_id_to_index_map.clear()

self._collation_id_to_index_map.clear()

self.missing_matches = {}
self.dir_file_count: int = -1
self.dir_file_count = -1
self.files_not_in_library.clear()
self.missing_files.clear()
self.fixed_files.clear()
self.filename_to_entry_id_map: dict[Path, int] = {}
self.ignored_extensions = self.default_ext_blacklist

self.tags.clear()
self._next_tag_id: int = 1000
self._tag_strings_to_id_map: dict[str, list[int]] = {}
self._tag_id_to_cluster_map: dict[int, list[int]] = {}
self._tag_id_to_index_map: dict[int, int] = {}
self._next_tag_id = 1000
self._tag_strings_to_id_map = {}
self._tag_id_to_cluster_map = {}
self._tag_id_to_index_map = {}
self._tag_entry_ref_map.clear()

def refresh_dir(self):
def refresh_dir(self) -> Generator:
"""Scans a directory for files, and adds those relative filenames to internal variables."""

# Reset file interfacing variables.
# -1 means uninitialized, aka a scan like this was never attempted before.
self.dir_file_count: int = 0
self.dir_file_count = 0
self.files_not_in_library.clear()

# Scans the directory for files, keeping track of:
Expand Down Expand Up @@ -1141,6 +1146,7 @@ def fix_missing_files(self):
# (int, str)

self._map_filenames_to_entry_ids()
# TODO - the type here doesnt match but I cant reproduce calling this
self.remove_missing_matches(fixed_indices)

# for i in fixed_indices:
Expand Down Expand Up @@ -1284,10 +1290,11 @@ def get_collation(self, collation_id: int) -> Collation:
return self.collations[self._collation_id_to_index_map[int(collation_id)]]

# @deprecated('Use new Entry ID system.')
def get_entry_from_index(self, index: int) -> Entry:
def get_entry_from_index(self, index: int) -> Entry | None:
"""Returns a Library Entry object given its index in the unfiltered Entries list."""
if self.entries:
return self.entries[int(index)]
return None

# @deprecated('Use new Entry ID system.')
def get_entry_id_from_filepath(self, filename):
Expand All @@ -1314,7 +1321,7 @@ def search_library(

if query:
# start_time = time.time()
query: str = query.strip().lower()
query = query.strip().lower()
query_words: list[str] = query.split(" ")
all_tag_terms: list[str] = []
only_untagged: bool = "untagged" in query or "no tags" in query
Expand Down Expand Up @@ -1696,7 +1703,7 @@ def search_tags(

# if context and id_weights:
# time.sleep(3)
[final.append(idw[0]) for idw in id_weights if idw[0] not in final]
[final.append(idw[0]) for idw in id_weights if idw[0] not in final] # type: ignore
# print(f'Final IDs: \"{[self.get_tag_from_id(id).display_name(self) for id in final]}\"')
# print('')
return final
Expand All @@ -1714,7 +1721,7 @@ def get_all_child_tag_ids(self, tag_id: int) -> list[int]:

return subtag_ids

def filter_field_templates(self: str, query) -> list[int]:
def filter_field_templates(self, query: str) -> list[int]:
"""Returns a list of Field Template IDs returned from a string query."""

matches: list[int] = []
Expand Down Expand Up @@ -2067,12 +2074,12 @@ def add_field_to_entry(self, entry_id: int, field_id: int) -> None:
def mirror_entry_fields(self, entry_ids: list[int]) -> None:
"""Combines and mirrors all fields across a list of given Entry IDs."""

all_fields = []
all_ids = [] # Parallel to all_fields
all_fields: list = []
all_ids: list = [] # Parallel to all_fields
# Extract and merge all fields from all given Entries.
for id in entry_ids:
if id:
entry: Entry = self.get_entry(id)
entry = self.get_entry(id)
if entry and entry.fields:
for field in entry.fields:
# First checks if their are matching tag_boxes to append to
Expand All @@ -2093,7 +2100,7 @@ def mirror_entry_fields(self, entry_ids: list[int]) -> None:

# Replace each Entry's fields with the new merged ones.
for id in entry_ids:
entry: Entry = self.get_entry(id)
entry = self.get_entry(id)
if entry:
entry.fields = all_fields

Expand Down Expand Up @@ -2121,7 +2128,7 @@ def mirror_entry_fields(self, entry_ids: list[int]) -> None:
# pass
# # TODO: Implement.

def get_field_attr(self, entry_field, attribute: str):
def get_field_attr(self, entry_field: dict, attribute: str):
"""Returns the value of a specified attribute inside an Entry field."""
if attribute.lower() == "id":
return list(entry_field.keys())[0]
Expand Down Expand Up @@ -2176,7 +2183,7 @@ def _map_tag_strings_to_tag_id(self, tag: Tag) -> None:
self._tag_strings_to_id_map[shorthand].append(tag.id)

for alias in tag.aliases:
alias: str = strip_punctuation(alias).lower()
alias = strip_punctuation(alias).lower()
if alias not in self._tag_strings_to_id_map:
self._tag_strings_to_id_map[alias] = []
self._tag_strings_to_id_map[alias].append(tag.id)
Expand Down
4 changes: 2 additions & 2 deletions tagstudio/src/core/palette.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from enum import Enum


class ColorType(Enum):
class ColorType(int, Enum):
PRIMARY = 0
TEXT = 1
BORDER = 2
Expand Down Expand Up @@ -278,7 +278,7 @@ class ColorType(Enum):
}


def get_tag_color(type: ColorType, color: str):
def get_tag_color(type, color):
color = color.lower()
try:
if type == ColorType.TEXT:
Expand Down
2 changes: 1 addition & 1 deletion tagstudio/src/core/ts_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ def match_conditions(self, entry_id: int) -> None:
# input()
pass

def build_url(self, entry_id: int, source: str) -> str:
def build_url(self, entry_id: int, source: str):
"""Tries to rebuild a source URL given a specific filename structure."""

source = source.lower().replace("-", " ").replace("_", " ")
Expand Down
5 changes: 2 additions & 3 deletions tagstudio/src/qt/helpers/function_iterator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio


from types import FunctionType

from PySide6.QtCore import Signal, QObject
from typing import Callable


class FunctionIterator(QObject):
"""Iterates over a yielding function and emits progress as the 'value' signal.\n\nThread-Safe Guarantee™"""

value = Signal(object)

def __init__(self, function: FunctionType):
def __init__(self, function: Callable):
super().__init__()
self.iterable = function

Expand Down
Loading

0 comments on commit 0641eb4

Please sign in to comment.