Improve formatting and consistency

This commit is contained in:
Timmy Welch 2022-07-18 12:17:13 -07:00
parent 6e7660c3d9
commit 40314367c9
23 changed files with 265 additions and 310 deletions

View File

@ -868,7 +868,7 @@ class ComicArchive:
def get_page_name(self, index: int) -> str:
if index is None:
return None
return ""
page_list = self.get_page_name_list()
@ -1153,8 +1153,8 @@ class ComicArchive:
for n in self.archiver.get_filename_list():
if os.path.dirname(n) == "" and os.path.splitext(n)[1].casefold() == ".xml":
# read in XML file, and validate it
data = ""
try:
data = ""
d = self.archiver.read_file(n)
if d:
data = d.decode("utf-8")

View File

@ -131,7 +131,7 @@ class ComicBookInfo:
cbi_container = CBIContainer(
{
"appID": "ComicTagger/" + "1.0.0",
"appID": "ComicTagger/1.0.0",
"lastModified": str(datetime.now()),
"ComicBookInfo/1.0": {},
}

View File

@ -54,13 +54,11 @@ class ComicInfoXml:
return self.convert_xml_to_metadata(tree)
def string_from_metadata(self, metadata: GenericMetadata, xml: bytes = b"") -> str:
tree = self.convert_metadata_to_xml(self, metadata, xml)
tree = self.convert_metadata_to_xml(metadata, xml)
tree_str = ET.tostring(tree.getroot(), encoding="utf-8", xml_declaration=True).decode("utf-8")
return str(tree_str)
def convert_metadata_to_xml(
self, filename: ComicInfoXml, metadata: GenericMetadata, xml: bytes = b""
) -> ElementTree:
def convert_metadata_to_xml(self, metadata: GenericMetadata, xml: bytes = b"") -> ElementTree:
# shorthand for the metadata
md = metadata
@ -261,6 +259,8 @@ class ComicInfoXml:
p: dict[str, Any] = page.attrib
if "Image" in p:
p["Image"] = int(p["Image"])
if "DoublePage" in p:
p["DoublePage"] = True if p["DoublePage"].casefold() in ("yes", "true", "1") else False
md.pages.append(cast(ImageMetadata, p))
md.is_empty = False
@ -268,7 +268,7 @@ class ComicInfoXml:
return md
def write_to_external_file(self, filename: str, metadata: GenericMetadata, xml: bytes = b"") -> None:
tree = self.convert_metadata_to_xml(self, metadata, xml)
tree = self.convert_metadata_to_xml(metadata, xml)
tree.write(filename, encoding="utf-8", xml_declaration=True)
def read_from_external_file(self, filename: str) -> GenericMetadata:

View File

@ -1,5 +1,5 @@
# Extracted and mutilated from https://github.com/lordwelch/wsfmt
# Which was extracted and mutliated from https://github.com/golang/go/tree/master/src/text/template/parse
# Which was extracted and mutilated from https://github.com/golang/go/tree/master/src/text/template/parse
from __future__ import annotations
import calendar
@ -138,7 +138,7 @@ class Lexer:
# AcceptRun consumes a run of runes from the valid set.
def accept_run(self, valid: str) -> None:
while self.get() in valid:
pass
continue
self.backup()

View File

@ -114,7 +114,7 @@ class FileNameParser:
# remove any "of NN" phrase with spaces (problem: this could break on
# some titles)
filename = re.sub(r"of [\d]+", self.repl, filename)
filename = re.sub(r"of \d+", self.repl, filename)
# we should now have a cleaned up filename version with all the words in
# the same positions as original filename
@ -143,7 +143,7 @@ class FileNameParser:
# first look for a word with "#" followed by digits with optional suffix
# this is almost certainly the issue number
for w in reversed(word_list):
if re.match(r"#[-]?(([0-9]*\.[0-9]+|[0-9]+)(\w*))", w[0]):
if re.match(r"#-?((\d*\.\d+|\d+)(\w*))", w[0]):
found = True
break
@ -151,7 +151,7 @@ class FileNameParser:
# list
if not found:
w = word_list[-1]
if re.match(r"[-]?(([0-9]*\.[0-9]+|[0-9]+)(\w*))", w[0]):
if re.match(r"-?((\d*\.\d+|\d+)(\w*))", w[0]):
found = True
# now try to look for a # followed by any characters
@ -245,7 +245,7 @@ class FileNameParser:
if match:
year = match.group()
# remove non-digits
year = re.sub(r"[^0-9]", "", year)
year = re.sub(r"\D", "", year)
return year
def get_remainder(self, filename: str, year: str, count: str, volume: str, issue_end: int) -> str:
@ -332,7 +332,7 @@ eof = filenamelexer.Item(filenamelexer.ItemType.EOF, -1, "")
# Extracted and mutilated from https://github.com/lordwelch/wsfmt
# Which was extracted and mutliated from https://github.com/golang/go/tree/master/src/text/template/parse
# Which was extracted and mutilated from https://github.com/golang/go/tree/master/src/text/template/parse
class Parser:
"""docstring for FilenameParser"""

View File

@ -137,7 +137,7 @@ class GenericMetadata:
def copy(self) -> GenericMetadata:
return copy.deepcopy(self)
def replace(self, /, **kwargs) -> GenericMetadata:
def replace(self, /, **kwargs: Any) -> GenericMetadata:
tmp = self.copy()
tmp.__dict__.update(kwargs)
return tmp
@ -253,7 +253,7 @@ class GenericMetadata:
def add_credit(self, person: str, role: str, primary: bool = False) -> None:
credit: CreditMetadata = {"person": person, "role": role, "primary": primary}
credit = CreditMetadata(person=person, role=role, primary=primary)
# look to see if it's not already there...
found = False
@ -369,91 +369,91 @@ class GenericMetadata:
self.imprint = imprint
md_test = GenericMetadata()
md_test.is_empty = False
md_test.tag_origin = None
md_test.series = "Cory Doctorow's Futuristic Tales of the Here and Now"
md_test.issue = "1"
md_test.title = "Anda's Game"
md_test.publisher = "IDW Publishing"
md_test.month = 10
md_test.year = 2007
md_test.day = 1
md_test.issue_count = 6
md_test.volume = 1
md_test.genre = "Sci-Fi"
md_test.language = "en"
md_test.comments = (
"For 12-year-old Anda, getting paid real money to kill the characters of players who were cheating"
" in her favorite online computer game was a win-win situation. Until she found out who was paying her,"
" and what those characters meant to the livelihood of children around the world."
md_test: GenericMetadata = GenericMetadata(
is_empty=False,
tag_origin=None,
series="Cory Doctorow's Futuristic Tales of the Here and Now",
issue="1",
title="Anda's Game",
publisher="IDW Publishing",
month=10,
year=2007,
day=1,
issue_count=6,
volume=1,
genre="Sci-Fi",
language="en",
comments=(
"For 12-year-old Anda, getting paid real money to kill the characters of players who were cheating"
" in her favorite online computer game was a win-win situation. Until she found out who was paying her,"
" and what those characters meant to the livelihood of children around the world."
),
volume_count=None,
critical_rating=3.0,
country=None,
alternate_series="Tales",
alternate_number="2",
alternate_count=7,
imprint="craphound.com",
notes="Tagged with ComicTagger 1.3.2a5 using info from Comic Vine on 2022-04-16 15:52:26. [Issue ID 140529]",
web_link="https://comicvine.gamespot.com/cory-doctorows-futuristic-tales-of-the-here-and-no/4000-140529/",
format="Series",
manga="No",
black_and_white=None,
page_count=24,
maturity_rating="Everyone 10+",
story_arc="Here and Now",
series_group="Futuristic Tales",
scan_info="(CC BY-NC-SA 3.0)",
characters="Anda",
teams="Fahrenheit",
locations="lonely cottage ",
credits=[
CreditMetadata(primary=False, person="Dara Naraghi", role="Writer"),
CreditMetadata(primary=False, person="Esteve Polls", role="Penciller"),
CreditMetadata(primary=False, person="Esteve Polls", role="Inker"),
CreditMetadata(primary=False, person="Neil Uyetake", role="Letterer"),
CreditMetadata(primary=False, person="Sam Kieth", role="Cover"),
CreditMetadata(primary=False, person="Ted Adams", role="Editor"),
],
tags=set(),
pages=[
ImageMetadata(Image=0, ImageHeight="1280", ImageSize="195977", ImageWidth="800", Type=PageType.FrontCover),
ImageMetadata(Image=1, ImageHeight="2039", ImageSize="611993", ImageWidth="1327"),
ImageMetadata(Image=2, ImageHeight="2039", ImageSize="783726", ImageWidth="1327"),
ImageMetadata(Image=3, ImageHeight="2039", ImageSize="679584", ImageWidth="1327"),
ImageMetadata(Image=4, ImageHeight="2039", ImageSize="788179", ImageWidth="1327"),
ImageMetadata(Image=5, ImageHeight="2039", ImageSize="864433", ImageWidth="1327"),
ImageMetadata(Image=6, ImageHeight="2039", ImageSize="765606", ImageWidth="1327"),
ImageMetadata(Image=7, ImageHeight="2039", ImageSize="876427", ImageWidth="1327"),
ImageMetadata(Image=8, ImageHeight="2039", ImageSize="852622", ImageWidth="1327"),
ImageMetadata(Image=9, ImageHeight="2039", ImageSize="800205", ImageWidth="1327"),
ImageMetadata(Image=10, ImageHeight="2039", ImageSize="746243", ImageWidth="1326"),
ImageMetadata(Image=11, ImageHeight="2039", ImageSize="718062", ImageWidth="1327"),
ImageMetadata(Image=12, ImageHeight="2039", ImageSize="532179", ImageWidth="1326"),
ImageMetadata(Image=13, ImageHeight="2039", ImageSize="686708", ImageWidth="1327"),
ImageMetadata(Image=14, ImageHeight="2039", ImageSize="641907", ImageWidth="1327"),
ImageMetadata(Image=15, ImageHeight="2039", ImageSize="805388", ImageWidth="1327"),
ImageMetadata(Image=16, ImageHeight="2039", ImageSize="668927", ImageWidth="1326"),
ImageMetadata(Image=17, ImageHeight="2039", ImageSize="710605", ImageWidth="1327"),
ImageMetadata(Image=18, ImageHeight="2039", ImageSize="761398", ImageWidth="1326"),
ImageMetadata(Image=19, ImageHeight="2039", ImageSize="743807", ImageWidth="1327"),
ImageMetadata(Image=20, ImageHeight="2039", ImageSize="552911", ImageWidth="1326"),
ImageMetadata(Image=21, ImageHeight="2039", ImageSize="556827", ImageWidth="1327"),
ImageMetadata(Image=22, ImageHeight="2039", ImageSize="675078", ImageWidth="1326"),
ImageMetadata(
Bookmark="Interview",
Image=23,
ImageHeight="2032",
ImageSize="800965",
ImageWidth="1338",
Type=PageType.Letters,
),
],
price=None,
is_version_of=None,
rights=None,
identifier=None,
last_mark=None,
cover_image=None,
)
md_test.volume_count = None
md_test.critical_rating = 3.0
md_test.country = None
md_test.alternate_series = "Tales"
md_test.alternate_number = "2"
md_test.alternate_count = 7
md_test.imprint = "craphound.com"
md_test.notes = "Tagged with ComicTagger 1.3.2a5 using info from Comic Vine on 2022-04-16 15:52:26. [Issue ID 140529]"
md_test.web_link = "https://comicvine.gamespot.com/cory-doctorows-futuristic-tales-of-the-here-and-no/4000-140529/"
md_test.format = "Series"
md_test.manga = "No"
md_test.black_and_white = None
md_test.page_count = 24
md_test.maturity_rating = "Everyone 10+"
md_test.story_arc = "Here and Now"
md_test.series_group = "Futuristic Tales"
md_test.scan_info = "(CC BY-NC-SA 3.0)"
md_test.characters = "Anda"
md_test.teams = "Fahrenheit"
md_test.locations = "lonely cottage "
md_test.credits = [
CreditMetadata({"primary": False, "person": "Dara Naraghi", "role": "Writer"}),
CreditMetadata({"primary": False, "person": "Esteve Polls", "role": "Penciller"}),
CreditMetadata({"primary": False, "person": "Esteve Polls", "role": "Inker"}),
CreditMetadata({"primary": False, "person": "Neil Uyetake", "role": "Letterer"}),
CreditMetadata({"primary": False, "person": "Sam Kieth", "role": "Cover"}),
CreditMetadata({"primary": False, "person": "Ted Adams", "role": "Editor"}),
]
md_test.tags = set()
md_test.pages = [
{"Image": 0, "ImageHeight": "1280", "ImageSize": "195977", "ImageWidth": "800", "Type": PageType.FrontCover},
{"Image": 1, "ImageHeight": "2039", "ImageSize": "611993", "ImageWidth": "1327"},
{"Image": 2, "ImageHeight": "2039", "ImageSize": "783726", "ImageWidth": "1327"},
{"Image": 3, "ImageHeight": "2039", "ImageSize": "679584", "ImageWidth": "1327"},
{"Image": 4, "ImageHeight": "2039", "ImageSize": "788179", "ImageWidth": "1327"},
{"Image": 5, "ImageHeight": "2039", "ImageSize": "864433", "ImageWidth": "1327"},
{"Image": 6, "ImageHeight": "2039", "ImageSize": "765606", "ImageWidth": "1327"},
{"Image": 7, "ImageHeight": "2039", "ImageSize": "876427", "ImageWidth": "1327"},
{"Image": 8, "ImageHeight": "2039", "ImageSize": "852622", "ImageWidth": "1327"},
{"Image": 9, "ImageHeight": "2039", "ImageSize": "800205", "ImageWidth": "1327"},
{"Image": 10, "ImageHeight": "2039", "ImageSize": "746243", "ImageWidth": "1326"},
{"Image": 11, "ImageHeight": "2039", "ImageSize": "718062", "ImageWidth": "1327"},
{"Image": 12, "ImageHeight": "2039", "ImageSize": "532179", "ImageWidth": "1326"},
{"Image": 13, "ImageHeight": "2039", "ImageSize": "686708", "ImageWidth": "1327"},
{"Image": 14, "ImageHeight": "2039", "ImageSize": "641907", "ImageWidth": "1327"},
{"Image": 15, "ImageHeight": "2039", "ImageSize": "805388", "ImageWidth": "1327"},
{"Image": 16, "ImageHeight": "2039", "ImageSize": "668927", "ImageWidth": "1326"},
{"Image": 17, "ImageHeight": "2039", "ImageSize": "710605", "ImageWidth": "1327"},
{"Image": 18, "ImageHeight": "2039", "ImageSize": "761398", "ImageWidth": "1326"},
{"Image": 19, "ImageHeight": "2039", "ImageSize": "743807", "ImageWidth": "1327"},
{"Image": 20, "ImageHeight": "2039", "ImageSize": "552911", "ImageWidth": "1326"},
{"Image": 21, "ImageHeight": "2039", "ImageSize": "556827", "ImageWidth": "1327"},
{"Image": 22, "ImageHeight": "2039", "ImageSize": "675078", "ImageWidth": "1326"},
{
"Bookmark": "Interview",
"Image": 23,
"ImageHeight": "2032",
"ImageSize": "800965",
"ImageWidth": "1338",
"Type": PageType.Letters,
},
]
md_test.price = None
md_test.is_version_of = None
md_test.rights = None
md_test.identifier = None
md_test.last_mark = None
md_test.cover_image = None

View File

@ -113,6 +113,4 @@ class IssueString:
# return the float, with no suffix
if len(self.suffix) == 1 and self.suffix.isnumeric():
return (self.num or 0) + unicodedata.numeric(self.suffix)
return 0.5
return self.num

View File

@ -137,9 +137,9 @@ def sanitize_title(text: str, basic: bool = False) -> str:
# replace any "dash punctuation" with a space
# makes sure that batman-superman and self-proclaimed stay separate words
text = "".join(
c if not unicodedata.category(c) in ("Pd") else " "
c if not unicodedata.category(c) in ("Pd",) else " "
for c in text
if unicodedata.category(c)[0] in "LZN" or unicodedata.category(c) in ("Pd")
if unicodedata.category(c)[0] in "LZN" or unicodedata.category(c) in ("Pd",)
)
# remove extra space and articles and all lower case
text = remove_articles(text).strip()
@ -147,7 +147,7 @@ def sanitize_title(text: str, basic: bool = False) -> str:
return text
def titles_match(search_title: str, record_title: str, threshold: int = 90):
def titles_match(search_title: str, record_title: str, threshold: int = 90) -> int:
sanitized_search = sanitize_title(search_title)
sanitized_record = sanitize_title(record_title)
ratio = thefuzz.fuzz.ratio(sanitized_search, sanitized_record)
@ -231,7 +231,7 @@ class ImprintDict(dict):
if the key does not exist the key is returned as the publisher unchanged
"""
def __init__(self, publisher: str, mapping=(), **kwargs) -> None:
def __init__(self, publisher: str, mapping: tuple | Mapping = (), **kwargs: dict) -> None:
super().__init__(mapping, **kwargs)
self.publisher = publisher

View File

@ -12,18 +12,18 @@ logger = logging.getLogger(__name__)
class QTextEditLogger(QtCore.QObject, logging.Handler):
qlog = QtCore.pyqtSignal(str)
def __init__(self, formatter: logging.Formatter, level: int):
def __init__(self, formatter: logging.Formatter, level: int) -> None:
super().__init__()
self.setFormatter(formatter)
self.setLevel(level)
def emit(self, record):
def emit(self, record: logging.LogRecord) -> None:
msg = self.format(record)
self.qlog.emit(msg.strip())
class ApplicationLogWindow(QtWidgets.QDialog):
def __init__(self, log_handler: QTextEditLogger, parent=None):
def __init__(self, log_handler: QTextEditLogger, parent: QtCore.QObject = None) -> None:
super().__init__(parent)
uic.loadUi(ComicTaggerSettings.get_ui_file("logwindow.ui"), self)
@ -43,7 +43,7 @@ class ApplicationLogWindow(QtWidgets.QDialog):
self._button.clicked.connect(self.test)
self.textEdit.setTabStopDistance(self.textEdit.tabStopDistance() * 2)
def test(self):
def test(self) -> None:
logger.debug("damn, a bug")
logger.info("something to remember")
logger.warning("that's not right")

View File

@ -22,7 +22,7 @@ import sqlite3 as lite
from typing import Any
from comictaggerlib import ctversion
from comictaggerlib.resulttypes import CVIssuesResults, CVVolumeResults, SelectDetails
from comictaggerlib.resulttypes import CVImage, CVIssuesResults, CVPublisher, CVVolumeResults, SelectDetails
from comictaggerlib.settings import ComicTaggerSettings
logger = logging.getLogger(__name__)
@ -194,16 +194,14 @@ class ComicCacher:
# now process the results
for record in rows:
result = CVVolumeResults(
{
"id": record[1],
"name": record[2],
"start_year": record[3],
"count_of_issues": record[5],
"description": record[7],
"publisher": {"name": record[4]},
"image": {"super_url": record[6]},
"aliases": record[10],
}
id=record[1],
name=record[2],
start_year=record[3],
count_of_issues=record[5],
description=record[7],
publisher=CVPublisher(name=record[4]),
image=CVImage(super_url=record[6]),
aliases=record[10],
)
results.append(result)
@ -248,7 +246,7 @@ class ComicCacher:
url_list_str = row[0]
if not url_list_str:
return []
url_list = url_list_str.split(",")
url_list = str(url_list_str).split(",")
return url_list
def add_volume_info(self, source_name: str, cv_volume_record: CVVolumeResults) -> None:
@ -333,14 +331,12 @@ class ComicCacher:
# since ID is primary key, there is only one row
result = CVVolumeResults(
{
"id": row[1],
"name": row[2],
"count_of_issues": row[4],
"start_year": row[5],
"publisher": {"name": row[3]},
"aliases": row[6],
}
id=row[1],
name=row[2],
count_of_issues=row[4],
start_year=row[5],
publisher=CVPublisher(name=row[3]),
aliases=row[6],
)
return result
@ -372,16 +368,14 @@ class ComicCacher:
# now process the results
for row in rows:
record = CVIssuesResults(
{
"id": row[1],
"name": row[2],
"issue_number": row[3],
"site_detail_url": row[4],
"cover_date": row[5],
"image": {"super_url": row[6], "thumb_url": row[7]},
"description": row[8],
"aliases": row[9],
}
id=row[1],
name=row[2],
issue_number=row[3],
site_detail_url=row[4],
cover_date=row[5],
image=CVImage(super_url=row[6], thumb_url=row[7]),
description=row[8],
aliases=row[9],
)
results.append(record)
@ -430,12 +424,10 @@ class ComicCacher:
row = cur.fetchone()
details = SelectDetails(
{
"image_url": None,
"thumb_image_url": None,
"cover_date": None,
"site_detail_url": None,
}
image_url=None,
thumb_image_url=None,
cover_date=None,
site_detail_url=None,
)
if row is not None and row[0] is not None:
details["image_url"] = row[0]

View File

@ -28,9 +28,8 @@ from bs4 import BeautifulSoup
from comicapi import utils
from comicapi.genericmetadata import GenericMetadata
from comicapi.issuestring import IssueString
from comictaggerlib import ctversion
from comictaggerlib import ctversion, resulttypes
from comictaggerlib.comiccacher import ComicCacher
from comictaggerlib.resulttypes import CVIssueDetailResults, CVIssuesResults, CVResult, CVVolumeResults, SelectDetails
from comictaggerlib.settings import ComicTaggerSettings
logger = logging.getLogger(__name__)
@ -140,7 +139,7 @@ class ComicVineTalker:
try:
test_url = url + "/issue/1/?api_key=" + key + "&format=json&field_list=name"
cv_response: CVResult = requests.get(
cv_response: resulttypes.CVResult = requests.get(
test_url, headers={"user-agent": "comictagger/" + ctversion.version}
).json()
@ -149,7 +148,7 @@ class ComicVineTalker:
except Exception:
return False
def get_cv_content(self, url: str, params: dict[str, Any]) -> CVResult:
def get_cv_content(self, url: str, params: dict[str, Any]) -> resulttypes.CVResult:
"""
Get the content from the CV server. If we're in "wait mode" and status code is a rate limit error
sleep for a bit and retry.
@ -159,7 +158,7 @@ class ComicVineTalker:
counter = 0
wait_times = [1, 2, 3, 4]
while True:
cv_response: CVResult = self.get_url_content(url, params)
cv_response: resulttypes.CVResult = self.get_url_content(url, params)
if self.wait_for_rate_limit and cv_response["status_code"] == ComicVineTalkerException.RateLimit:
self.write_log(f"Rate limit encountered. Waiting for {limit_wait_time} minutes\n")
time.sleep(limit_wait_time * 60)
@ -208,7 +207,7 @@ class ComicVineTalker:
callback: Callable[[int, int], None] | None = None,
refresh_cache: bool = False,
literal: bool = False,
) -> list[CVVolumeResults]:
) -> list[resulttypes.CVVolumeResults]:
# Sanitize the series name for comicvine searching, comicvine search ignore symbols
search_series_name = utils.sanitize_title(series_name, literal)
@ -235,7 +234,7 @@ class ComicVineTalker:
cv_response = self.get_cv_content(self.api_base_url + "/search", params)
search_results: list[CVVolumeResults] = []
search_results: list[resulttypes.CVVolumeResults] = []
# see http://api.comicvine.com/documentation/#handling_responses
@ -255,21 +254,20 @@ class ComicVineTalker:
self.write_log(
f"Found {cv_response['number_of_page_results']} of {cv_response['number_of_total_results']} results\n"
)
search_results.extend(cast(list[CVVolumeResults], cv_response["results"]))
search_results.extend(cast(list[resulttypes.CVVolumeResults], cv_response["results"]))
page = 1
if callback is not None:
callback(current_result_count, total_result_count)
# see if we need to keep asking for more pages...
stop_searching = False
while current_result_count < total_result_count:
if not literal:
# Stop searching once any entry falls below the threshold
stop_searching = any(
not utils.titles_match(search_series_name, volume["name"], self.series_match_thresh)
for volume in cast(list[CVVolumeResults], cv_response["results"])
for volume in cast(list[resulttypes.CVVolumeResults], cv_response["results"])
)
if stop_searching:
@ -282,7 +280,7 @@ class ComicVineTalker:
params["page"] = page
cv_response = self.get_cv_content(self.api_base_url + "/search", params)
search_results.extend(cast(list[CVVolumeResults], cv_response["results"]))
search_results.extend(cast(list[resulttypes.CVVolumeResults], cv_response["results"]))
current_result_count += cv_response["number_of_page_results"]
if callback is not None:
@ -294,7 +292,7 @@ class ComicVineTalker:
return search_results
def fetch_volume_data(self, series_id: int) -> CVVolumeResults:
def fetch_volume_data(self, series_id: int) -> resulttypes.CVVolumeResults:
# before we search online, look in our cache, since we might already have this info
cvc = ComicCacher()
@ -312,14 +310,14 @@ class ComicVineTalker:
}
cv_response = self.get_cv_content(volume_url, params)
volume_results = cast(CVVolumeResults, cv_response["results"])
volume_results = cast(resulttypes.CVVolumeResults, cv_response["results"])
if volume_results:
cvc.add_volume_info(self.source_name, volume_results)
return volume_results
def fetch_issues_by_volume(self, series_id: int) -> list[CVIssuesResults]:
def fetch_issues_by_volume(self, series_id: int) -> list[resulttypes.CVIssuesResults]:
# before we search online, look in our cache, since we might already have this info
cvc = ComicCacher()
cached_volume_issues_result = cvc.get_volume_issues_info(series_id, self.source_name)
@ -339,7 +337,7 @@ class ComicVineTalker:
current_result_count = cv_response["number_of_page_results"]
total_result_count = cv_response["number_of_total_results"]
volume_issues_result = cast(list[CVIssuesResults], cv_response["results"])
volume_issues_result = cast(list[resulttypes.CVIssuesResults], cv_response["results"])
page = 1
offset = 0
@ -351,7 +349,7 @@ class ComicVineTalker:
params["offset"] = offset
cv_response = self.get_cv_content(self.api_base_url + "/issues/", params)
volume_issues_result.extend(cast(list[CVIssuesResults], cv_response["results"]))
volume_issues_result.extend(cast(list[resulttypes.CVIssuesResults], cv_response["results"]))
current_result_count += cv_response["number_of_page_results"]
self.repair_urls(volume_issues_result)
@ -362,7 +360,7 @@ class ComicVineTalker:
def fetch_issues_by_volume_issue_num_and_year(
self, volume_id_list: list[int], issue_number: str, year: str | int | None
) -> list[CVIssuesResults]:
) -> list[resulttypes.CVIssuesResults]:
volume_filter = ""
for vid in volume_id_list:
volume_filter += str(vid) + "|"
@ -384,7 +382,7 @@ class ComicVineTalker:
current_result_count = cv_response["number_of_page_results"]
total_result_count = cv_response["number_of_total_results"]
filtered_issues_result = cast(list[CVIssuesResults], cv_response["results"])
filtered_issues_result = cast(list[resulttypes.CVIssuesResults], cv_response["results"])
page = 1
offset = 0
@ -396,7 +394,7 @@ class ComicVineTalker:
params["offset"] = offset
cv_response = self.get_cv_content(self.api_base_url + "/issues/", params)
filtered_issues_result.extend(cast(list[CVIssuesResults], cv_response["results"]))
filtered_issues_result.extend(cast(list[resulttypes.CVIssuesResults], cv_response["results"]))
current_result_count += cv_response["number_of_page_results"]
self.repair_urls(filtered_issues_result)
@ -422,7 +420,7 @@ class ComicVineTalker:
issue_url = self.api_base_url + "/issue/" + CVTypeID.Issue + "-" + str(f_record["id"])
params = {"api_key": self.api_key, "format": "json"}
cv_response = self.get_cv_content(issue_url, params)
issue_results = cast(CVIssueDetailResults, cv_response["results"])
issue_results = cast(resulttypes.CVIssueDetailResults, cv_response["results"])
else:
return GenericMetadata()
@ -436,7 +434,7 @@ class ComicVineTalker:
params = {"api_key": self.api_key, "format": "json"}
cv_response = self.get_cv_content(issue_url, params)
issue_results = cast(CVIssueDetailResults, cv_response["results"])
issue_results = cast(resulttypes.CVIssueDetailResults, cv_response["results"])
volume_results = self.fetch_volume_data(issue_results["volume"]["id"])
@ -446,7 +444,10 @@ class ComicVineTalker:
return md
def map_cv_data_to_metadata(
self, volume_results: CVVolumeResults, issue_results: CVIssueDetailResults, settings: ComicTaggerSettings
self,
volume_results: resulttypes.CVVolumeResults,
issue_results: resulttypes.CVIssueDetailResults,
settings: ComicTaggerSettings,
) -> GenericMetadata:
# Now, map the Comic Vine data to generic metadata
@ -613,7 +614,7 @@ class ComicVineTalker:
details = self.fetch_issue_select_details(issue_id)
return details["site_detail_url"]
def fetch_issue_select_details(self, issue_id: int) -> SelectDetails:
def fetch_issue_select_details(self, issue_id: int) -> resulttypes.SelectDetails:
cached_details = self.fetch_cached_issue_select_details(issue_id)
if cached_details["image_url"] is not None:
return cached_details
@ -623,14 +624,14 @@ class ComicVineTalker:
params = {"api_key": self.api_key, "format": "json", "field_list": "image,cover_date,site_detail_url"}
cv_response = self.get_cv_content(issue_url, params)
results = cast(CVIssueDetailResults, cv_response["results"])
results = cast(resulttypes.CVIssueDetailResults, cv_response["results"])
details: SelectDetails = {
"image_url": results["image"]["super_url"],
"thumb_image_url": results["image"]["thumb_url"],
"cover_date": results["cover_date"],
"site_detail_url": results["site_detail_url"],
}
details = resulttypes.SelectDetails(
image_url=results["image"]["super_url"],
thumb_image_url=results["image"]["thumb_url"],
cover_date=results["cover_date"],
site_detail_url=results["site_detail_url"],
)
if (
details["image_url"] is not None
@ -647,7 +648,7 @@ class ComicVineTalker:
)
return details
def fetch_cached_issue_select_details(self, issue_id: int) -> SelectDetails:
def fetch_cached_issue_select_details(self, issue_id: int) -> resulttypes.SelectDetails:
# before we search online, look in our cache, since we might already have this info
cvc = ComicCacher()
@ -736,7 +737,7 @@ class ComicVineTalker:
data = reply.readAll()
try:
cv_response = cast(CVResult, json.loads(bytes(data)))
cv_response = cast(resulttypes.CVResult, json.loads(bytes(data)))
except Exception:
logger.exception("Comic Vine query failed to get JSON data\n%s", str(data))
return
@ -745,7 +746,7 @@ class ComicVineTalker:
logger.error("Comic Vine query failed with error: [%s]. ", cv_response["error"])
return
result = cast(CVIssuesResults, cv_response["results"])
result = cast(resulttypes.CVIssuesResults, cv_response["results"])
image_url = result["image"]["super_url"]
thumb_url = result["image"]["thumb_url"]
@ -778,12 +779,15 @@ class ComicVineTalker:
ComicVineTalker.alt_url_list_fetch_complete(alt_cover_url_list)
def repair_urls(
self, issue_list: list[CVIssuesResults] | list[CVVolumeResults] | list[CVIssueDetailResults]
self,
issue_list: list[resulttypes.CVIssuesResults]
| list[resulttypes.CVVolumeResults]
| list[resulttypes.CVIssueDetailResults],
) -> None:
# make sure there are URLs for the image fields
for issue in issue_list:
if issue["image"] is None:
issue["image"] = {
"super_url": ComicVineTalker.logo_url,
"thumb_url": ComicVineTalker.logo_url,
}
issue["image"] = resulttypes.CVImage(
super_url=ComicVineTalker.logo_url,
thumb_url=ComicVineTalker.logo_url,
)

View File

@ -90,6 +90,7 @@ class CoverImageWidget(QtWidgets.QWidget):
def __init__(self, parent: QtWidgets.QWidget, mode: int, expand_on_click: bool = True) -> None:
super().__init__(parent)
self.cover_fetcher = ImageFetcher()
uic.loadUi(ComicTaggerSettings.get_ui_file("coverimagewidget.ui"), self)
reduce_widget_font_size(self.label)
@ -193,7 +194,7 @@ class CoverImageWidget(QtWidgets.QWidget):
self.update_content()
def primary_url_fetch_complete(self, primary_url: str, thumb_url: str | None) -> None:
def primary_url_fetch_complete(self, primary_url: str, thumb_url: str | None = None) -> None:
self.url_list.append(str(primary_url))
self.imageIndex = 0
self.imageCount = len(self.url_list)

View File

@ -170,16 +170,17 @@ class FileRenamer:
md_dict["month_name"] = ""
md_dict["month_abbr"] = ""
for Component in pathlib.PureWindowsPath(template).parts:
new_basename = ""
for component in pathlib.PureWindowsPath(template).parts:
if (
self.platform.casefold() in ["universal", "windows"] or sys.platform.casefold() in ["windows"]
) and self.smart_cleanup:
# colons get special treatment
Component = Component.replace(": ", " - ")
Component = Component.replace(":", "-")
component = component.replace(": ", " - ")
component = component.replace(":", "-")
new_basename = str(
sanitize_filename(fmt.vformat(Component, args=[], kwargs=Default(md_dict)), platform=self.platform)
sanitize_filename(fmt.vformat(component, args=[], kwargs=Default(md_dict)), platform=self.platform)
).strip()
new_name = os.path.join(new_name, new_basename)

View File

@ -246,12 +246,12 @@ class FileSelectionList(QtWidgets.QWidget):
if platform.system() == "Windows":
rar_help = windowsRarHelp
elif platform.system() == "Linux":
rar_help = linuxRarHelp
elif platform.system() == "Darwin":
rar_help = macRarHelp
else:
rar_help = linuxRarHelp
OptionalMessageDialog.msg_no_checkbox(
self,
"RAR Files are Read-Only",
@ -376,7 +376,7 @@ class FileSelectionList(QtWidgets.QWidget):
try:
fi.ca.read_cix()
except Exception:
...
pass
fi.ca.has_cbi()
def get_selected_archive_list(self) -> list[ComicArchive]:

View File

@ -38,7 +38,7 @@ logger = logging.getLogger(__name__)
class ImageFetcherException(Exception):
pass
...
def fetch_complete(image_data: bytes | QtCore.QByteArray) -> None:

View File

@ -57,11 +57,11 @@ class Score(TypedDict):
class IssueIdentifierNetworkError(Exception):
pass
...
class IssueIdentifierCancelled(Exception):
pass
...
class IssueIdentifier:
@ -175,35 +175,29 @@ class IssueIdentifier:
def get_search_keys(self) -> SearchKeys:
ca = self.comic_archive
search_keys: SearchKeys = {
"series": None,
"issue_number": None,
"month": None,
"year": None,
"issue_count": None,
}
if ca is None:
return None
search_keys: SearchKeys
if self.only_use_additional_meta_data:
search_keys["series"] = self.additional_metadata.series
search_keys["issue_number"] = self.additional_metadata.issue
search_keys["year"] = self.additional_metadata.year
search_keys["month"] = self.additional_metadata.month
search_keys["issue_count"] = self.additional_metadata.issue_count
search_keys = SearchKeys(
series=self.additional_metadata.series,
issue_number=self.additional_metadata.issue,
year=self.additional_metadata.year,
month=self.additional_metadata.month,
issue_count=self.additional_metadata.issue_count,
)
return search_keys
# see if the archive has any useful meta data for searching with
if ca.has_cix():
try:
try:
if ca.has_cix():
internal_metadata = ca.read_cix()
except Exception as e:
logger.error("Failed to load metadata for %s: %s", ca.path, e)
elif ca.has_cbi():
internal_metadata = ca.read_cbi()
else:
internal_metadata = ca.read_cbi()
else:
internal_metadata = ca.read_cbi()
except Exception as e:
internal_metadata = GenericMetadata()
logger.error("Failed to load metadata for %s: %s", ca.path, e)
# try to get some metadata from filename
md_from_filename = ca.metadata_from_filename(
@ -213,45 +207,22 @@ class IssueIdentifier:
self.settings.remove_publisher,
)
working_md = md_from_filename.copy()
working_md.overlay(internal_metadata)
working_md.overlay(self.additional_metadata)
# preference order:
# 1. Additional metadata
# 1. Internal metadata
# 1. Filename metadata
if self.additional_metadata.series is not None:
search_keys["series"] = self.additional_metadata.series
elif internal_metadata.series is not None:
search_keys["series"] = internal_metadata.series
else:
search_keys["series"] = md_from_filename.series
if self.additional_metadata.issue is not None:
search_keys["issue_number"] = self.additional_metadata.issue
elif internal_metadata.issue is not None:
search_keys["issue_number"] = internal_metadata.issue
else:
search_keys["issue_number"] = md_from_filename.issue
if self.additional_metadata.year is not None:
search_keys["year"] = self.additional_metadata.year
elif internal_metadata.year is not None:
search_keys["year"] = internal_metadata.year
else:
search_keys["year"] = md_from_filename.year
if self.additional_metadata.month is not None:
search_keys["month"] = self.additional_metadata.month
elif internal_metadata.month is not None:
search_keys["month"] = internal_metadata.month
else:
search_keys["month"] = md_from_filename.month
if self.additional_metadata.issue_count is not None:
search_keys["issue_count"] = self.additional_metadata.issue_count
elif internal_metadata.issue_count is not None:
search_keys["issue_count"] = internal_metadata.issue_count
else:
search_keys["issue_count"] = md_from_filename.issue_count
search_keys = SearchKeys(
series=working_md.series,
issue_number=working_md.issue,
year=working_md.year,
month=working_md.month,
issue_count=working_md.issue_count,
)
return search_keys
@ -293,9 +264,7 @@ class IssueIdentifier:
if self.cover_url_callback is not None:
self.cover_url_callback(url_image_data)
remote_cover_list = []
remote_cover_list.append(Score({"url": primary_img_url, "hash": self.calculate_hash(url_image_data)}))
remote_cover_list = [Score(url=primary_img_url, hash=self.calculate_hash(url_image_data))]
if self.cancel:
raise IssueIdentifierCancelled
@ -316,7 +285,7 @@ class IssueIdentifier:
if self.cover_url_callback is not None:
self.cover_url_callback(alt_url_image_data)
remote_cover_list.append(Score({"url": alt_url, "hash": self.calculate_hash(alt_url_image_data)}))
remote_cover_list.append(Score(url=alt_url, hash=self.calculate_hash(alt_url_image_data)))
if self.cancel:
raise IssueIdentifierCancelled
@ -331,9 +300,7 @@ class IssueIdentifier:
for local_cover_hash in local_cover_hash_list:
for remote_cover_item in remote_cover_list:
score = ImageHasher.hamming_distance(local_cover_hash, remote_cover_item["hash"])
score_list.append(
Score({"score": score, "url": remote_cover_item["url"], "hash": remote_cover_item["hash"]})
)
score_list.append(Score(score=score, url=remote_cover_item["url"], hash=remote_cover_item["hash"]))
if use_log:
self.log_msg(score, False)

View File

@ -132,18 +132,18 @@ class IssueSelectionWindow(QtWidgets.QDialog):
if len(parts) > 1:
item_text = parts[0] + "-" + parts[1]
QTW_item = QtWidgets.QTableWidgetItem(item_text)
QTW_item.setData(QtCore.Qt.ItemDataRole.ToolTipRole, item_text)
QTW_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled)
self.twList.setItem(row, 1, QTW_item)
qtw_item = QtWidgets.QTableWidgetItem(item_text)
qtw_item.setData(QtCore.Qt.ItemDataRole.ToolTipRole, item_text)
qtw_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled)
self.twList.setItem(row, 1, qtw_item)
item_text = record["name"]
if item_text is None:
item_text = ""
QTW_item = QtWidgets.QTableWidgetItem(item_text)
QTW_item.setData(QtCore.Qt.ItemDataRole.ToolTipRole, item_text)
QTW_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled)
self.twList.setItem(row, 2, QTW_item)
qtw_item = QtWidgets.QTableWidgetItem(item_text)
qtw_item.setData(QtCore.Qt.ItemDataRole.ToolTipRole, item_text)
qtw_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled)
self.twList.setItem(row, 2, qtw_item)
if (
IssueString(record["issue_number"]).as_string().casefold()

View File

@ -92,7 +92,7 @@ try:
except ImportError as e:
def show_exception_box(log_msg: str) -> None:
pass
...
logger.error(str(e))
qt_available = False
@ -115,7 +115,7 @@ def update_publishers() -> None:
def ctmain() -> None:
opts = parse_cmd_line()
SETTINGS = ComicTaggerSettings(opts.config_path)
settings = ComicTaggerSettings(opts.config_path)
os.makedirs(ComicTaggerSettings.get_settings_folder() / "logs", exist_ok=True)
stream_handler = logging.StreamHandler()
@ -138,15 +138,15 @@ def ctmain() -> None:
# manage the CV API key
# None comparison is used so that the empty string can unset the value
if opts.cv_api_key is not None or opts.cv_url is not None:
SETTINGS.cv_api_key = opts.cv_api_key if opts.cv_api_key is not None else SETTINGS.cv_api_key
SETTINGS.cv_url = opts.cv_url if opts.cv_url is not None else SETTINGS.cv_url
SETTINGS.save()
settings.cv_api_key = opts.cv_api_key if opts.cv_api_key is not None else settings.cv_api_key
settings.cv_url = opts.cv_url if opts.cv_url is not None else settings.cv_url
settings.save()
if opts.only_set_cv_key:
print("Key set") # noqa: T201
return
ComicVineTalker.api_key = SETTINGS.cv_api_key
ComicVineTalker.api_base_url = SETTINGS.cv_url
ComicVineTalker.api_key = settings.cv_api_key
ComicVineTalker.api_base_url = settings.cv_url
signal.signal(signal.SIGINT, signal.SIG_DFL)
@ -166,11 +166,11 @@ def ctmain() -> None:
if not qt_available and not opts.no_gui:
opts.no_gui = True
logger.warn("PyQt5 is not available. ComicTagger is limited to command-line mode.")
logger.warning("PyQt5 is not available. ComicTagger is limited to command-line mode.")
if opts.no_gui:
try:
cli.cli_mode(opts, SETTINGS)
cli.cli_mode(opts, settings)
except Exception:
logger.exception("CLI mode failed")
else:
@ -206,7 +206,7 @@ def ctmain() -> None:
QtWidgets.QApplication.processEvents()
try:
tagger_window = TaggerWindow(opts.files, SETTINGS, opts=opts)
tagger_window = TaggerWindow(opts.files, settings, opts=opts)
tagger_window.setWindowIcon(QtGui.QIcon(ComicTaggerSettings.get_graphic("app.png")))
tagger_window.show()

View File

@ -119,10 +119,10 @@ class PageListEditor(QtWidgets.QWidget):
if show_shortcut:
text = text + " (" + shortcut + ")"
self.cbPageType.addItem(text, user_data)
actionItem = QtWidgets.QAction(shortcut, self)
actionItem.triggered.connect(lambda: self.select_page_type_item(self.cbPageType.findData(user_data)))
actionItem.setShortcut(shortcut)
self.addAction(actionItem)
action_item = QtWidgets.QAction(shortcut, self)
action_item.triggered.connect(lambda: self.select_page_type_item(self.cbPageType.findData(user_data)))
action_item.setShortcut(shortcut)
self.addAction(action_item)
def select_page_type_item(self, idx: int) -> None:
if self.cbPageType.isEnabled():
@ -132,19 +132,19 @@ class PageListEditor(QtWidgets.QWidget):
def get_new_indexes(self, movement: int) -> list[tuple[int, int]]:
selection = self.listWidget.selectionModel().selectedRows()
selection.sort(reverse=movement > 0)
newindexes: list[int] = []
oldindexes: list[int] = []
new_indexes: list[int] = []
old_indexes: list[int] = []
for x in selection:
current = x.row()
oldindexes.append(current)
old_indexes.append(current)
if 0 <= current + movement <= self.listWidget.count() - 1:
if len(newindexes) < 1 or current + movement != newindexes[-1]:
if len(new_indexes) < 1 or current + movement != new_indexes[-1]:
current += movement
newindexes.append(current)
oldindexes.sort()
newindexes.sort()
return list(zip(newindexes, oldindexes))
new_indexes.append(current)
old_indexes.sort()
new_indexes.sort()
return list(zip(new_indexes, old_indexes))
def set_selection(self, indexes: list[tuple[int, int]]) -> list[tuple[int, int]]:
selection_ranges: list[tuple[int, int]] = []

View File

@ -106,7 +106,7 @@ Accepts the following variables:
{cover artist} (string)
{editor} (string)
{tags} (list of str)
{pages} (list of dict({'Image': string(int), 'Type': string, 'Bookmark': string, 'DoublePage': string}))
{pages} (list of dict({'Image': string(int), 'Type': string, 'Bookmark': string, 'DoublePage': boolean}))
CoMet-only items:
{price} (float)

View File

@ -1014,7 +1014,7 @@ Have fun!
self.query_online(autoselect=True)
def literal_search(self):
def literal_search(self) -> None:
self.query_online(autoselect=False, literal=True)
def query_online(self, autoselect: bool = False, literal: bool = False) -> None:
@ -1155,7 +1155,6 @@ Have fun!
if self.save_data_style == MetaDataStyle.CIX:
# loop over credit table, mark selected rows
r = 0
for r in range(self.twCredits.rowCount()):
if str(self.twCredits.item(r, 1).text()).casefold() not in cix_credits:
self.twCredits.item(r, 1).setBackground(inactive_brush)
@ -1166,7 +1165,6 @@ Have fun!
if self.save_data_style == MetaDataStyle.CBI:
# loop over credit table, make all active color
r = 0
for r in range(self.twCredits.rowCount()):
self.twCredits.item(r, 0).setBackground(active_brush)
self.twCredits.item(r, 1).setBackground(active_brush)
@ -1350,16 +1348,11 @@ Have fun!
def open_web_link(self) -> None:
if self.leWebLink is not None:
web_link = self.leWebLink.text().strip()
valid = False
try:
result = urlparse(web_link)
valid = all([result.scheme in ["http", "https"], result.netloc])
except ValueError:
pass
if valid:
all([result.scheme in ["http", "https"], result.netloc])
webbrowser.open_new_tab(web_link)
else:
except ValueError:
QtWidgets.QMessageBox.warning(self, self.tr("Web Link"), self.tr("Web Link is invalid."))
def show_settings(self) -> None:
@ -1367,8 +1360,7 @@ Have fun!
settingswin = SettingsWindow(self, self.settings)
settingswin.setModal(True)
settingswin.exec()
if settingswin.result():
pass
settingswin.result()
def set_app_position(self) -> None:
if self.settings.last_main_window_width != 0:
@ -1938,7 +1930,7 @@ Have fun!
QtWidgets.QMessageBox.information(self, self.tr("Auto-Tag Summary"), self.tr(summary))
logger.info(summary)
def exception(self, message):
def exception(self, message: str) -> None:
errorbox = QtWidgets.QMessageBox()
errorbox.setText(message)
errorbox.exec()

View File

@ -106,7 +106,7 @@ tr:nth-child(even) {
&lt;tr&gt;&lt;td&gt;{cover artist}&lt;/td&gt;&lt;td&gt;(string)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;{editor}&lt;/td&gt;&lt;td&gt;(string)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;{tags}&lt;/td&gt;&lt;td&gt;list of str&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;{pages}&lt;/td&gt;&lt;td&gt;list of dict({&apos;Image&apos;: string(int), &apos;Type&apos;: string, &apos;Bookmark&apos;: string, &apos;DoublePage&apos;: string})&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;{pages}&lt;/td&gt;&lt;td&gt;list of dict({&apos;Image&apos;: string(int), &apos;Type&apos;: string, &apos;Bookmark&apos;: string, &apos;DoublePage&apos;: boolean})&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;{price}&lt;/td&gt;&lt;td&gt;float&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;{is_version_of}&lt;/td&gt;&lt;td&gt;string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;{rights}&lt;/td&gt;&lt;td&gt;string&lt;/td&gt;&lt;/tr&gt;

View File

@ -394,7 +394,7 @@ class VolumeSelectionWindow(QtWidgets.QDialog):
deques: list[deque[CVVolumeResults]] = [deque(), deque(), deque()]
def categorize(result):
def categorize(result: CVVolumeResults) -> int:
# We don't remove anything on this one so that we only get exact matches
if utils.sanitize_title(result["name"], True).casefold() == sanitized_no_articles:
return 0
@ -454,7 +454,7 @@ class VolumeSelectionWindow(QtWidgets.QDialog):
self.twList.selectRow(0)
self.twList.resizeColumnsToContents()
def showEvent(self, event: QtGui.QShowEvent):
def showEvent(self, event: QtGui.QShowEvent) -> None:
if not self.cv_search_results:
QtCore.QCoreApplication.processEvents()
QtWidgets.QMessageBox.information(self, "Search Result", "No matches found!")