Skip to content

Commit 8946135

Browse files
committed
ci: add mypy check
1 parent 6e56f13 commit 8946135

23 files changed

+180
-132
lines changed

.github/workflows/mypy.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: MyPy
2+
3+
on: [ push, pull_request ]
4+
5+
6+
jobs:
7+
mypy:
8+
name: Run MyPy
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- name: Checkout code
13+
uses: actions/checkout@v4
14+
15+
- name: Set up Python
16+
uses: actions/setup-python@v5
17+
with:
18+
python-version: '3.12'
19+
20+
- name: Install dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
# pyside 6.6.3 has some issue in their .pyi files
24+
pip install PySide6==6.6.2
25+
pip install -r requirements.txt
26+
pip install mypy==1.10.0
27+
28+
- name: Run MyPy
29+
run: |
30+
cd tagstudio
31+
mkdir -p .mypy_cache
32+
mypy --install-types --non-interactive
33+
mypy --config-file ../pyproject.toml .

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
[tool.ruff]
22
exclude = ["main_window.py", "home_ui.py", "resources.py", "resources_rc.py"]
3+
4+
[tool.mypy]
5+
strict_optional = false
6+
disable_error_code = ["union-attr", "annotation-unchecked", "import-untyped"]
7+
explicit_package_bases = true

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
ruff==0.4.2
22
pre-commit==3.7.0
33
Pyinstaller==6.6.0
4+
mypy==1.10.0

tagstudio/src/cli/ts_cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# type: ignore
12
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
23
# Licensed under the GPL-3.0 License.
34
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio

tagstudio/src/core/field_template.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def __repr__(self) -> str:
1919

2020
def to_compressed_obj(self) -> dict:
2121
"""An alternative to __dict__ that only includes fields containing non-default data."""
22-
obj = {}
22+
obj: dict = {}
2323
# All Field fields (haha) are mandatory, so no value checks are done.
2424
obj["id"] = self.id
2525
obj["name"] = self.name

tagstudio/src/core/json_typing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class JsonLibary(TypedDict("", {"ts-version": str})):
88
fields: list # TODO
99
macros: "list[JsonMacro]"
1010
entries: "list[JsonEntry]"
11+
ignored_extensions: list[str]
1112

1213

1314
class JsonBase(TypedDict):

tagstudio/src/core/library.py

Lines changed: 49 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@
66

77
import datetime
88
import glob
9-
import json
109
import logging
1110
import os
1211
import sys
1312
import time
1413
import traceback
1514
import xml.etree.ElementTree as ET
1615
from enum import Enum
16+
from pathlib import Path
17+
from typing import cast, Generator
18+
19+
from typing_extensions import Self
20+
1721
import ujson
1822

1923
from src.core.json_typing import JsonCollation, JsonEntry, JsonLibary, JsonTag
@@ -42,7 +46,7 @@ def __init__(self, id: int, filename: str, path: str, fields: list[dict]) -> Non
4246
self.id = int(id)
4347
self.filename = filename
4448
self.path = path
45-
self.fields = fields
49+
self.fields: list[dict] = fields
4650
self.type = None
4751

4852
# Optional Fields ======================================================
@@ -75,6 +79,7 @@ def __repr__(self) -> str:
7579
return self.__str__()
7680

7781
def __eq__(self, __value: object) -> bool:
82+
__value = cast(Self, object)
7883
if os.name == "nt":
7984
return (
8085
int(self.id) == int(__value.id)
@@ -129,18 +134,16 @@ def remove_tag(self, library: "Library", tag_id: int, field_index=-1):
129134
)
130135
t.remove(tag_id)
131136
elif field_index < 0:
132-
t: list[int] = library.get_field_attr(f, "content")
137+
t = library.get_field_attr(f, "content")
133138
while tag_id in t:
134139
t.remove(tag_id)
135140

136141
def add_tag(
137-
self, library: "Library", tag_id: int, field_id: int, field_index: int = None
142+
self, library: "Library", tag_id: int, field_id: int, field_index: int = -1
138143
):
139-
# field_index: int = -1
140144
# if self.fields:
141145
# if field_index != -1:
142146
# logging.info(f'[LIBRARY] ADD TAG to E:{self.id}, F-DI:{field_id}, F-INDEX:{field_index}')
143-
field_index = -1 if field_index is None else field_index
144147
for i, f in enumerate(self.fields):
145148
if library.get_field_attr(f, "id") == field_id:
146149
field_index = i
@@ -183,7 +186,7 @@ def __init__(
183186
self.shorthand = shorthand
184187
self.aliases = aliases
185188
# Ensures no duplicates while retaining order.
186-
self.subtag_ids = []
189+
self.subtag_ids: list[int] = []
187190
for s in subtags_ids:
188191
if int(s) not in self.subtag_ids:
189192
self.subtag_ids.append(int(s))
@@ -276,18 +279,19 @@ def __repr__(self) -> str:
276279
return self.__str__()
277280

278281
def __eq__(self, __value: object) -> bool:
282+
__value = cast(Self, __value)
279283
if os.name == "nt":
280284
return (
281-
int(self.id) == int(__value.id_)
282-
and self.filename.lower() == __value.filename.lower()
283-
and self.path.lower() == __value.path.lower()
285+
int(self.id) == int(__value.id)
286+
and self.filename.lower() == __value.filename.lower() # type: ignore
287+
and self.path.lower() == __value.path.lower() # type: ignore
284288
and self.fields == __value.fields
285289
)
286290
else:
287291
return (
288-
int(self.id) == int(__value.id_)
289-
and self.filename == __value.filename
290-
and self.path == __value.path
292+
int(self.id) == int(__value.id)
293+
and self.filename == __value.filename # type: ignore
294+
and self.path == __value.path # type: ignore
291295
and self.fields == __value.fields
292296
)
293297

@@ -342,7 +346,7 @@ def __init__(self) -> None:
342346
self.files_not_in_library: list[str] = []
343347
self.missing_files: list[str] = []
344348
self.fixed_files: list[str] = [] # TODO: Get rid of this.
345-
self.missing_matches = {}
349+
self.missing_matches: dict = {}
346350
# Duplicate Files
347351
# Defined by files that are exact or similar copies to others. Generated by DupeGuru.
348352
# (Filepath, Matched Filepath, Match Percentage)
@@ -393,7 +397,7 @@ def __init__(self) -> None:
393397
# Tag(id=1, name='Favorite', shorthand='', aliases=['Favorited, Favorites, Likes, Liked, Loved'], subtags_ids=[], color='yellow'),
394398
# ]
395399

396-
self.default_fields = [
400+
self.default_fields: list[dict] = [
397401
{"id": 0, "name": "Title", "type": "text_line"},
398402
{"id": 1, "name": "Author", "type": "text_line"},
399403
{"id": 2, "name": "Artist", "type": "text_line"},
@@ -512,8 +516,8 @@ def open_library(self, path: str) -> int:
512516
),
513517
"r",
514518
encoding="utf-8",
515-
) as f:
516-
json_dump: JsonLibary = ujson.load(f)
519+
) as file:
520+
json_dump: JsonLibary = ujson.load(file)
517521
self.library_dir = str(path)
518522
self.verify_ts_folders()
519523
major, minor, patch = json_dump["ts-version"].split(".")
@@ -591,7 +595,7 @@ def open_library(self, path: str) -> int:
591595

592596
filename = entry.get("filename", "")
593597
e_path = entry.get("path", "")
594-
fields = []
598+
fields: list = []
595599
if "fields" in entry:
596600
# Cast JSON str keys to ints
597601
for f in entry["fields"]:
@@ -688,14 +692,14 @@ def open_library(self, path: str) -> int:
688692
self._next_collation_id = id + 1
689693

690694
title = collation.get("title", "")
691-
e_ids_and_pages = collation.get("e_ids_and_pages", "")
692-
sort_order = collation.get("sort_order", [])
693-
cover_id = collation.get("cover_id", [])
695+
e_ids_and_pages = collation.get("e_ids_and_pages", [])
696+
sort_order = collation.get("sort_order", "")
697+
cover_id = collation.get("cover_id", -1)
694698

695699
c = Collation(
696700
id=id,
697701
title=title,
698-
e_ids_and_pages=e_ids_and_pages,
702+
e_ids_and_pages=e_ids_and_pages, # type: ignore
699703
sort_order=sort_order,
700704
cover_id=cover_id,
701705
)
@@ -861,33 +865,33 @@ def clear_internal_vars(self):
861865
self.is_legacy_library = False
862866

863867
self.entries.clear()
864-
self._next_entry_id: int = 0
868+
self._next_entry_id = 0
865869
# self.filtered_entries.clear()
866870
self._entry_id_to_index_map.clear()
867871

868872
self._collation_id_to_index_map.clear()
869873

870874
self.missing_matches = {}
871-
self.dir_file_count: int = -1
875+
self.dir_file_count = -1
872876
self.files_not_in_library.clear()
873877
self.missing_files.clear()
874878
self.fixed_files.clear()
875-
self.filename_to_entry_id_map: dict[str, int] = {}
879+
self.filename_to_entry_id_map = {}
876880
self.ignored_extensions = self.default_ext_blacklist
877881

878882
self.tags.clear()
879-
self._next_tag_id: int = 1000
880-
self._tag_strings_to_id_map: dict[str, list[int]] = {}
881-
self._tag_id_to_cluster_map: dict[int, list[int]] = {}
882-
self._tag_id_to_index_map: dict[int, int] = {}
883+
self._next_tag_id = 1000
884+
self._tag_strings_to_id_map = {}
885+
self._tag_id_to_cluster_map = {}
886+
self._tag_id_to_index_map = {}
883887
self._tag_entry_ref_map.clear()
884888

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

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

893897
# Scans the directory for files, keeping track of:
@@ -1210,7 +1214,8 @@ def fix_missing_files(self):
12101214
# (int, str)
12111215

12121216
self._map_filenames_to_entry_ids()
1213-
self.remove_missing_matches(fixed_indices)
1217+
# TODO - the type here doesnt match but I cant reproduce calling this
1218+
self.remove_missing_matches(fixed_indices) # type: ignore
12141219

12151220
# for i in fixed_indices:
12161221
# # print(json_dump[i])
@@ -1330,10 +1335,11 @@ def get_collation(self, collation_id: int) -> Collation:
13301335
return self.collations[self._collation_id_to_index_map[int(collation_id)]]
13311336

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

13381344
# @deprecated('Use new Entry ID system.')
13391345
def get_entry_id_from_filepath(self, filename):
@@ -1368,7 +1374,7 @@ def search_library(
13681374

13691375
if query:
13701376
# start_time = time.time()
1371-
query: str = query.strip().lower()
1377+
query = query.strip().lower()
13721378
query_words: list[str] = query.split(" ")
13731379
all_tag_terms: list[str] = []
13741380
only_untagged: bool = "untagged" in query or "no tags" in query
@@ -1548,7 +1554,7 @@ def search_library(
15481554
else:
15491555
for entry in self.entries:
15501556
added = False
1551-
allowed_ext: bool = (
1557+
allowed_ext = (
15521558
os.path.splitext(entry.filename)[1][1:].lower()
15531559
not in self.ignored_extensions
15541560
)
@@ -1756,7 +1762,7 @@ def search_tags(
17561762

17571763
# if context and id_weights:
17581764
# time.sleep(3)
1759-
[final.append(idw[0]) for idw in id_weights if idw[0] not in final]
1765+
[final.append(idw[0]) for idw in id_weights if idw[0] not in final] # type: ignore
17601766
# print(f'Final IDs: \"{[self.get_tag_from_id(id).display_name(self) for id in final]}\"')
17611767
# print('')
17621768
return final
@@ -1774,7 +1780,7 @@ def get_all_child_tag_ids(self, tag_id: int) -> list[int]:
17741780

17751781
return subtag_ids
17761782

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

17801786
matches: list[int] = []
@@ -2127,12 +2133,12 @@ def add_field_to_entry(self, entry_id: int, field_id: int) -> None:
21272133
def mirror_entry_fields(self, entry_ids: list[int]) -> None:
21282134
"""Combines and mirrors all fields across a list of given Entry IDs."""
21292135

2130-
all_fields = []
2131-
all_ids = [] # Parallel to all_fields
2136+
all_fields: list = []
2137+
all_ids: list = [] # Parallel to all_fields
21322138
# Extract and merge all fields from all given Entries.
21332139
for id in entry_ids:
21342140
if id:
2135-
entry: Entry = self.get_entry(id)
2141+
entry = self.get_entry(id)
21362142
if entry and entry.fields:
21372143
for field in entry.fields:
21382144
# First checks if their are matching tag_boxes to append to
@@ -2153,7 +2159,7 @@ def mirror_entry_fields(self, entry_ids: list[int]) -> None:
21532159

21542160
# Replace each Entry's fields with the new merged ones.
21552161
for id in entry_ids:
2156-
entry: Entry = self.get_entry(id)
2162+
entry = self.get_entry(id)
21572163
if entry:
21582164
entry.fields = all_fields
21592165

@@ -2181,7 +2187,7 @@ def mirror_entry_fields(self, entry_ids: list[int]) -> None:
21812187
# pass
21822188
# # TODO: Implement.
21832189

2184-
def get_field_attr(self, entry_field, attribute: str):
2190+
def get_field_attr(self, entry_field: dict, attribute: str):
21852191
"""Returns the value of a specified attribute inside an Entry field."""
21862192
if attribute.lower() == "id":
21872193
return list(entry_field.keys())[0]
@@ -2236,7 +2242,7 @@ def _map_tag_strings_to_tag_id(self, tag: Tag) -> None:
22362242
self._tag_strings_to_id_map[shorthand].append(tag.id)
22372243

22382244
for alias in tag.aliases:
2239-
alias: str = strip_punctuation(alias).lower()
2245+
alias = strip_punctuation(alias).lower()
22402246
if alias not in self._tag_strings_to_id_map:
22412247
self._tag_strings_to_id_map[alias] = []
22422248
self._tag_strings_to_id_map[alias].append(tag.id)

tagstudio/src/core/palette.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from enum import Enum
66

77

8-
class ColorType(Enum):
8+
class ColorType(int, Enum):
99
PRIMARY = 0
1010
TEXT = 1
1111
BORDER = 2
@@ -278,7 +278,7 @@ class ColorType(Enum):
278278
}
279279

280280

281-
def get_tag_color(type: ColorType, color: str):
281+
def get_tag_color(type, color):
282282
color = color.lower()
283283
try:
284284
if type == ColorType.TEXT:

tagstudio/src/core/ts_core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ def match_conditions(self, entry_id: int) -> None:
300300
# input()
301301
pass
302302

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

306306
source = source.lower().replace("-", " ").replace("_", " ")

0 commit comments

Comments
 (0)