diff --git a/comicapi/utils.py b/comicapi/utils.py
index 497e336..a532a00 100644
--- a/comicapi/utils.py
+++ b/comicapi/utils.py
@@ -165,7 +165,7 @@ def sanitize_title(text: str, basic: bool = False) -> str:
def titles_match(search_title: str, record_title: str, threshold: int = 90) -> bool:
sanitized_search = sanitize_title(search_title)
sanitized_record = sanitize_title(record_title)
- ratio: int = rapidfuzz.fuzz.ratio(sanitized_search, sanitized_record)
+ ratio = int(rapidfuzz.fuzz.ratio(sanitized_search, sanitized_record))
logger.debug(
"search title: %s ; record title: %s ; ratio: %d ; match threshold: %d",
search_title,
diff --git a/comictaggerlib/applicationlogwindow.py b/comictaggerlib/applicationlogwindow.py
index 7b04939..afece95 100644
--- a/comictaggerlib/applicationlogwindow.py
+++ b/comictaggerlib/applicationlogwindow.py
@@ -23,7 +23,7 @@ class QTextEditLogger(QtCore.QObject, logging.Handler):
class ApplicationLogWindow(QtWidgets.QDialog):
- def __init__(self, log_handler: QTextEditLogger, parent: QtCore.QObject = None) -> None:
+ def __init__(self, log_handler: QTextEditLogger, parent: QtCore.QObject | None = None) -> None:
super().__init__(parent)
uic.loadUi(ui_path / "logwindow.ui", self)
diff --git a/comictaggerlib/autotagmatchwindow.py b/comictaggerlib/autotagmatchwindow.py
index 29cd789..ca0abfb 100644
--- a/comictaggerlib/autotagmatchwindow.py
+++ b/comictaggerlib/autotagmatchwindow.py
@@ -28,7 +28,7 @@ from comictaggerlib.coverimagewidget import CoverImageWidget
from comictaggerlib.resulttypes import IssueResult, MultipleMatch
from comictaggerlib.ui import ui_path
from comictaggerlib.ui.qtutils import reduce_widget_font_size
-from comictalker.talkerbase import ComicTalker
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
@@ -41,7 +41,7 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
style: int,
fetch_func: Callable[[IssueResult], GenericMetadata],
config: settngs.Namespace,
- talker_api: ComicTalker,
+ talker: ComicTalker,
) -> None:
super().__init__(parent)
@@ -52,7 +52,7 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
self.current_match_set: MultipleMatch = match_set_list[0]
self.altCoverWidget = CoverImageWidget(
- self.altCoverContainer, CoverImageWidget.AltCoverMode, config.runtime_config.user_cache_dir, talker_api
+ self.altCoverContainer, CoverImageWidget.AltCoverMode, config.runtime_config.user_cache_dir, talker
)
gridlayout = QtWidgets.QGridLayout(self.altCoverContainer)
gridlayout.addWidget(self.altCoverWidget)
diff --git a/comictaggerlib/autotagprogresswindow.py b/comictaggerlib/autotagprogresswindow.py
index b8362d1..f2486bb 100644
--- a/comictaggerlib/autotagprogresswindow.py
+++ b/comictaggerlib/autotagprogresswindow.py
@@ -22,13 +22,13 @@ from PyQt5 import QtCore, QtWidgets, uic
from comictaggerlib.coverimagewidget import CoverImageWidget
from comictaggerlib.ui import ui_path
from comictaggerlib.ui.qtutils import reduce_widget_font_size
-from comictalker.talkerbase import ComicTalker
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
class AutoTagProgressWindow(QtWidgets.QDialog):
- def __init__(self, parent: QtWidgets.QWidget, talker_api: ComicTalker) -> None:
+ def __init__(self, parent: QtWidgets.QWidget, talker: ComicTalker) -> None:
super().__init__(parent)
uic.loadUi(ui_path / "autotagprogresswindow.ui", self)
diff --git a/comictaggerlib/autotagstartwindow.py b/comictaggerlib/autotagstartwindow.py
index 5a20530..7e7db5d 100644
--- a/comictaggerlib/autotagstartwindow.py
+++ b/comictaggerlib/autotagstartwindow.py
@@ -49,7 +49,7 @@ class AutoTagStartWindow(QtWidgets.QDialog):
self.cbxIgnoreLeadingDigitsInFilename.setChecked(self.config.autotag_ignore_leading_numbers_in_filename)
self.cbxRemoveAfterSuccess.setChecked(self.config.autotag_remove_archive_after_successful_match)
self.cbxWaitForRateLimit.setChecked(self.config.autotag_wait_and_retry_on_rate_limit)
- self.cbxAutoImprint.setChecked(self.config.talkers_auto_imprint)
+ self.cbxAutoImprint.setChecked(self.config.talker_auto_imprint)
nlmt_tip = """The Name Match Ratio Threshold: Auto-Identify is for eliminating automatic
search matches that are too long compared to your series name search. The lower
@@ -75,7 +75,7 @@ class AutoTagStartWindow(QtWidgets.QDialog):
self.remove_after_success = False
self.wait_and_retry_on_rate_limit = False
self.search_string = ""
- self.name_length_match_tolerance = self.config.talkers_series_match_search_thresh
+ self.name_length_match_tolerance = self.config.talker_series_match_search_thresh
self.split_words = self.cbxSplitWords.isChecked()
def search_string_toggle(self) -> None:
diff --git a/comictaggerlib/cli.py b/comictaggerlib/cli.py
index bd49c52..86fd9a3 100644
--- a/comictaggerlib/cli.py
+++ b/comictaggerlib/cli.py
@@ -34,21 +34,27 @@ from comictaggerlib.filerenamer import FileRenamer, get_rename_dir
from comictaggerlib.graphics import graphics_path
from comictaggerlib.issueidentifier import IssueIdentifier
from comictaggerlib.resulttypes import MultipleMatch, OnlineMatchResults
-from comictalker.talkerbase import ComicTalker, TalkerError
+from comictalker.comictalker import ComicTalker, TalkerError
logger = logging.getLogger(__name__)
class CLI:
- def __init__(self, config: settngs.Values, talker_api: ComicTalker):
+ def __init__(self, config: settngs.Namespace, talkers: dict[str, ComicTalker]):
self.config = config
- self.talker_api = talker_api
+ self.talkers = talkers
self.batch_mode = False
+ def current_talker(self) -> ComicTalker:
+ if self.config[0].talker_source in self.talkers:
+ return self.talkers[self.config[0].talker_source]
+ logger.error("Could not find the '%s' talker", self.config[0].talker_source)
+ raise SystemExit(2)
+
def actual_issue_data_fetch(self, issue_id: str) -> GenericMetadata:
# now get the particular issue data
try:
- ct_md = self.talker_api.fetch_comic_data(issue_id)
+ ct_md = self.current_talker().fetch_comic_data(issue_id)
except TalkerError as e:
logger.exception(f"Error retrieving issue details. Save aborted.\n{e}")
return GenericMetadata()
@@ -108,7 +114,7 @@ class CLI:
ca = match_set.ca
md = self.create_local_metadata(ca)
ct_md = self.actual_issue_data_fetch(match_set.matches[int(i) - 1]["issue_id"])
- if self.config.talkers_clear_metadata_on_import:
+ if self.config.talker_clear_metadata_on_import:
md = ct_md
else:
notes = (
@@ -117,7 +123,7 @@ class CLI:
)
md.overlay(ct_md.replace(notes=utils.combine_notes(md.notes, notes, "Tagged with ComicTagger")))
- if self.config.talkers_auto_imprint:
+ if self.config.talker_auto_imprint:
md.fix_publisher()
self.actual_metadata_save(ca, md)
@@ -342,7 +348,7 @@ class CLI:
if self.config.runtime_issue_id is not None:
# we were given the actual issue ID to search with
try:
- ct_md = self.talker_api.fetch_comic_data(self.config.runtime_issue_id)
+ ct_md = self.current_talker().fetch_comic_data(self.config.runtime_issue_id)
except TalkerError as e:
logger.exception(f"Error retrieving issue details. Save aborted.\n{e}")
match_results.fetch_data_failures.append(str(ca.path.absolute()))
@@ -361,7 +367,7 @@ class CLI:
match_results.no_matches.append(str(ca.path.absolute()))
return
- ii = IssueIdentifier(ca, self.config, self.talker_api)
+ ii = IssueIdentifier(ca, self.config, self.current_talker())
def myoutput(text: str) -> None:
if self.config.runtime_verbose:
@@ -421,7 +427,7 @@ class CLI:
match_results.fetch_data_failures.append(str(ca.path.absolute()))
return
- if self.config.talkers_clear_metadata_on_import:
+ if self.config.talker_clear_metadata_on_import:
md = ct_md
else:
notes = (
@@ -430,7 +436,7 @@ class CLI:
)
md.overlay(ct_md.replace(notes=utils.combine_notes(md.notes, notes, "Tagged with ComicTagger")))
- if self.config.talkers_auto_imprint:
+ if self.config.talker_auto_imprint:
md.fix_publisher()
# ok, done building our metadata. time to save
diff --git a/comictaggerlib/coverimagewidget.py b/comictaggerlib/coverimagewidget.py
index 15fc459..62bdb9f 100644
--- a/comictaggerlib/coverimagewidget.py
+++ b/comictaggerlib/coverimagewidget.py
@@ -31,7 +31,7 @@ from comictaggerlib.imagepopup import ImagePopup
from comictaggerlib.pageloader import PageLoader
from comictaggerlib.ui import ui_path
from comictaggerlib.ui.qtutils import get_qimage_from_data, reduce_widget_font_size
-from comictalker.talkerbase import ComicTalker
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
@@ -68,17 +68,17 @@ class CoverImageWidget(QtWidgets.QWidget):
parent: QtWidgets.QWidget,
mode: int,
cache_folder: pathlib.Path | None,
- talker_api: ComicTalker | None,
+ talker: ComicTalker | None,
expand_on_click: bool = True,
) -> None:
super().__init__(parent)
if mode not in (self.AltCoverMode, self.URLMode) or cache_folder is None:
self.cover_fetcher = None
- self.talker_api = None
+ self.talker = None
else:
self.cover_fetcher = ImageFetcher(cache_folder)
- self.talker_api = None
+ self.talker = None
uic.loadUi(ui_path / "coverimagewidget.ui", self)
reduce_widget_font_size(self.label)
diff --git a/comictaggerlib/ctsettings/__init__.py b/comictaggerlib/ctsettings/__init__.py
index a186759..88fc1b3 100644
--- a/comictaggerlib/ctsettings/__init__.py
+++ b/comictaggerlib/ctsettings/__init__.py
@@ -8,7 +8,7 @@ from comictaggerlib.ctsettings.commandline import (
from comictaggerlib.ctsettings.file import register_file_settings, validate_file_settings
from comictaggerlib.ctsettings.plugin import register_plugin_settings, validate_plugin_settings
from comictaggerlib.ctsettings.types import ComicTaggerPaths
-from comictalker.talkerbase import ComicTalker
+from comictalker import ComicTalker
talkers: dict[str, ComicTalker] = {}
diff --git a/comictaggerlib/ctsettings/commandline.py b/comictaggerlib/ctsettings/commandline.py
index ccb3956..0bcd505 100644
--- a/comictaggerlib/ctsettings/commandline.py
+++ b/comictaggerlib/ctsettings/commandline.py
@@ -267,8 +267,9 @@ def register_commandline_settings(parser: settngs.Manager) -> None:
parser.add_group("runtime", register_settings)
-def validate_commandline_settings(config: settngs.Config[settngs.Values], parser: settngs.Manager) -> settngs.Values:
-
+def validate_commandline_settings(
+ config: settngs.Config[settngs.Namespace], parser: settngs.Manager
+) -> settngs.Config[settngs.Namespace]:
if config[0].commands_version:
parser.exit(
status=1,
diff --git a/comictaggerlib/ctsettings/file.py b/comictaggerlib/ctsettings/file.py
index c9cb4ff..577c079 100644
--- a/comictaggerlib/ctsettings/file.py
+++ b/comictaggerlib/ctsettings/file.py
@@ -2,7 +2,6 @@ from __future__ import annotations
import argparse
import uuid
-from typing import Any
import settngs
@@ -84,8 +83,8 @@ def filename(parser: settngs.Manager) -> None:
)
-def talkers(parser: settngs.Manager) -> None:
- # General settings for all information talkers
+def talker(parser: settngs.Manager) -> None:
+ # General settings for talkers
parser.add_setting("--source", default="comicvine", help="Use a specified source by source ID")
parser.add_setting(
"--series-match-search-thresh",
@@ -225,7 +224,7 @@ def autotag(parser: settngs.Manager) -> None:
)
-def validate_file_settings(config: settngs.Config[settngs.Values]) -> dict[str, dict[str, Any]]:
+def validate_file_settings(config: settngs.Config[settngs.Namespace]) -> settngs.Config[settngs.Namespace]:
config[0].identifier_publisher_filter = [x.strip() for x in config[0].identifier_publisher_filter if x.strip()]
config[0].rename_replacements = Replacements(
[Replacement(x[0], x[1], x[2]) for x in config[0].rename_replacements[0]],
@@ -240,7 +239,7 @@ def register_file_settings(parser: settngs.Manager) -> None:
parser.add_group("identifier", identifier, False)
parser.add_group("dialog", dialog, False)
parser.add_group("filename", filename, False)
- parser.add_group("talkers", talkers, False)
+ parser.add_group("talker", talker, False)
parser.add_group("cbl", cbl, False)
parser.add_group("rename", rename, False)
parser.add_group("autotag", autotag, False)
diff --git a/comictaggerlib/gui.py b/comictaggerlib/gui.py
index 1e7dbb8..6f41e88 100644
--- a/comictaggerlib/gui.py
+++ b/comictaggerlib/gui.py
@@ -10,7 +10,7 @@ import types
import settngs
from comictaggerlib.graphics import graphics_path
-from comictalker.talkerbase import ComicTalker
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger("comictagger")
try:
@@ -83,7 +83,7 @@ except ImportError as e:
def open_tagger_window(
- talker_api: ComicTalker | None, config: settngs.Config[settngs.Namespace], error: tuple[str, bool] | None
+ talkers: dict[str, ComicTalker], config: settngs.Config[settngs.Namespace], error: tuple[str, bool] | None
) -> None:
os.environ["QtWidgets.QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
args = []
@@ -96,8 +96,6 @@ def open_tagger_window(
if error[1]:
raise SystemExit(1)
- assert talker_api is not None
-
# needed to catch initial open file events (macOS)
app.openFileRequest.connect(lambda x: config[0].runtime_files.append(x.toLocalFile()))
@@ -127,7 +125,7 @@ def open_tagger_window(
QtWidgets.QApplication.processEvents()
try:
- tagger_window = TaggerWindow(config[0].runtime_files, config, talker_api)
+ tagger_window = TaggerWindow(config[0].runtime_files, config, talkers)
tagger_window.setWindowIcon(QtGui.QIcon(str(graphics_path / "app.png")))
tagger_window.show()
diff --git a/comictaggerlib/issueidentifier.py b/comictaggerlib/issueidentifier.py
index 66c9155..0ac4c4f 100644
--- a/comictaggerlib/issueidentifier.py
+++ b/comictaggerlib/issueidentifier.py
@@ -30,7 +30,7 @@ from comicapi.issuestring import IssueString
from comictaggerlib.imagefetcher import ImageFetcher, ImageFetcherException
from comictaggerlib.imagehasher import ImageHasher
from comictaggerlib.resulttypes import IssueResult
-from comictalker.talkerbase import ComicTalker, TalkerError
+from comictalker.comictalker import ComicTalker, TalkerError
logger = logging.getLogger(__name__)
@@ -72,9 +72,9 @@ class IssueIdentifier:
result_one_good_match = 4
result_multiple_good_matches = 5
- def __init__(self, comic_archive: ComicArchive, config: settngs.Namespace, talker_api: ComicTalker) -> None:
+ def __init__(self, comic_archive: ComicArchive, config: settngs.Namespace, talker: ComicTalker) -> None:
self.config = config
- self.talker_api = talker_api
+ self.talker = talker
self.comic_archive: ComicArchive = comic_archive
self.image_hasher = 1
@@ -188,8 +188,8 @@ class IssueIdentifier:
height = bbox[3] - bbox[1]
# Convert to percent
- width_percent = 100 - ((width / im.width) * 100)
- height_percent = 100 - ((height / im.height) * 100)
+ width_percent = int(100 - ((width / im.width) * 100))
+ height_percent = int(100 - ((height / im.height) * 100))
logger.debug(
"Width: %s Height: %s, ratio: %s %s ratio met: %s",
im.width,
@@ -411,7 +411,7 @@ class IssueIdentifier:
self.log_msg(f"Searching for {keys['series']} #{keys['issue_number']} ...")
try:
- ct_search_results = self.talker_api.search_for_series(keys["series"])
+ ct_search_results = self.talker.search_for_series(keys["series"])
except TalkerError as e:
self.log_msg(f"Error searching for series.\n{e}")
return []
@@ -460,7 +460,7 @@ class IssueIdentifier:
issue_list = None
try:
if len(series_by_id) > 0:
- issue_list = self.talker_api.fetch_issues_by_series_issue_num_and_year(
+ issue_list = self.talker.fetch_issues_by_series_issue_num_and_year(
list(series_by_id.keys()), keys["issue_number"], keys["year"]
)
except TalkerError as e:
diff --git a/comictaggerlib/issueselectionwindow.py b/comictaggerlib/issueselectionwindow.py
index 04cb51e..c1a32e1 100644
--- a/comictaggerlib/issueselectionwindow.py
+++ b/comictaggerlib/issueselectionwindow.py
@@ -24,8 +24,8 @@ from comicapi.issuestring import IssueString
from comictaggerlib.coverimagewidget import CoverImageWidget
from comictaggerlib.ui import ui_path
from comictaggerlib.ui.qtutils import reduce_widget_font_size
+from comictalker.comictalker import ComicTalker, TalkerError
from comictalker.resulttypes import ComicIssue
-from comictalker.talkerbase import ComicTalker, TalkerError
logger = logging.getLogger(__name__)
@@ -43,7 +43,7 @@ class IssueSelectionWindow(QtWidgets.QDialog):
self,
parent: QtWidgets.QWidget,
config: settngs.Namespace,
- talker_api: ComicTalker,
+ talker: ComicTalker,
series_id: str,
issue_number: str,
) -> None:
@@ -52,7 +52,7 @@ class IssueSelectionWindow(QtWidgets.QDialog):
uic.loadUi(ui_path / "issueselectionwindow.ui", self)
self.coverWidget = CoverImageWidget(
- self.coverImageContainer, CoverImageWidget.AltCoverMode, config.runtime_config.user_cache_dir, talker_api
+ self.coverImageContainer, CoverImageWidget.AltCoverMode, config.runtime_config.user_cache_dir, talker
)
gridlayout = QtWidgets.QGridLayout(self.coverImageContainer)
gridlayout.addWidget(self.coverWidget)
@@ -72,24 +72,24 @@ class IssueSelectionWindow(QtWidgets.QDialog):
self.series_id = series_id
self.issue_id: str = ""
self.config = config
- self.talker_api = talker_api
+ self.talker = talker
self.url_fetch_thread = None
self.issue_list: list[ComicIssue] = []
# Display talker logo and set url
- self.lblIssuesSourceName.setText(talker_api.static_config.attribution_string)
+ self.lblIssuesSourceName.setText(talker.attribution)
self.imageIssuesSourceWidget = CoverImageWidget(
self.imageIssuesSourceLogo,
CoverImageWidget.URLMode,
config.runtime_config.user_cache_dir,
- talker_api,
+ talker,
False,
)
gridlayoutIssuesSourceLogo = QtWidgets.QGridLayout(self.imageIssuesSourceLogo)
gridlayoutIssuesSourceLogo.addWidget(self.imageIssuesSourceWidget)
gridlayoutIssuesSourceLogo.setContentsMargins(0, 2, 0, 0)
- self.imageIssuesSourceWidget.set_url(talker_api.source_details.logo)
+ self.imageIssuesSourceWidget.set_url(talker.logo_url)
if issue_number is None or issue_number == "":
self.issue_number = "1"
@@ -119,7 +119,7 @@ class IssueSelectionWindow(QtWidgets.QDialog):
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
try:
- self.issue_list = self.talker_api.fetch_issues_by_series(self.series_id)
+ self.issue_list = self.talker.fetch_issues_by_series(self.series_id)
except TalkerError as e:
QtWidgets.QApplication.restoreOverrideCursor()
QtWidgets.QMessageBox.critical(self, f"{e.source} {e.code_name} Error", f"{e}")
diff --git a/comictaggerlib/main.py b/comictaggerlib/main.py
index 2a92774..3e35a1d 100644
--- a/comictaggerlib/main.py
+++ b/comictaggerlib/main.py
@@ -24,7 +24,7 @@ import sys
import settngs
import comicapi
-import comictalker.comictalkerapi as ct_api
+import comictalker
from comictaggerlib import cli, ctsettings
from comictaggerlib.ctversion import version
from comictaggerlib.log import setup_logging
@@ -34,21 +34,22 @@ if sys.version_info < (3, 10):
else:
import importlib.metadata as importlib_metadata
+logger = logging.getLogger("comictagger")
+
try:
from comictaggerlib import gui
qt_available = gui.qt_available
except Exception:
+ logger.exception("Qt unavailable")
qt_available = False
-logger = logging.getLogger("comictagger")
-
logger.setLevel(logging.DEBUG)
-def update_publishers(config: settngs.Namespace) -> None:
- json_file = config.runtime_config.user_config_dir / "publishers.json"
+def update_publishers(config: settngs.Config[settngs.Namespace]) -> None:
+ json_file = config[0].runtime_config.user_config_dir / "publishers.json"
if json_file.exists():
try:
comicapi.utils.update_publishers(json.loads(json_file.read_text("utf-8")))
@@ -60,7 +61,7 @@ class App:
"""docstring for App"""
def __init__(self) -> None:
- self.config = settngs.Config({}, {})
+ self.config: settngs.Config[settngs.Namespace]
self.initial_arg_parser = ctsettings.initial_commandline_parser()
self.config_load_success = False
@@ -73,9 +74,9 @@ class App:
self.main()
- def load_plugins(self, opts) -> None:
+ def load_plugins(self, opts: argparse.Namespace) -> None:
comicapi.comicarchive.load_archive_plugins()
- ctsettings.talkers = ct_api.get_talkers(version, opts.config.user_cache_dir)
+ ctsettings.talkers = comictalker.get_talkers(version, opts.config.user_cache_dir)
def initialize(self) -> argparse.Namespace:
conf, _ = self.initial_arg_parser.parse_known_args()
@@ -92,9 +93,9 @@ class App:
ctsettings.register_file_settings(self.manager)
ctsettings.register_plugin_settings(self.manager)
- def parse_settings(self, config_paths: ctsettings.ComicTaggerPaths) -> settngs.Config:
- config, self.config_load_success = self.manager.parse_config(config_paths.user_config_dir / "settings.json")
- config = self.manager.get_namespace(config)
+ def parse_settings(self, config_paths: ctsettings.ComicTaggerPaths) -> settngs.Config[settngs.Namespace]:
+ cfg, self.config_load_success = self.manager.parse_config(config_paths.user_config_dir / "settings.json")
+ config = self.manager.get_namespace(cfg)
config = ctsettings.validate_commandline_settings(config, self.manager)
config = ctsettings.validate_file_settings(config)
@@ -125,7 +126,7 @@ class App:
logger.debug("%s\t%s", pkg.metadata["Name"], pkg.metadata["Version"])
comicapi.utils.load_publishers()
- update_publishers(self.config[0])
+ update_publishers(self.config)
if not qt_available and not self.config[0].runtime_no_gui:
self.config[0].runtime_no_gui = True
@@ -143,31 +144,26 @@ class App:
print("Key set") # noqa: T201
return
- try:
- talker_api = ctsettings.talkers[self.config[0].talkers_source]
- except Exception as e:
- logger.exception(f"Unable to load talker {self.config[0].talkers_source}")
- error = (f"Unable to load talker {self.config[0].talkers_source}: {e}", True)
- talker_api = None
-
if not self.config_load_success:
error = (
f"Failed to load settings, check the log located in '{self.config[0].runtime_config.user_log_dir}' for more details",
True,
)
+ talkers = ctsettings.talkers
+ del ctsettings.talkers
+
if self.config[0].runtime_no_gui:
if error and error[1]:
print(f"A fatal error occurred please check the log for more information: {error[0]}") # noqa: T201
raise SystemExit(1)
- assert talker_api is not None
try:
- cli.CLI(self.config[0], talker_api).run()
+ cli.CLI(self.config[0], talkers).run()
except Exception:
logger.exception("CLI mode failed")
else:
- gui.open_tagger_window(talker_api, self.config, error)
+ gui.open_tagger_window(talkers, self.config, error)
-def main():
+def main() -> None:
App().run()
diff --git a/comictaggerlib/matchselectionwindow.py b/comictaggerlib/matchselectionwindow.py
index ed8864d..c942404 100644
--- a/comictaggerlib/matchselectionwindow.py
+++ b/comictaggerlib/matchselectionwindow.py
@@ -26,7 +26,7 @@ from comictaggerlib.coverimagewidget import CoverImageWidget
from comictaggerlib.resulttypes import IssueResult
from comictaggerlib.ui import ui_path
from comictaggerlib.ui.qtutils import reduce_widget_font_size
-from comictalker.talkerbase import ComicTalker
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
@@ -37,15 +37,15 @@ class MatchSelectionWindow(QtWidgets.QDialog):
parent: QtWidgets.QWidget,
matches: list[IssueResult],
comic_archive: ComicArchive,
- config: settngs.Values,
- talker_api: ComicTalker,
+ config: settngs.Namespace,
+ talker: ComicTalker,
) -> None:
super().__init__(parent)
uic.loadUi(ui_path / "matchselectionwindow.ui", self)
self.altCoverWidget = CoverImageWidget(
- self.altCoverContainer, CoverImageWidget.AltCoverMode, config.runtime_config.user_cache_dir, talker_api
+ self.altCoverContainer, CoverImageWidget.AltCoverMode, config.runtime_config.user_cache_dir, talker
)
gridlayout = QtWidgets.QGridLayout(self.altCoverContainer)
gridlayout.addWidget(self.altCoverWidget)
diff --git a/comictaggerlib/pagebrowser.py b/comictaggerlib/pagebrowser.py
index 79e7968..dc923f7 100644
--- a/comictaggerlib/pagebrowser.py
+++ b/comictaggerlib/pagebrowser.py
@@ -25,13 +25,12 @@ from comicapi.genericmetadata import GenericMetadata
from comictaggerlib.coverimagewidget import CoverImageWidget
from comictaggerlib.graphics import graphics_path
from comictaggerlib.ui import ui_path
-from comictalker.talkerbase import ComicTalker
logger = logging.getLogger(__name__)
class PageBrowserWindow(QtWidgets.QDialog):
- def __init__(self, parent: QtWidgets.QWidget, talker_api: ComicTalker, metadata: GenericMetadata) -> None:
+ def __init__(self, parent: QtWidgets.QWidget, metadata: GenericMetadata) -> None:
super().__init__(parent)
uic.loadUi(ui_path / "pagebrowser.ui", self)
diff --git a/comictaggerlib/pagelisteditor.py b/comictaggerlib/pagelisteditor.py
index 136cb39..56c7171 100644
--- a/comictaggerlib/pagelisteditor.py
+++ b/comictaggerlib/pagelisteditor.py
@@ -23,7 +23,6 @@ from comicapi.comicarchive import ComicArchive, MetaDataStyle
from comicapi.genericmetadata import ImageMetadata, PageType
from comictaggerlib.coverimagewidget import CoverImageWidget
from comictaggerlib.ui import ui_path
-from comictalker.talkerbase import ComicTalker
logger = logging.getLogger(__name__)
@@ -68,7 +67,7 @@ class PageListEditor(QtWidgets.QWidget):
PageType.Deleted: "Deleted",
}
- def __init__(self, parent: QtWidgets.QWidget, talker_api: ComicTalker) -> None:
+ def __init__(self, parent: QtWidgets.QWidget) -> None:
super().__init__(parent)
uic.loadUi(ui_path / "pagelisteditor.ui", self)
diff --git a/comictaggerlib/renamewindow.py b/comictaggerlib/renamewindow.py
index 5bd643d..81b2f68 100644
--- a/comictaggerlib/renamewindow.py
+++ b/comictaggerlib/renamewindow.py
@@ -27,7 +27,7 @@ from comictaggerlib.filerenamer import FileRenamer, get_rename_dir
from comictaggerlib.settingswindow import SettingsWindow
from comictaggerlib.ui import ui_path
from comictaggerlib.ui.qtutils import center_window_on_parent
-from comictalker.talkerbase import ComicTalker
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
@@ -38,8 +38,8 @@ class RenameWindow(QtWidgets.QDialog):
parent: QtWidgets.QWidget,
comic_archive_list: list[ComicArchive],
data_style: int,
- config: settngs.Config,
- talker_api: ComicTalker,
+ config: settngs.Config[settngs.Namespace],
+ talker: ComicTalker,
) -> None:
super().__init__(parent)
@@ -55,7 +55,7 @@ class RenameWindow(QtWidgets.QDialog):
)
self.config = config
- self.talker_api = talker_api
+ self.talker = talker
self.comic_archive_list = comic_archive_list
self.data_style = data_style
self.rename_list: list[str] = []
@@ -162,7 +162,7 @@ class RenameWindow(QtWidgets.QDialog):
self.twList.setSortingEnabled(True)
def modify_settings(self) -> None:
- settingswin = SettingsWindow(self, self.config, self.talker_api)
+ settingswin = SettingsWindow(self, self.config, self.talker)
settingswin.setModal(True)
settingswin.show_rename_tab()
settingswin.exec()
diff --git a/comictaggerlib/seriesselectionwindow.py b/comictaggerlib/seriesselectionwindow.py
index 96a127a..164364a 100644
--- a/comictaggerlib/seriesselectionwindow.py
+++ b/comictaggerlib/seriesselectionwindow.py
@@ -33,8 +33,8 @@ from comictaggerlib.matchselectionwindow import MatchSelectionWindow
from comictaggerlib.progresswindow import IDProgressWindow
from comictaggerlib.ui import ui_path
from comictaggerlib.ui.qtutils import reduce_widget_font_size
+from comictalker.comictalker import ComicTalker, TalkerError
from comictalker.resulttypes import ComicSeries
-from comictalker.talkerbase import ComicTalker, TalkerError
logger = logging.getLogger(__name__)
@@ -45,14 +45,14 @@ class SearchThread(QtCore.QThread):
def __init__(
self,
- talker_api: ComicTalker,
+ talker: ComicTalker,
series_name: str,
refresh: bool,
literal: bool = False,
series_match_thresh: int = 90,
) -> None:
QtCore.QThread.__init__(self)
- self.talker_api = talker_api
+ self.talker = talker
self.series_name = series_name
self.refresh: bool = refresh
self.error_e: TalkerError
@@ -64,7 +64,7 @@ class SearchThread(QtCore.QThread):
def run(self) -> None:
try:
self.ct_error = False
- self.ct_search_results = self.talker_api.search_for_series(
+ self.ct_search_results = self.talker.search_for_series(
self.series_name, self.prog_callback, self.refresh, self.literal, self.series_match_thresh
)
except TalkerError as e:
@@ -112,7 +112,7 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
cover_index_list: list[int],
comic_archive: ComicArchive | None,
config: settngs.Namespace,
- talker_api: ComicTalker,
+ talker: ComicTalker,
autoselect: bool = False,
literal: bool = False,
) -> None:
@@ -121,7 +121,7 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
uic.loadUi(ui_path / "seriesselectionwindow.ui", self)
self.imageWidget = CoverImageWidget(
- self.imageContainer, CoverImageWidget.URLMode, config.runtime_config.user_cache_dir, talker_api
+ self.imageContainer, CoverImageWidget.URLMode, config.runtime_config.user_cache_dir, talker
)
gridlayout = QtWidgets.QGridLayout(self.imageContainer)
gridlayout.addWidget(self.imageWidget)
@@ -156,25 +156,25 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
self.progdialog: QtWidgets.QProgressDialog | None = None
self.search_thread: SearchThread | None = None
- self.use_filter = self.config.talkers_always_use_publisher_filter
+ self.use_filter = self.config.talker_always_use_publisher_filter
# Load to retrieve settings
- self.talker_api = talker_api
+ self.talker = talker
# Display talker logo and set url
- self.lblSourceName.setText(talker_api.static_config.attribution_string)
+ self.lblSourceName.setText(talker.attribution)
self.imageSourceWidget = CoverImageWidget(
self.imageSourceLogo,
CoverImageWidget.URLMode,
config.runtime_config.user_cache_dir,
- talker_api,
+ talker,
False,
)
gridlayoutSourceLogo = QtWidgets.QGridLayout(self.imageSourceLogo)
gridlayoutSourceLogo.addWidget(self.imageSourceWidget)
gridlayoutSourceLogo.setContentsMargins(0, 2, 0, 0)
- self.imageSourceWidget.set_url(talker_api.source_details.logo)
+ self.imageSourceWidget.set_url(talker.logo_url)
# Set the minimum row height to the default.
# this way rows will be more consistent when resizeRowsToContents is called
@@ -224,7 +224,7 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
self.iddialog.rejected.connect(self.identify_cancel)
self.iddialog.show()
- self.ii = IssueIdentifier(self.comic_archive, self.config, self.talker_api)
+ self.ii = IssueIdentifier(self.comic_archive, self.config, self.talker)
md = GenericMetadata()
md.series = self.series_name
@@ -298,7 +298,7 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
if choices:
selector = MatchSelectionWindow(
- self, matches, self.comic_archive, talker_api=self.talker_api, config=self.config
+ self, matches, self.comic_archive, talker=self.talker, config=self.config
)
selector.setModal(True)
selector.exec()
@@ -315,7 +315,7 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
self.show_issues()
def show_issues(self) -> None:
- selector = IssueSelectionWindow(self, self.config, self.talker_api, self.series_id, self.issue_number)
+ selector = IssueSelectionWindow(self, self.config, self.talker, self.series_id, self.issue_number)
title = ""
for record in self.ct_search_results:
if record.id == self.series_id:
@@ -343,7 +343,7 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
def perform_query(self, refresh: bool = False) -> None:
self.search_thread = SearchThread(
- self.talker_api, self.series_name, refresh, self.literal, self.config.talkers_series_match_search_thresh
+ self.talker, self.series_name, refresh, self.literal, self.config.talker_series_match_search_thresh
)
self.search_thread.searchComplete.connect(self.search_complete)
self.search_thread.progressUpdate.connect(self.search_progress_update)
@@ -410,7 +410,7 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
# compare as str in case extra chars ie. '1976?'
# - missing (none) values being converted to 'None' - consistent with prior behaviour in v1.2.3
# sort by start_year if set
- if self.config.talkers_sort_series_by_year:
+ if self.config.talker_sort_series_by_year:
try:
self.ct_search_results = sorted(
self.ct_search_results,
@@ -428,7 +428,7 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
logger.exception("bad data error sorting results by count_of_issues")
# move sanitized matches to the front
- if self.config.talkers_exact_series_matches_first:
+ if self.config.talker_exact_series_matches_first:
try:
sanitized = utils.sanitize_title(self.series_name, False).casefold()
sanitized_no_articles = utils.sanitize_title(self.series_name, True).casefold()
diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py
index 437c216..13fc626 100644
--- a/comictaggerlib/settingswindow.py
+++ b/comictaggerlib/settingswindow.py
@@ -32,7 +32,7 @@ from comictaggerlib.filerenamer import FileRenamer, Replacement, Replacements
from comictaggerlib.imagefetcher import ImageFetcher
from comictaggerlib.ui import ui_path
from comictalker.comiccacher import ComicCacher
-from comictalker.talkerbase import ComicTalker
+from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
@@ -130,7 +130,9 @@ Spider-Geddon #1 - New Players; Check In
class SettingsWindow(QtWidgets.QDialog):
- def __init__(self, parent: QtWidgets.QWidget, config: settngs.Config, talker_api: ComicTalker) -> None:
+ def __init__(
+ self, parent: QtWidgets.QWidget, config: settngs.Config[settngs.Namespace], talker: ComicTalker
+ ) -> None:
super().__init__(parent)
uic.loadUi(ui_path / "settingswindow.ui", self)
@@ -140,7 +142,7 @@ class SettingsWindow(QtWidgets.QDialog):
)
self.config = config
- self.talker_api = talker_api
+ self.talker = talker
self.name = "Settings"
if platform.system() == "Windows":
@@ -294,12 +296,12 @@ class SettingsWindow(QtWidgets.QDialog):
def settings_to_form(self) -> None:
self.disconnect_signals()
# Copy values from settings to form
- if "archiver" in self.config[1] and "rar" in self.config[1]["archiver"]:
- self.leRarExePath.setText(getattr(self.config[0], self.config[1]["archiver"]["rar"].internal_name))
+ if "archiver" in self.config[1] and "rar" in self.config[1]["archiver"].v:
+ self.leRarExePath.setText(getattr(self.config[0], self.config[1]["archiver"].v["rar"].internal_name))
else:
self.leRarExePath.setEnabled(False)
self.sbNameMatchIdentifyThresh.setValue(self.config[0].identifier_series_match_identify_thresh)
- self.sbNameMatchSearchThresh.setValue(self.config[0].talkers_series_match_search_thresh)
+ self.sbNameMatchSearchThresh.setValue(self.config[0].talker_series_match_search_thresh)
self.tePublisherFilter.setPlainText("\n".join(self.config[0].identifier_publisher_filter))
self.cbxCheckForNewVersion.setChecked(self.config[0].general_check_for_new_version)
@@ -311,12 +313,12 @@ class SettingsWindow(QtWidgets.QDialog):
self.switch_parser()
self.cbxUseSeriesStartAsVolume.setChecked(self.config[0].talker_comicvine_cv_use_series_start_as_volume)
- self.cbxClearFormBeforePopulating.setChecked(self.config[0].talkers_clear_form_before_populating)
+ self.cbxClearFormBeforePopulating.setChecked(self.config[0].talker_clear_form_before_populating)
self.cbxRemoveHtmlTables.setChecked(self.config[0].talker_comicvine_cv_remove_html_tables)
- self.cbxUseFilter.setChecked(self.config[0].talkers_always_use_publisher_filter)
- self.cbxSortByYear.setChecked(self.config[0].talkers_sort_series_by_year)
- self.cbxExactMatches.setChecked(self.config[0].talkers_exact_series_matches_first)
+ self.cbxUseFilter.setChecked(self.config[0].talker_always_use_publisher_filter)
+ self.cbxSortByYear.setChecked(self.config[0].talker_sort_series_by_year)
+ self.cbxExactMatches.setChecked(self.config[0].talker_exact_series_matches_first)
self.leKey.setText(self.config[0].talker_comicvine_cv_api_key)
self.leURL.setText(self.config[0].talker_comicvine_cv_url)
@@ -403,11 +405,11 @@ class SettingsWindow(QtWidgets.QDialog):
)
# Copy values from form to settings and save
- if "archiver" in self.config[1] and "rar" in self.config[1]["archiver"]:
- setattr(self.config[0], self.config[1]["archiver"]["rar"].internal_name, str(self.leRarExePath.text()))
+ if "archiver" in self.config[1] and "rar" in self.config[1]["archiver"].v:
+ setattr(self.config[0], self.config[1]["archiver"].v["rar"].internal_name, str(self.leRarExePath.text()))
# make sure rar program is now in the path for the rar class
- if self.config[0].archivers_rar:
+ if self.config[0].archiver_rar:
utils.add_to_path(os.path.dirname(str(self.leRarExePath.text())))
if not str(self.leIssueNumPadding.text()).isdigit():
@@ -416,7 +418,7 @@ class SettingsWindow(QtWidgets.QDialog):
self.config[0].general_check_for_new_version = self.cbxCheckForNewVersion.isChecked()
self.config[0].identifier_series_match_identify_thresh = self.sbNameMatchIdentifyThresh.value()
- self.config[0].talkers_series_match_search_thresh = self.sbNameMatchSearchThresh.value()
+ self.config[0].talker_series_match_search_thresh = self.sbNameMatchSearchThresh.value()
self.config[0].identifier_publisher_filter = [
x.strip() for x in str(self.tePublisherFilter.toPlainText()).splitlines() if x.strip()
]
@@ -427,20 +429,20 @@ class SettingsWindow(QtWidgets.QDialog):
self.config[0].filename_remove_publisher = self.cbxRemovePublisher.isChecked()
self.config[0].talker_comicvine_cv_use_series_start_as_volume = self.cbxUseSeriesStartAsVolume.isChecked()
- self.config[0].talkers_clear_form_before_populating = self.cbxClearFormBeforePopulating.isChecked()
+ self.config[0].talker_clear_form_before_populating = self.cbxClearFormBeforePopulating.isChecked()
self.config[0].talker_comicvine_cv_remove_html_tables = self.cbxRemoveHtmlTables.isChecked()
- self.config[0].talkers_always_use_publisher_filter = self.cbxUseFilter.isChecked()
- self.config[0].talkers_sort_series_by_year = self.cbxSortByYear.isChecked()
- self.config[0].talkers_exact_series_matches_first = self.cbxExactMatches.isChecked()
+ self.config[0].talker_always_use_publisher_filter = self.cbxUseFilter.isChecked()
+ self.config[0].talker_sort_series_by_year = self.cbxSortByYear.isChecked()
+ self.config[0].talker_exact_series_matches_first = self.cbxExactMatches.isChecked()
if self.leKey.text().strip():
self.config[0].talker_comicvine_cv_api_key = self.leKey.text().strip()
- self.talker_api.api_key = self.config[0].talker_comicvine_cv_api_key
+ self.talker.api_key = self.config[0].talker_comicvine_cv_api_key
if self.leURL.text().strip():
self.config[0].talker_comicvine_cv_url = self.leURL.text().strip()
- self.talker_api.api_url = self.config[0].talker_comicvine_cv_url
+ self.talker.api_url = self.config[0].talker_comicvine_cv_url
self.config[0].cbl_assume_lone_credit_is_primary = self.cbxAssumeLoneCreditIsPrimary.isChecked()
self.config[0].cbl_copy_characters_to_tags = self.cbxCopyCharactersToTags.isChecked()
@@ -462,10 +464,17 @@ class SettingsWindow(QtWidgets.QDialog):
self.config[0].rename_strict = self.cbxRenameStrict.isChecked()
self.config[0].rename_replacements = self.get_replacements()
+ self.update_talkers_config()
+
settngs.save_file(self.config, self.config[0].runtime_config.user_config_dir / "settings.json")
self.parent().config = self.config
QtWidgets.QDialog.accept(self)
+ def update_talkers_config(self) -> None:
+ cfg = settngs.normalize_config(self.config, True, True)
+ if f"talker_{self.talker.id}" in cfg[0]:
+ self.talker.parse_settings(cfg[0][f"talker_{self.talker.id}"])
+
def select_rar(self) -> None:
self.select_file(self.leRarExePath, "RAR")
@@ -475,7 +484,7 @@ class SettingsWindow(QtWidgets.QDialog):
QtWidgets.QMessageBox.information(self, self.name, "Cache has been cleared.")
def test_api_key(self) -> None:
- if self.talker_api.check_api_key(self.leKey.text().strip(), self.leURL.text().strip()):
+ if self.talker.check_api_key(self.leKey.text().strip(), self.leURL.text().strip()):
QtWidgets.QMessageBox.information(self, "API Key Test", "Key is valid!")
else:
QtWidgets.QMessageBox.warning(self, "API Key Test", "Key is NOT valid.")
diff --git a/comictaggerlib/taggerwindow.py b/comictaggerlib/taggerwindow.py
index e708347..35dfc0b 100644
--- a/comictaggerlib/taggerwindow.py
+++ b/comictaggerlib/taggerwindow.py
@@ -63,7 +63,7 @@ from comictaggerlib.settingswindow import SettingsWindow
from comictaggerlib.ui import ui_path
from comictaggerlib.ui.qtutils import center_window_on_parent, reduce_widget_font_size
from comictaggerlib.versionchecker import VersionChecker
-from comictalker.talkerbase import ComicTalker, TalkerError
+from comictalker.comictalker import ComicTalker, TalkerError
logger = logging.getLogger(__name__)
@@ -79,15 +79,15 @@ class TaggerWindow(QtWidgets.QMainWindow):
def __init__(
self,
file_list: list[str],
- config: settngs.Config,
- talker_api: ComicTalker,
+ config: settngs.Config[settngs.Namespace],
+ talkers: dict[str, ComicTalker],
parent: QtWidgets.QWidget | None = None,
) -> None:
super().__init__(parent)
uic.loadUi(ui_path / "taggerwindow.ui", self)
self.config = config
- self.talker_api = talker_api
+ self.talkers = talkers
self.log_window = self.setup_logger()
# prevent multiple instances
@@ -126,7 +126,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
grid_layout.addWidget(self.archiveCoverWidget)
grid_layout.setContentsMargins(0, 0, 0, 0)
- self.page_list_editor = PageListEditor(self.tabPages, self.talker_api)
+ self.page_list_editor = PageListEditor(self.tabPages)
grid_layout = QtWidgets.QGridLayout(self.tabPages)
grid_layout.addWidget(self.page_list_editor)
@@ -249,21 +249,27 @@ class TaggerWindow(QtWidgets.QMainWindow):
self,
"Welcome!",
"""
-Thanks for trying ComicTagger!
-Be aware that this is beta-level software, and consider it experimental.
-You should use it very carefully when modifying your data files. As the
-license says, it's "AS IS!"
-Also, be aware that writing tags to comic archives will change their file hashes,
-which has implications with respect to other software packages. It's best to
-use ComicTagger on local copies of your comics.
-Have fun!
-""",
+ Thanks for trying ComicTagger!
+ Be aware that this is beta-level software, and consider it experimental.
+ You should use it very carefully when modifying your data files. As the
+ license says, it's "AS IS!"
+ Also, be aware that writing tags to comic archives will change their file hashes,
+ which has implications with respect to other software packages. It's best to
+ use ComicTagger on local copies of your comics.
+ Have fun!
+ """,
)
self.config[0].dialog_show_disclaimer = not checked
if self.config[0].general_check_for_new_version:
self.check_latest_version_online()
+ def current_talker(self) -> ComicTalker:
+ if self.config[0].talker_source in self.talkers:
+ return self.talkers[self.config[0].talker_source]
+ logger.error("Could not find the '%s' talker", self.config[0].talker_source)
+ raise SystemExit(2)
+
def open_file_event(self, url: QtCore.QUrl) -> None:
logger.info(url.toLocalFile())
self.fileSelectionList.add_path_list([url.toLocalFile()])
@@ -1045,7 +1051,7 @@ Have fun!
cover_index_list,
self.comic_archive,
self.config[0],
- self.talker_api,
+ self.current_talker(),
autoselect,
literal,
)
@@ -1063,7 +1069,7 @@ Have fun!
self.form_to_metadata()
try:
- new_metadata = self.talker_api.fetch_comic_data(
+ new_metadata = self.current_talker().fetch_comic_data(
issue_id=selector.issue_id, series_id=selector.series_id, issue_number=selector.issue_number
)
except TalkerError as e:
@@ -1075,11 +1081,11 @@ Have fun!
if self.config[0].cbl_apply_transform_on_import:
new_metadata = CBLTransformer(new_metadata, self.config[0]).apply()
- if self.config[0].talkers_clear_form_before_populating:
+ if self.config[0].talker_clear_form_before_populating:
self.clear_form()
notes = (
- f"Tagged with ComicTagger {ctversion.version} using info from {self.talker_api.source_details.name} on"
+ f"Tagged with ComicTagger {ctversion.version} using info from {self.current_talker().name} on"
f" {datetime.now():%Y-%m-%d %H:%M:%S}. [Issue ID {new_metadata.issue_id}]"
)
self.metadata.overlay(
@@ -1360,7 +1366,7 @@ Have fun!
def show_settings(self) -> None:
- settingswin = SettingsWindow(self, self.config, self.talker_api)
+ settingswin = SettingsWindow(self, self.config, self.current_talker())
settingswin.setModal(True)
settingswin.exec()
settingswin.result()
@@ -1669,7 +1675,7 @@ Have fun!
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
try:
- ct_md = self.talker_api.fetch_comic_data(match["issue_id"])
+ ct_md = self.current_talker().fetch_comic_data(match["issue_id"])
except TalkerError:
logger.exception("Save aborted.")
@@ -1694,7 +1700,7 @@ Have fun!
self, ca: ComicArchive, match_results: OnlineMatchResults, dlg: AutoTagStartWindow
) -> tuple[bool, OnlineMatchResults]:
success = False
- ii = IssueIdentifier(ca, self.config[0], self.talker_api)
+ ii = IssueIdentifier(ca, self.config[0], self.current_talker())
# read in metadata, and parse file name if not there
try:
@@ -1793,7 +1799,7 @@ Have fun!
)
md.overlay(ct_md.replace(notes=utils.combine_notes(md.notes, notes, "Tagged with ComicTagger")))
- if self.config[0].talkers_auto_imprint:
+ if self.config[0].talker_auto_imprint:
md.fix_publisher()
if not ca.write_metadata(md, self.save_data_style):
@@ -1834,7 +1840,7 @@ Have fun!
if not atstartdlg.exec():
return
- self.atprogdialog = AutoTagProgressWindow(self, self.talker_api)
+ self.atprogdialog = AutoTagProgressWindow(self, self.current_talker())
self.atprogdialog.setModal(True)
self.atprogdialog.show()
self.atprogdialog.progressBar.setMaximum(len(ca_list))
@@ -1926,7 +1932,7 @@ Have fun!
style,
self.actual_issue_data_fetch,
self.config[0],
- self.talker_api,
+ self.current_talker(),
)
matchdlg.setModal(True)
matchdlg.exec()
@@ -1989,7 +1995,7 @@ Have fun!
def show_page_browser(self) -> None:
if self.page_browser is None:
- self.page_browser = PageBrowserWindow(self, self.talker_api, self.metadata)
+ self.page_browser = PageBrowserWindow(self, self.metadata)
if self.comic_archive is not None:
self.page_browser.set_comic_archive(self.comic_archive)
self.page_browser.finished.connect(self.page_browser_closed)
@@ -2056,7 +2062,7 @@ Have fun!
"File Rename", "If you rename files now, unsaved data in the form will be lost. Are you sure?"
):
- dlg = RenameWindow(self, ca_list, self.load_data_style, self.config, self.talker_api)
+ dlg = RenameWindow(self, ca_list, self.load_data_style, self.config, self.current_talker())
dlg.setModal(True)
if dlg.exec() and self.comic_archive is not None:
self.fileSelectionList.update_selected_rows()
diff --git a/comictalker/__init__.py b/comictalker/__init__.py
index 9d48db4..e5bd095 100644
--- a/comictalker/__init__.py
+++ b/comictalker/__init__.py
@@ -1 +1,31 @@
from __future__ import annotations
+
+import logging
+import pathlib
+
+import comictalker.talkers.comicvine
+from comictalker.comictalker import ComicTalker, TalkerError
+from comictalker.resulttypes import ComicIssue, ComicSeries
+
+logger = logging.getLogger(__name__)
+
+__all__ = [
+ "ComicTalker",
+ "TalkerError",
+ "ComicIssue",
+ "ComicSeries",
+]
+
+
+def get_talkers(version: str, cache: pathlib.Path) -> dict[str, ComicTalker]:
+ """Returns all comic talker instances"""
+ talkers: dict[str, ComicTalker] = {}
+
+ for talker in [comictalker.talkers.comicvine.ComicVineTalker]:
+ try:
+ obj = talker(version, cache)
+ talkers[obj.id] = obj
+ except Exception:
+ logger.exception("Failed to load talker: %s", "comicvine")
+ raise TalkerError(source="comicvine", code=4, desc="Failed to initialise talker")
+ return talkers
diff --git a/comictalker/talkerbase.py b/comictalker/comictalker.py
similarity index 81%
rename from comictalker/talkerbase.py
rename to comictalker/comictalker.py
index 60bbf01..7fe7d5a 100644
--- a/comictalker/talkerbase.py
+++ b/comictalker/comictalker.py
@@ -25,38 +25,6 @@ from comictalker.resulttypes import ComicIssue, ComicSeries
logger = logging.getLogger(__name__)
-class SourceDetails:
- def __init__(
- self,
- name: str = "",
- ident: str = "",
- logo: str = "",
- ):
- self.name = name
- self.id = ident
- self.logo = logo
-
-
-class SourceStaticSettings:
- def __init__(
- self,
- website: str = "",
- attribution_string: str = "", # Full string including web link, example: Metadata provided by Example
- has_issues: bool = False,
- has_alt_covers: bool = False,
- requires_apikey: bool = False,
- has_nsfw: bool = False,
- has_censored_covers: bool = False,
- ) -> None:
- self.website = website
- self.attribution_string = attribution_string
- self.has_issues = has_issues
- self.has_alt_covers = has_alt_covers
- self.requires_apikey = requires_apikey
- self.has_nsfw = has_nsfw
- self.has_censored_covers = has_censored_covers
-
-
class TalkerError(Exception):
"""Base class exception for information sources.
@@ -71,10 +39,8 @@ class TalkerError(Exception):
codes = {1: "General", 2: "Network", 3: "Data", 4: "Other"}
- def __init__(self, source: str, desc: str, code: int = 4, sub_code: int = 0) -> None:
+ def __init__(self, source: str, desc: str = "Unknown", code: int = 4, sub_code: int = 0) -> None:
super().__init__()
- if desc == "":
- desc = "Unknown"
self.desc = desc
self.code = code
self.code_name = self.codes[code]
@@ -136,14 +102,16 @@ class TalkerDataError(TalkerError):
super().__init__(source, desc, 3, sub_code)
-# Class talkers instance
class ComicTalker:
"""The base class for all comic source talkers"""
+ name: str = "Example"
+ id: str = "example"
+ logo_url: str = "https://example.com/logo.png"
+ website: str = "https://example.com/"
+ attribution: str = f"Metadata provided by {name}"
+
def __init__(self, version: str, cache_folder: pathlib.Path) -> None:
- # Identity name for the information source etc.
- self.source_details = SourceDetails()
- self.static_config = SourceStaticSettings()
self.cache_folder = cache_folder
self.version = version
self.api_key: str = ""
@@ -153,10 +121,12 @@ class ComicTalker:
"""Allows registering settings using the settngs package with an argparse like interface"""
return None
- def parse_settings(self, settings: dict[str, Any]) -> None:
- """settings is a dictionary of options defined in register_settings.
- It is only guaranteed that the settings defined in register_settings will be present."""
- return None
+ def parse_settings(self, settings: dict[str, Any]) -> dict[str, Any]:
+ """
+ settings is a dictionary of settings defined in register_settings.
+ It is only guaranteed that the settings defined in register_settings will be present.
+ """
+ return settings
def check_api_key(self, key: str, url: str) -> bool:
"""
diff --git a/comictalker/comictalkerapi.py b/comictalker/comictalkerapi.py
deleted file mode 100644
index 664f7cb..0000000
--- a/comictalker/comictalkerapi.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""Handles collecting data from source talkers."""
-
-# Copyright 2012-2014 Anthony Beville
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-from __future__ import annotations
-
-import logging
-import pathlib
-
-import comictalker.talkers.comicvine
-from comictalker.talkerbase import ComicTalker, TalkerError
-
-logger = logging.getLogger(__name__)
-
-
-def get_talkers(version: str, cache: pathlib.Path) -> dict[str, ComicTalker]:
- """Returns all comic talker instances"""
- # TODO separate PR will bring talkers in via entry points. TalkerError etc. source will then be a var
- talkers: dict[str, ComicTalker] = {}
-
- for talker in [comictalker.talkers.comicvine.ComicVineTalker]:
- try:
- obj = talker(version, cache)
- talkers[obj.source_details.id] = obj
- except Exception:
- logger.exception("Failed to load talker: %s", "comicvine")
- raise TalkerError(source="comicvine", code=4, desc="Failed to initialise talker")
- return talkers
diff --git a/comictalker/talker_utils.py b/comictalker/talker_utils.py
index 26efecb..bd605ca 100644
--- a/comictalker/talker_utils.py
+++ b/comictalker/talker_utils.py
@@ -1,5 +1,3 @@
-"""Generic sources utils to format API data and the like.
-"""
# Copyright 2012-2014 Anthony Beville
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,7 +21,7 @@ from bs4 import BeautifulSoup
from comicapi import utils
from comicapi.genericmetadata import GenericMetadata
from comicapi.issuestring import IssueString
-from comictalker.talkerbase import ComicIssue
+from comictalker.resulttypes import ComicIssue
logger = logging.getLogger(__name__)
diff --git a/comictalker/talkers/comicvine.py b/comictalker/talkers/comicvine.py
index 1689203..8d77cc8 100644
--- a/comictalker/talkers/comicvine.py
+++ b/comictalker/talkers/comicvine.py
@@ -33,8 +33,8 @@ from comicapi import utils
from comicapi.genericmetadata import GenericMetadata
from comicapi.issuestring import IssueString
from comictalker.comiccacher import ComicCacher
+from comictalker.comictalker import ComicTalker, TalkerDataError, TalkerNetworkError
from comictalker.resulttypes import ComicIssue, ComicSeries, Credit
-from comictalker.talkerbase import ComicTalker, SourceDetails, SourceStaticSettings, TalkerDataError, TalkerNetworkError
logger = logging.getLogger(__name__)
@@ -150,40 +150,28 @@ class CVResult(TypedDict, Generic[T]):
version: str
-CV_RATE_LIMIT_STATUS = 107
+CV_STATUS_RATELIMIT = 107
class ComicVineTalker(ComicTalker):
+ name: str = "Comic Vine"
+ id: str = "comicvine"
+ logo_url: str = "https://comicvine.gamespot.com/a/bundles/comicvinesite/images/logo.png"
+ website: str = "https://comicvine.gamespot.com/"
+ attribution: str = f"Metadata provided by {name}"
+
def __init__(
self,
version: str,
cache_folder: pathlib.Path,
):
super().__init__(version, cache_folder)
- self.source_details = SourceDetails(
- name="Comic Vine",
- ident="comicvine",
- logo="https://comicvine.gamespot.com/a/bundles/comicvinesite/images/logo.png",
- )
- self.static_config = SourceStaticSettings(
- website="https://comicvine.gamespot.com/",
- attribution_string="Metadata provided by Comic Vine",
- has_issues=True,
- has_alt_covers=True,
- requires_apikey=True,
- has_nsfw=False,
- has_censored_covers=False,
- )
# Default settings
self.api_url: str = "https://comicvine.gamespot.com/api"
self.api_key: str = "27431e6787042105bd3e47e169a624521f89f3a4"
self.remove_html_tables: bool = False
self.use_series_start_as_volume: bool = False
- self.wait_for_rate_limit: bool = False
-
- # Identity name for the information source
- self.source_name: str = self.source_details.id
- self.source_name_friendly: str = self.source_details.name
+ self.wait_on_ratelimit: bool = False
tmp_url = urlsplit(self.api_url)
@@ -194,7 +182,7 @@ class ComicVineTalker(ComicTalker):
self.api_url = tmp_url.geturl()
# NOTE: This was hardcoded before which is why it isn't in settings
- self.wait_for_rate_limit_time: int = 20
+ self.wait_on_ratelimit_time: int = 20
def register_settings(self, parser: settngs.Manager) -> None:
parser.add_setting("--cv-use-series-start-as-volume", default=False, action=argparse.BooleanOptionalAction)
@@ -214,9 +202,7 @@ class ComicVineTalker(ComicTalker):
help="Use the given Comic Vine URL.",
)
- def parse_settings(self, settings: dict[str, Any]) -> None:
- self.remove_html_tables = settings["cv_remove_html_tables"]
- self.use_series_start_as_volume = settings["cv_use_series_start_as_volume"]
+ def parse_settings(self, settings: dict[str, Any]) -> dict[str, Any]:
if settings["cv_api_key"]:
self.api_key = settings["cv_api_key"]
if settings["cv_url"]:
@@ -227,6 +213,11 @@ class ComicVineTalker(ComicTalker):
self.api_url = tmp_url.geturl()
+ self.use_series_start_as_volume = settings["cv_use_series_start_as_volume"]
+ self.wait_on_ratelimit = settings["cv_wait_on_ratelimit"]
+ self.remove_html_tables = settings["cv_remove_html_tables"]
+ return settngs
+
def check_api_key(self, key: str, url: str) -> bool:
if not url:
url = self.api_url
@@ -259,7 +250,7 @@ class ComicVineTalker(ComicTalker):
wait_times = [1, 2, 3, 4]
while True:
cv_response: CVResult = self.get_url_content(url, params)
- if self.wait_for_rate_limit and cv_response["status_code"] == CV_RATE_LIMIT_STATUS:
+ if self.wait_on_ratelimit and cv_response["status_code"] == CV_STATUS_RATELIMIT:
logger.info(f"Rate limit encountered. Waiting for {limit_wait_time} minutes\n")
time.sleep(limit_wait_time * 60)
total_time_waited += limit_wait_time
@@ -267,15 +258,13 @@ class ComicVineTalker(ComicTalker):
if counter < 3:
counter += 1
# don't wait much more than 20 minutes
- if total_time_waited < self.wait_for_rate_limit_time:
+ if total_time_waited < self.wait_on_ratelimit_time:
continue
if cv_response["status_code"] != 1:
logger.debug(
- f"{self.source_name_friendly} query failed with error #{cv_response['status_code']}: [{cv_response['error']}]."
- )
- raise TalkerNetworkError(
- self.source_name_friendly, 0, f"{cv_response['status_code']}: {cv_response['error']}"
+ f"{self.name} query failed with error #{cv_response['status_code']}: [{cv_response['error']}]."
)
+ raise TalkerNetworkError(self.name, 0, f"{cv_response['status_code']}: {cv_response['error']}")
# it's all good
break
@@ -298,16 +287,16 @@ class ComicVineTalker(ComicTalker):
break
except requests.exceptions.Timeout:
- logger.debug(f"Connection to {self.source_name_friendly} timed out.")
- raise TalkerNetworkError(self.source_name_friendly, 4)
+ logger.debug(f"Connection to {self.name} timed out.")
+ raise TalkerNetworkError(self.name, 4)
except requests.exceptions.RequestException as e:
logger.debug(f"Request exception: {e}")
- raise TalkerNetworkError(self.source_name_friendly, 0, str(e)) from e
+ raise TalkerNetworkError(self.name, 0, str(e)) from e
except json.JSONDecodeError as e:
logger.debug(f"JSON decode error: {e}")
- raise TalkerDataError(self.source_name_friendly, 2, "ComicVine did not provide json")
+ raise TalkerDataError(self.name, 2, "ComicVine did not provide json")
- raise TalkerNetworkError(self.source_name_friendly, 5)
+ raise TalkerNetworkError(self.name, 5)
def format_search_results(self, search_results: list[CVSeries]) -> list[ComicSeries]:
formatted_results = []
@@ -415,13 +404,13 @@ class ComicVineTalker(ComicTalker):
) -> list[ComicSeries]:
# Sanitize the series name for comicvine searching, comicvine search ignore symbols
search_series_name = utils.sanitize_title(series_name, literal)
- logger.info(f"{self.source_name_friendly} searching: {search_series_name}")
+ logger.info(f"{self.name} searching: {search_series_name}")
# Before we search online, look in our cache, since we might have done this same search recently
# For literal searches always retrieve from online
cvc = ComicCacher(self.cache_folder, self.version)
if not refresh_cache and not literal:
- cached_search_results = cvc.get_search_results(self.source_name, series_name)
+ cached_search_results = cvc.get_search_results(self.id, series_name)
if len(cached_search_results) > 0:
return cached_search_results
@@ -495,7 +484,7 @@ class ComicVineTalker(ComicTalker):
# Cache these search results, even if it's literal we cache the results
# The most it will cause is extra processing time
- cvc.add_search_results(self.source_name, series_name, formatted_search_results)
+ cvc.add_search_results(self.id, series_name, formatted_search_results)
return formatted_search_results
@@ -514,7 +503,7 @@ class ComicVineTalker(ComicTalker):
def fetch_series_data(self, series_id: int) -> ComicSeries:
# before we search online, look in our cache, since we might already have this info
cvc = ComicCacher(self.cache_folder, self.version)
- cached_series_result = cvc.get_series_info(str(series_id), self.source_name)
+ cached_series_result = cvc.get_series_info(str(series_id), self.id)
if cached_series_result is not None:
return cached_series_result
@@ -531,14 +520,14 @@ class ComicVineTalker(ComicTalker):
formatted_series_results = self.format_search_results([series_results])
if series_results:
- cvc.add_series_info(self.source_name, formatted_series_results[0])
+ cvc.add_series_info(self.id, formatted_series_results[0])
return formatted_series_results[0]
def fetch_issues_by_series(self, series_id: str) -> list[ComicIssue]:
# before we search online, look in our cache, since we might already have this info
cvc = ComicCacher(self.cache_folder, self.version)
- cached_series_issues_result = cvc.get_series_issues_info(series_id, self.source_name)
+ cached_series_issues_result = cvc.get_series_issues_info(series_id, self.id)
series_data = self.fetch_series_data(int(series_id))
@@ -575,7 +564,7 @@ class ComicVineTalker(ComicTalker):
# Format to expected output
formatted_series_issues_result = self.format_issue_results(series_issues_result)
- cvc.add_series_issues_info(self.source_name, formatted_series_issues_result)
+ cvc.add_series_issues_info(self.id, formatted_series_issues_result)
return formatted_series_issues_result
@@ -640,7 +629,7 @@ class ComicVineTalker(ComicTalker):
if f_record and f_record.complete:
# Cache had full record
return talker_utils.map_comic_issue_to_metadata(
- f_record, self.source_name_friendly, self.remove_html_tables, self.use_series_start_as_volume
+ f_record, self.name, self.remove_html_tables, self.use_series_start_as_volume
)
if f_record is not None:
@@ -650,12 +639,12 @@ class ComicVineTalker(ComicTalker):
def fetch_issue_data_by_issue_id(self, issue_id: str) -> GenericMetadata:
# before we search online, look in our cache, since we might already have this info
cvc = ComicCacher(self.cache_folder, self.version)
- cached_issues_result = cvc.get_issue_info(int(issue_id), self.source_name)
+ cached_issues_result = cvc.get_issue_info(int(issue_id), self.id)
if cached_issues_result and cached_issues_result.complete:
return talker_utils.map_comic_issue_to_metadata(
cached_issues_result,
- self.source_name_friendly,
+ self.name,
self.remove_html_tables,
self.use_series_start_as_volume,
)
@@ -672,12 +661,12 @@ class ComicVineTalker(ComicTalker):
# Due to issue not returning publisher, fetch the series.
cv_issues[0].series = self.fetch_series_data(int(cv_issues[0].series.id))
- cvc.add_series_issues_info(self.source_name, cv_issues)
+ cvc.add_series_issues_info(self.id, cv_issues)
# Now, map the ComicIssue data to generic metadata
return talker_utils.map_comic_issue_to_metadata(
cv_issues[0],
- self.source_name_friendly,
+ self.name,
self.remove_html_tables,
self.use_series_start_as_volume,
)
diff --git a/requirements.txt b/requirements.txt
index 2d29204..a6aefcd 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,7 +5,7 @@ natsort>=8.1.0
pathvalidate
pillow>=9.1.0, <10
pycountry
-pyicu; sys_platform == 'linux' or sys_platform == 'darwin'
+#pyicu; sys_platform == 'linux' or sys_platform == 'darwin'
rapidfuzz>=2.12.0
requests==2.*
settngs==0.5.0
diff --git a/tests/comicvinetalker_test.py b/tests/comicvinetalker_test.py
index 5621189..149bddb 100644
--- a/tests/comicvinetalker_test.py
+++ b/tests/comicvinetalker_test.py
@@ -11,7 +11,7 @@ import testing.comicvine
def test_search_for_series(comicvine_api, comic_cache):
results = comicvine_api.search_for_series("cory doctorows futuristic tales of the here and now")
cache_issues = comic_cache.get_search_results(
- comicvine_api.source_name, "cory doctorows futuristic tales of the here and now"
+ comicvine_api.id, "cory doctorows futuristic tales of the here and now"
)
assert results == cache_issues
@@ -20,7 +20,7 @@ def test_fetch_series_data(comicvine_api, comic_cache):
result = comicvine_api.fetch_series_data(23437)
# del result["description"]
# del result["image_url"]
- cache_result = comic_cache.get_series_info(23437, comicvine_api.source_name)
+ cache_result = comic_cache.get_series_info(23437, comicvine_api.id)
# del cache_result["description"]
# del cache_result["image_url"]
assert result == cache_result
@@ -28,7 +28,7 @@ def test_fetch_series_data(comicvine_api, comic_cache):
def test_fetch_issues_by_series(comicvine_api, comic_cache):
results = comicvine_api.fetch_issues_by_series(23437)
- cache_issues = comic_cache.get_series_issues_info(23437, comicvine_api.source_name)
+ cache_issues = comic_cache.get_series_issues_info(23437, comicvine_api.id)
assert dataclasses.asdict(results[0])["series"] == dataclasses.asdict(cache_issues[0])["series"]
diff --git a/tests/conftest.py b/tests/conftest.py
index 3127f98..40084d6 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -15,8 +15,8 @@ from PIL import Image
import comicapi.comicarchive
import comicapi.genericmetadata
import comictaggerlib.ctsettings
+import comictalker
import comictalker.comiccacher
-import comictalker.comictalkerapi
import comictalker.talkers.comicvine
from comicapi import utils
from testing import comicvine, filenames