From 2d8c47edcaacfbf6715c83b845b6a6c2aa04b72f Mon Sep 17 00:00:00 2001 From: Mizaki Date: Mon, 2 Jan 2023 01:04:15 +0000 Subject: [PATCH] Use new settings system for plugin --- comictaggerlib/autotagstartwindow.py | 2 +- comictaggerlib/cli.py | 8 +-- comictaggerlib/ctoptions/file.py | 36 ++++-------- comictaggerlib/main.py | 34 +++++------ comictaggerlib/seriesselectionwindow.py | 6 +- comictaggerlib/settingswindow.py | 24 ++++---- comictaggerlib/taggerwindow.py | 4 +- comictalker/talkerbase.py | 20 ++++--- comictalker/talkers/comicvine.py | 77 +++++++++++++++++++++---- tests/conftest.py | 6 -- 10 files changed, 126 insertions(+), 91 deletions(-) diff --git a/comictaggerlib/autotagstartwindow.py b/comictaggerlib/autotagstartwindow.py index b0ff6b2..6fa6a31 100644 --- a/comictaggerlib/autotagstartwindow.py +++ b/comictaggerlib/autotagstartwindow.py @@ -49,7 +49,7 @@ class AutoTagStartWindow(QtWidgets.QDialog): self.cbxIgnoreLeadingDigitsInFilename.setChecked(self.options.autotag_ignore_leading_numbers_in_filename) self.cbxRemoveAfterSuccess.setChecked(self.options.autotag_remove_archive_after_successful_match) self.cbxWaitForRateLimit.setChecked(self.options.autotag_wait_and_retry_on_rate_limit) - self.cbxAutoImprint.setChecked(self.options.comicvine_auto_imprint) + self.cbxAutoImprint.setChecked(self.options.talkers_general_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 diff --git a/comictaggerlib/cli.py b/comictaggerlib/cli.py index 9da4228..7938f43 100644 --- a/comictaggerlib/cli.py +++ b/comictaggerlib/cli.py @@ -108,7 +108,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.options.comicvine_clear_metadata_on_import: + if self.options.talkers_general_clear_metadata_on_import: md = ct_md else: notes = ( @@ -117,7 +117,7 @@ class CLI: ) md.overlay(ct_md.replace(notes=utils.combine_notes(md.notes, notes, "Tagged with ComicTagger"))) - if self.options.comicvine_auto_imprint: + if self.options.talkers_general_auto_imprint: md.fix_publisher() self.actual_metadata_save(ca, md) @@ -428,7 +428,7 @@ class CLI: match_results.fetch_data_failures.append(str(ca.path.absolute())) return - if self.options.comicvine_clear_metadata_on_import: + if self.options.talkers_general_clear_metadata_on_import: md = ct_md else: notes = ( @@ -437,7 +437,7 @@ class CLI: ) md.overlay(ct_md.replace(notes=utils.combine_notes(md.notes, notes, "Tagged with ComicTagger"))) - if self.options.comicvine_auto_imprint: + if self.options.talkers_general_auto_imprint: md.fix_publisher() # ok, done building our metadata. time to save diff --git a/comictaggerlib/ctoptions/file.py b/comictaggerlib/ctoptions/file.py index 0d6e4f7..8bdef4c 100644 --- a/comictaggerlib/ctoptions/file.py +++ b/comictaggerlib/ctoptions/file.py @@ -93,13 +93,9 @@ def filename(parser: settngs.Manager) -> None: ) -def comicvine(parser: settngs.Manager) -> None: - # Comic Vine settings - parser.add_setting( - "--series-match-search-thresh", - default=90, - type=int, - ) +def talkers_general(parser: settngs.Manager) -> None: + # General settings for all information talkers + parser.add_setting("--source", default="comicvine", help="Use a specified source by source ID") parser.add_setting("--use-series-start-as-volume", default=False, action=argparse.BooleanOptionalAction) parser.add_setting( "--clear-metadata", @@ -108,20 +104,6 @@ def comicvine(parser: settngs.Manager) -> None: dest="clear_metadata_on_import", action=argparse.BooleanOptionalAction, ) - parser.add_setting( - "--remove-html-tables", - default=False, - action=argparse.BooleanOptionalAction, - help="Removes html tables instead of converting them to text", - ) - parser.add_setting( - "--cv-api-key", - help="Use the given Comic Vine API Key (persisted in settings).", - ) - parser.add_setting( - "--cv-url", - help="Use the given Comic Vine URL (persisted in settings).", - ) parser.add_setting( "-a", "--auto-imprint", @@ -146,10 +128,10 @@ def comicvine(parser: settngs.Manager) -> None: help="Enables the publisher filter", ) parser.add_setting( - "--clear-form-before-populating-from-cv", + "--clear-form-before-populating", default=False, action=argparse.BooleanOptionalAction, - help="Clears all existing metadata when applying metadata from ComicVine", + help="Clears all existing metadata when applying metadata from comic source", ) @@ -257,13 +239,17 @@ def validate_settings(options: settngs.Config[settngs.Values], parser: settngs.M return options -def register_settings(parser: settngs.Manager) -> None: +def register_settings(parser: settngs.Manager, talkers: dict[str, Any]) -> None: parser.add_group("general", general, False) parser.add_group("internal", internal, False) parser.add_group("identifier", identifier, False) parser.add_group("dialog", dialog, False) parser.add_group("filename", filename, False) - parser.add_group("comicvine", comicvine, False) + parser.add_group("talkers_general", talkers_general, False) parser.add_group("cbl", cbl, False) parser.add_group("rename", rename, False) parser.add_group("autotag", autotag, False) + + # Register talker plugin settings + for talker, cls in talkers.items(): + parser.add_group(talker, cls.comic_settings, False) diff --git a/comictaggerlib/main.py b/comictaggerlib/main.py index 639ebb3..5bf5497 100644 --- a/comictaggerlib/main.py +++ b/comictaggerlib/main.py @@ -66,8 +66,10 @@ class App: self.options = settngs.Config({}, {}) self.initial_arg_parser = ctoptions.initial_cmd_line_parser() self.config_load_success = False + self.talker_plugins: dict = {} def run(self) -> None: + self.talker_plugins = ct_api.get_talkers() opts = self.initialize() self.register_options() self.parse_options(opts.config) @@ -87,7 +89,7 @@ class App: "For more help visit the wiki at: https://github.com/comictagger/comictagger/wiki", ) ctoptions.register_commandline(self.manager) - ctoptions.register_settings(self.manager) + ctoptions.register_settings(self.manager, self.talker_plugins) def parse_options(self, config_paths: ctoptions.ComicTaggerPaths) -> None: self.options, self.config_load_success = self.manager.parse_config( @@ -99,6 +101,18 @@ class App: self.options = ctoptions.validate_settings(self.options, self.manager) self.options = self.options + # parse talker settings + for loaded in self.talker_plugins.values(): + parse_options = getattr(loaded, "parse_settings", None) + if parse_options is None: + logger.warning(f"Failed to find parse_settings in talker {loaded}") + continue + + try: + parse_options(self.options) + except Exception as e: + logger.warning(f"Failed to parse talker options for {loaded}: {e}") + def initialize_dirs(self) -> None: self.options[0].runtime_config.user_data_dir.mkdir(parents=True, exist_ok=True) self.options[0].runtime_config.user_config_dir.mkdir(parents=True, exist_ok=True) @@ -136,28 +150,12 @@ class App: self.options[0].runtime_no_gui = True logger.warning("PyQt5 is not available. ComicTagger is limited to command-line mode.") - # manage the CV API key - # None comparison is used so that the empty string can unset the value - if self.options[0].comicvine_cv_api_key is not None or self.options[0].comicvine_cv_url is not None: - settings_path = self.options[0].runtime_config.user_config_dir / "settings.json" - if self.config_load_success: - self.manager.save_file(self.options[0], settings_path) - - if self.options[0].commands_only_set_cv_key: - if self.config_load_success: - print("Key set") # noqa: T201 - return + # TODO Have option to save passed in config options and quit? try: talker_api = ct_api.get_comic_talker("comicvine")( # type: ignore[call-arg] version=version, cache_folder=self.options[0].runtime_config.user_cache_dir, - series_match_thresh=self.options[0].comicvine_series_match_search_thresh, - remove_html_tables=self.options[0].comicvine_remove_html_tables, - use_series_start_as_volume=self.options[0].comicvine_use_series_start_as_volume, - wait_on_ratelimit=self.options[0].autotag_wait_and_retry_on_rate_limit, - api_url=self.options[0].comicvine_cv_url, - api_key=self.options[0].comicvine_cv_api_key, ) except TalkerError as e: logger.exception("Unable to load talker") diff --git a/comictaggerlib/seriesselectionwindow.py b/comictaggerlib/seriesselectionwindow.py index 7c1c479..f214a15 100644 --- a/comictaggerlib/seriesselectionwindow.py +++ b/comictaggerlib/seriesselectionwindow.py @@ -156,7 +156,7 @@ class SeriesSelectionWindow(QtWidgets.QDialog): self.progdialog: QtWidgets.QProgressDialog | None = None self.search_thread: SearchThread | None = None - self.use_filter = self.options.comicvine_always_use_publisher_filter + self.use_filter = self.options.talkers_general_always_use_publisher_filter # Load to retrieve settings self.talker_api = talker_api @@ -395,7 +395,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.options.comicvine_sort_series_by_year: + if self.options.talkers_general_sort_series_by_year: try: self.ct_search_results = sorted( self.ct_search_results, @@ -413,7 +413,7 @@ class SeriesSelectionWindow(QtWidgets.QDialog): logger.exception("bad data error sorting results by count_of_issues") # move sanitized matches to the front - if self.options.comicvine_exact_series_matches_first: + if self.options.talkers_general_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 74c9043..21d5f12 100644 --- a/comictaggerlib/settingswindow.py +++ b/comictaggerlib/settingswindow.py @@ -282,13 +282,13 @@ class SettingsWindow(QtWidgets.QDialog): self.cbxRemovePublisher.setChecked(self.options[0].filename_remove_publisher) self.switch_parser() - self.cbxUseSeriesStartAsVolume.setChecked(self.options[0].comicvine_use_series_start_as_volume) - self.cbxClearFormBeforePopulating.setChecked(self.options[0].comicvine_clear_form_before_populating_from_cv) - self.cbxRemoveHtmlTables.setChecked(self.options[0].comicvine_remove_html_tables) + self.cbxUseSeriesStartAsVolume.setChecked(self.options[0].comicvine_cv_use_series_start_as_volume) + self.cbxClearFormBeforePopulating.setChecked(self.options[0].talkers_general_clear_form_before_populating) + self.cbxRemoveHtmlTables.setChecked(self.options[0].comicvine_cv_remove_html_tables) - self.cbxUseFilter.setChecked(self.options[0].comicvine_always_use_publisher_filter) - self.cbxSortByYear.setChecked(self.options[0].comicvine_sort_series_by_year) - self.cbxExactMatches.setChecked(self.options[0].comicvine_exact_series_matches_first) + self.cbxUseFilter.setChecked(self.options[0].talkers_general_always_use_publisher_filter) + self.cbxSortByYear.setChecked(self.options[0].talkers_general_sort_series_by_year) + self.cbxExactMatches.setChecked(self.options[0].talkers_general_exact_series_matches_first) self.leKey.setText(self.options[0].comicvine_cv_api_key) self.leURL.setText(self.options[0].comicvine_cv_url) @@ -396,13 +396,13 @@ class SettingsWindow(QtWidgets.QDialog): self.options[0].filename_remove_fcbd = self.cbxRemoveFCBD.isChecked() self.options[0].filename_remove_publisher = self.cbxRemovePublisher.isChecked() - self.options[0].comicvine_use_series_start_as_volume = self.cbxUseSeriesStartAsVolume.isChecked() - self.options[0].comicvine_clear_form_before_populating_from_cv = self.cbxClearFormBeforePopulating.isChecked() - self.options[0].comicvine_remove_html_tables = self.cbxRemoveHtmlTables.isChecked() + self.options[0].comicvine_cv_use_series_start_as_volume = self.cbxUseSeriesStartAsVolume.isChecked() + self.options[0].talkers_general_clear_form_before_populating = self.cbxClearFormBeforePopulating.isChecked() + self.options[0].comicvine_cv_remove_html_tables = self.cbxRemoveHtmlTables.isChecked() - self.options[0].comicvine_always_use_publisher_filter = self.cbxUseFilter.isChecked() - self.options[0].comicvine_sort_series_by_year = self.cbxSortByYear.isChecked() - self.options[0].comicvine_exact_series_matches_first = self.cbxExactMatches.isChecked() + self.options[0].talkers_general_always_use_publisher_filter = self.cbxUseFilter.isChecked() + self.options[0].talkers_general_sort_series_by_year = self.cbxSortByYear.isChecked() + self.options[0].talkers_general_exact_series_matches_first = self.cbxExactMatches.isChecked() if self.leKey.text().strip(): self.options[0].comicvine_cv_api_key = self.leKey.text().strip() diff --git a/comictaggerlib/taggerwindow.py b/comictaggerlib/taggerwindow.py index cd46aac..d115a11 100644 --- a/comictaggerlib/taggerwindow.py +++ b/comictaggerlib/taggerwindow.py @@ -1084,7 +1084,7 @@ Have fun! if self.options[0].cbl_apply_transform_on_import: new_metadata = CBLTransformer(new_metadata, self.options[0]).apply() - if self.options[0].comicvine_clear_form_before_populating_from_cv: + if self.options[0].talkers_general_clear_form_before_populating: self.clear_form() notes = ( @@ -1802,7 +1802,7 @@ Have fun! ) md.overlay(ct_md.replace(notes=utils.combine_notes(md.notes, notes, "Tagged with ComicTagger"))) - if self.options[0].comicvine_auto_imprint: + if self.options[0].talkers_general_auto_imprint: md.fix_publisher() if not ca.write_metadata(md, self.save_data_style): diff --git a/comictalker/talkerbase.py b/comictalker/talkerbase.py index 7e87304..40413d1 100644 --- a/comictalker/talkerbase.py +++ b/comictalker/talkerbase.py @@ -13,10 +13,12 @@ # limitations under the License. from __future__ import annotations +import argparse import logging import pathlib from typing import Callable -from urllib.parse import urlsplit + +import settngs from comicapi.genericmetadata import GenericMetadata from comictalker.resulttypes import ComicIssue, ComicSeries @@ -148,16 +150,16 @@ class ComicTalker: self.cache_folder = cache_folder self.version = version - self.api_key = api_key or self.default_api_key - self.api_url = api_url or self.default_api_url + self.api_key = "" + self.api_url = "" - tmp_url = urlsplit(self.api_url) + @classmethod + def comic_settings(cls, parser: settngs.Manager) -> None: + """Talker settings.""" - # joinurl only works properly if there is a trailing slash - if tmp_url.path and tmp_url.path[-1] != "/": - tmp_url = tmp_url._replace(path=tmp_url.path + "/") - - self.api_url = tmp_url.geturl() + @classmethod + def parse_settings(cls, settings: argparse.Namespace) -> None: + """Parse settings.""" def check_api_key(self, key: str, url: str) -> bool: """ diff --git a/comictalker/talkers/comicvine.py b/comictalker/talkers/comicvine.py index 3b264f3..4042c53 100644 --- a/comictalker/talkers/comicvine.py +++ b/comictalker/talkers/comicvine.py @@ -16,6 +16,7 @@ ComicVine information source # limitations under the License. from __future__ import annotations +import argparse import json import logging import pathlib @@ -24,6 +25,7 @@ from typing import Any, Callable, Generic, TypeVar from urllib.parse import urljoin, urlsplit import requests +import settngs from typing_extensions import Required, TypedDict import comictalker.talker_utils as talker_utils @@ -155,18 +157,20 @@ class ComicVineTalker(ComicTalker): default_api_key = "27431e6787042105bd3e47e169a624521f89f3a4" default_api_url = "https://comicvine.gamespot.com/api" + # Settings + api_url: str = "" + api_key: str = "" + series_match_thresh: int = 90 + remove_html_tables: bool = False + use_series_start_as_volume: bool = False + wait_on_ratelimit: bool = False + def __init__( self, version: str, cache_folder: pathlib.Path, - api_url: str = "", - api_key: str = "", - series_match_thresh: int = 90, - remove_html_tables: bool = False, - use_series_start_as_volume: bool = False, - wait_on_ratelimit: bool = False, ): - super().__init__(version, cache_folder, api_url, api_key) + super().__init__(version, cache_folder) self.source_details = SourceDetails(name="Comic Vine", ident="comicvine") self.static_options = SourceStaticOptions( website="https://comicvine.gamespot.com/", @@ -181,14 +185,65 @@ class ComicVineTalker(ComicTalker): self.source_name: str = self.source_details.id self.source_name_friendly: str = self.source_details.name - self.wait_for_rate_limit: bool = wait_on_ratelimit + # If cls api_url or api_key is empty, use default + self.api_url = ComicVineTalker.api_url or ComicVineTalker.default_api_url + self.api_key = ComicVineTalker.api_key or ComicVineTalker.default_api_key + + tmp_url = urlsplit(self.api_url) + + # joinurl only works properly if there is a trailing slash + if tmp_url.path and tmp_url.path[-1] != "/": + tmp_url = tmp_url._replace(path=tmp_url.path + "/") + + self.api_url = tmp_url.geturl() + + self.wait_for_rate_limit: bool = ComicVineTalker.wait_on_ratelimit # NOTE: This was hardcoded before which is why it isn't passed in self.wait_for_rate_limit_time: int = 20 - self.remove_html_tables: bool = remove_html_tables - self.use_series_start_as_volume: bool = use_series_start_as_volume + self.remove_html_tables: bool = ComicVineTalker.remove_html_tables + self.use_series_start_as_volume: bool = ComicVineTalker.use_series_start_as_volume - self.series_match_thresh: int = series_match_thresh + self.series_match_thresh: int = ComicVineTalker.series_match_thresh + + @classmethod + def comic_settings(cls, parser: settngs.Manager) -> None: + # Might be general settings? + parser.add_setting( + "--series-match-search-thresh", + default=90, + type=int, + ) + parser.add_setting("--cv-use-series-start-as-volume", default=False, action=argparse.BooleanOptionalAction) + + # Comic Vine settings + parser.add_setting("--cv-wait-on-ratelimit", default=False, action=argparse.BooleanOptionalAction) + parser.add_setting( + "--cv-remove-html-tables", + default=False, + action=argparse.BooleanOptionalAction, + help="Removes html tables instead of converting them to text.", + ) + parser.add_setting( + "--cv-api-key", + help="Use the given Comic Vine API Key.", + ) + parser.add_setting( + "--cv-url", + help="Use the given Comic Vine URL.", + ) + + @classmethod + def parse_settings(cls, settings: settngs.Config) -> None: + """Parse settings.""" + if settings[0].comicvine_cv_remove_html_tables: + cls.remove_html_tables = bool(settings[0].comicvine_cv_remove_html_tables) + if settings[0].comicvine_cv_use_series_start_as_volume: + cls.use_series_start_as_volume = settings[0].comicvine_cv_use_series_start_as_volume + if settings[0].comicvine_cv_api_key: + cls.api_key = settings[0].comicvine_cv_api_key + if settings[0].comicvine_cv_url: + cls.api_url = settings[0].comicvine_cv_url def check_api_key(self, key: str, url: str) -> bool: if not url: diff --git a/tests/conftest.py b/tests/conftest.py index 7b20081..a2af911 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -118,12 +118,6 @@ def comicvine_api( cv = comictalker.talkers.comicvine.ComicVineTalker( version=mock_version[0], cache_folder=options[0].runtime_config.user_cache_dir, - api_url="", - api_key="", - series_match_thresh=90, - remove_html_tables=False, - use_series_start_as_volume=False, - wait_on_ratelimit=False, ) return cv