clean up talker

This commit is contained in:
Timmy Welch 2023-02-09 19:33:10 -08:00
parent 4a5d02119e
commit e5b15abf91
No known key found for this signature in database
29 changed files with 250 additions and 289 deletions

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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 = """<html>The <b>Name Match Ratio Threshold: Auto-Identify</b> 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:

View File

@ -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

View File

@ -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)

View File

@ -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] = {}

View File

@ -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,

View File

@ -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)

View File

@ -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()

View File

@ -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:

View File

@ -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}")

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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.")

View File

@ -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!<br><br>
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!"<br><br>
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.<br><br>
Have fun!
""",
Thanks for trying ComicTagger!<br><br>
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!"<br><br>
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.<br><br>
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()

View File

@ -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

View File

@ -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 <a href='http://website'>Example</a>
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 <a href='{website}'>{name}</a>"
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:
"""

View File

@ -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

View File

@ -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__)

View File

@ -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 <a href='{website}'>{name}</a>"
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 <a href='https://comicvine.gamespot.com/'>Comic Vine</a>",
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,
)

View File

@ -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

View File

@ -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"]

View File

@ -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