From 783e10a9a180a6dd219c738b9845302c15dfce4c Mon Sep 17 00:00:00 2001 From: Timmy Welch Date: Fri, 9 Jun 2023 16:20:00 -0700 Subject: [PATCH] Generate a namespace object for typing settngs --- .pre-commit-config.yaml | 2 +- build-tools/generate_settngs.py | 19 ++++ comicapi/utils.py | 8 +- comictaggerlib/autotagmatchwindow.py | 4 +- comictaggerlib/autotagstartwindow.py | 4 +- comictaggerlib/cbltransformer.py | 5 +- comictaggerlib/cli.py | 16 +-- comictaggerlib/ctsettings/__init__.py | 2 + comictaggerlib/ctsettings/commandline.py | 31 +++--- comictaggerlib/ctsettings/file.py | 25 ++++- comictaggerlib/ctsettings/plugin.py | 12 +- .../ctsettings/settngs_namespace.py | 105 ++++++++++++++++++ comictaggerlib/ctsettings/types.py | 74 ++---------- comictaggerlib/fileselectionlist.py | 4 +- comictaggerlib/gui.py | 3 +- comictaggerlib/issueidentifier.py | 4 +- comictaggerlib/issueselectionwindow.py | 4 +- comictaggerlib/main.py | 21 ++-- comictaggerlib/matchselectionwindow.py | 4 +- comictaggerlib/renamewindow.py | 4 +- comictaggerlib/seriesselectionwindow.py | 4 +- comictaggerlib/settingswindow.py | 9 +- comictaggerlib/taggerwindow.py | 3 +- comictaggerlib/ui/talkeruigenerator.py | 13 ++- comictaggerlib/versionchecker.py | 6 +- setup.cfg | 3 +- tests/comiccacher_test.py | 2 +- tests/conftest.py | 21 ++-- 28 files changed, 251 insertions(+), 161 deletions(-) create mode 100644 build-tools/generate_settngs.py create mode 100644 comictaggerlib/ctsettings/settngs_namespace.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fa79b71..3736a8c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,6 +41,6 @@ repos: rev: v1.2.0 hooks: - id: mypy - additional_dependencies: [types-setuptools, types-requests] + additional_dependencies: [types-setuptools, types-requests, settngs>=0.7.1] ci: skip: [mypy] diff --git a/build-tools/generate_settngs.py b/build-tools/generate_settngs.py new file mode 100644 index 0000000..6fd90d0 --- /dev/null +++ b/build-tools/generate_settngs.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +import pathlib + +import settngs + +import comictaggerlib.main + + +def generate() -> str: + app = comictaggerlib.main.App() + app.register_settings() + return settngs.generate_ns(app.manager.definitions) + + +if __name__ == "__main__": + src = generate() + pathlib.Path("./comictaggerlib/ctsettings/settngs_namespace.py").write_text(src) + print(src, end="") diff --git a/comicapi/utils.py b/comicapi/utils.py index 7499c9a..090c59b 100644 --- a/comicapi/utils.py +++ b/comicapi/utils.py @@ -261,7 +261,7 @@ def get_publisher(publisher: str) -> tuple[str, str]: if ok: break - return (imprint, publisher) + return imprint, publisher def update_publishers(new_publishers: Mapping[str, Mapping[str, str]]) -> None: @@ -290,11 +290,11 @@ class ImprintDict(dict): # type: ignore def __getitem__(self, k: str) -> tuple[str, str, bool]: item = super().__getitem__(k.casefold()) if k.casefold() == self.publisher.casefold(): - return ("", self.publisher, True) + return "", self.publisher, True if item is None: - return ("", k, False) + return "", k, False else: - return (item, self.publisher, True) + return item, self.publisher, True def copy(self) -> ImprintDict: return ImprintDict(self.publisher, super().copy()) diff --git a/comictaggerlib/autotagmatchwindow.py b/comictaggerlib/autotagmatchwindow.py index 2abd2c5..94eaf1c 100644 --- a/comictaggerlib/autotagmatchwindow.py +++ b/comictaggerlib/autotagmatchwindow.py @@ -19,12 +19,12 @@ import logging import os from typing import Callable -import settngs from PyQt5 import QtCore, QtGui, QtWidgets, uic from comicapi.comicarchive import MetaDataStyle from comicapi.genericmetadata import GenericMetadata from comictaggerlib.coverimagewidget import CoverImageWidget +from comictaggerlib.ctsettings import ct_ns from comictaggerlib.resulttypes import IssueResult, MultipleMatch from comictaggerlib.ui import ui_path from comictaggerlib.ui.qtutils import reduce_widget_font_size @@ -40,7 +40,7 @@ class AutoTagMatchWindow(QtWidgets.QDialog): match_set_list: list[MultipleMatch], style: int, fetch_func: Callable[[IssueResult], GenericMetadata], - config: settngs.Namespace, + config: ct_ns, talker: ComicTalker, ) -> None: super().__init__(parent) diff --git a/comictaggerlib/autotagstartwindow.py b/comictaggerlib/autotagstartwindow.py index efb3c29..94380e8 100644 --- a/comictaggerlib/autotagstartwindow.py +++ b/comictaggerlib/autotagstartwindow.py @@ -17,16 +17,16 @@ from __future__ import annotations import logging -import settngs from PyQt5 import QtCore, QtWidgets, uic +from comictaggerlib.ctsettings import ct_ns from comictaggerlib.ui import ui_path logger = logging.getLogger(__name__) class AutoTagStartWindow(QtWidgets.QDialog): - def __init__(self, parent: QtWidgets.QWidget, config: settngs.Namespace, msg: str) -> None: + def __init__(self, parent: QtWidgets.QWidget, config: ct_ns, msg: str) -> None: super().__init__(parent) uic.loadUi(ui_path / "autotagstartwindow.ui", self) diff --git a/comictaggerlib/cbltransformer.py b/comictaggerlib/cbltransformer.py index a1aa714..c37fff3 100644 --- a/comictaggerlib/cbltransformer.py +++ b/comictaggerlib/cbltransformer.py @@ -17,15 +17,14 @@ from __future__ import annotations import logging -import settngs - from comicapi.genericmetadata import CreditMetadata, GenericMetadata +from comictaggerlib.ctsettings import ct_ns logger = logging.getLogger(__name__) class CBLTransformer: - def __init__(self, metadata: GenericMetadata, config: settngs.Namespace) -> None: + def __init__(self, metadata: GenericMetadata, config: ct_ns) -> None: self.metadata = metadata self.config = config diff --git a/comictaggerlib/cli.py b/comictaggerlib/cli.py index 405bc2c..638af19 100644 --- a/comictaggerlib/cli.py +++ b/comictaggerlib/cli.py @@ -23,13 +23,12 @@ import sys from datetime import datetime from pprint import pprint -import settngs - from comicapi import utils from comicapi.comicarchive import ComicArchive, MetaDataStyle from comicapi.genericmetadata import GenericMetadata from comictaggerlib import ctversion from comictaggerlib.cbltransformer import CBLTransformer +from comictaggerlib.ctsettings import ct_ns from comictaggerlib.filerenamer import FileRenamer, get_rename_dir from comictaggerlib.graphics import graphics_path from comictaggerlib.issueidentifier import IssueIdentifier @@ -40,7 +39,7 @@ logger = logging.getLogger(__name__) class CLI: - def __init__(self, config: settngs.Namespace, talkers: dict[str, ComicTalker]) -> None: + def __init__(self, config: ct_ns, talkers: dict[str, ComicTalker]) -> None: self.config = config self.talkers = talkers self.batch_mode = False @@ -171,14 +170,14 @@ class CLI: self.display_match_set_for_choice(label, match_set) def run(self) -> None: - if len(self.config.runtime_file_list) < 1: + if len(self.config.runtime_files) < 1: logger.error("You must specify at least one filename. Use the -h option for more info") return match_results = OnlineMatchResults() - self.batch_mode = len(self.config.runtime_file_list) > 1 + self.batch_mode = len(self.config.runtime_files) > 1 - for f in self.config.runtime_file_list: + for f in self.config.runtime_files: self.process_file_cli(f, match_results) sys.stdout.flush() @@ -318,7 +317,7 @@ class CLI: md = GenericMetadata() logger.error("Failed to load metadata for %s: %s", ca.path, e) - if self.config.apply_transform_on_bulk_operation_ndetadata_style == MetaDataStyle.CBI: + if self.config.cbl_apply_transform_on_bulk_operation == MetaDataStyle.CBI: md = CBLTransformer(md, self.config).apply() if not ca.write_metadata(md, metadata_style): @@ -342,7 +341,7 @@ class CLI: md = self.create_local_metadata(ca) if md.issue is None or md.issue == "": - if self.config.runtime_assume_issue_one: + if self.config.autotag_assume_1_if_no_issue_num: md.issue = "1" # now, search online @@ -488,6 +487,7 @@ class CLI: return except Exception: logger.exception("Formatter failure: %s metadata: %s", self.config.rename_template, renamer.metadata) + return folder = get_rename_dir(ca, self.config.rename_dir if self.config.rename_move_to_dir else None) diff --git a/comictaggerlib/ctsettings/__init__.py b/comictaggerlib/ctsettings/__init__.py index 88fc1b3..d440907 100644 --- a/comictaggerlib/ctsettings/__init__.py +++ b/comictaggerlib/ctsettings/__init__.py @@ -7,6 +7,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.settngs_namespace import settngs_namespace as ct_ns from comictaggerlib.ctsettings.types import ComicTaggerPaths from comictalker import ComicTalker @@ -21,4 +22,5 @@ __all__ = [ "validate_file_settings", "validate_plugin_settings", "ComicTaggerPaths", + "ct_ns", ] diff --git a/comictaggerlib/ctsettings/commandline.py b/comictaggerlib/ctsettings/commandline.py index e9e8376..8f10eef 100644 --- a/comictaggerlib/ctsettings/commandline.py +++ b/comictaggerlib/ctsettings/commandline.py @@ -25,14 +25,20 @@ import settngs from comicapi import utils from comicapi.genericmetadata import GenericMetadata from comictaggerlib import ctversion -from comictaggerlib.ctsettings.types import ComicTaggerPaths, metadata_type, parse_metadata_from_string +from comictaggerlib.ctsettings.settngs_namespace import settngs_namespace as ct_ns +from comictaggerlib.ctsettings.types import ( + ComicTaggerPaths, + metadata_type, + metadata_type_single, + parse_metadata_from_string, +) logger = logging.getLogger(__name__) def initial_commandline_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(add_help=False) - # Ensure this stays up to date with register_settings + # Ensure this stays up to date with register_runtime parser.add_argument( "--config", help="Config directory defaults to ~/.ComicTagger\non Linux/Mac and %%APPDATA%% on Windows\n", @@ -43,7 +49,7 @@ def initial_commandline_parser() -> argparse.ArgumentParser: return parser -def register_settings(parser: settngs.Manager) -> None: +def register_runtime(parser: settngs.Manager) -> None: parser.add_setting( "--config", help="Config directory defaults to ~/.Config/ComicTagger\non Linux, ~/Library/Application Support/ComicTagger on Mac and %%APPDATA%%\\ComicTagger on Windows\n", @@ -83,7 +89,7 @@ def register_settings(parser: settngs.Manager) -> None: parser.add_setting( "--id", dest="issue_id", - type=int, + type=str, help="""Use the issue ID when searching online.\nOverrides all other metadata.\n\n""", file=False, ) @@ -177,6 +183,7 @@ def register_settings(parser: settngs.Manager) -> None: help="""Apply metadata to already tagged archives (relevant for -s or -c).""", file=False, ) + parser.add_setting("--no-gui", action="store_true", help="Do not open the GUI, force the commandline", file=False) parser.add_setting("files", nargs="*", file=False) @@ -200,7 +207,7 @@ def register_commands(parser: settngs.Manager) -> None: parser.add_setting( "-c", "--copy", - type=metadata_type, + type=metadata_type_single, metavar="{CR,CBL,COMET}", help="Copy the specified source tag block to\ndestination style specified via -t\n(potentially lossy operation).\n\n", file=False, @@ -236,12 +243,10 @@ def register_commands(parser: settngs.Manager) -> None: def register_commandline_settings(parser: settngs.Manager) -> None: parser.add_group("commands", register_commands, True) - parser.add_persistent_group("runtime", register_settings) + parser.add_persistent_group("runtime", register_runtime) -def validate_commandline_settings( - config: settngs.Config[settngs.Namespace], parser: settngs.Manager -) -> settngs.Config[settngs.Namespace]: +def validate_commandline_settings(config: settngs.Config[ct_ns], parser: settngs.Manager) -> settngs.Config[ct_ns]: if config[0].commands_version: parser.exit( status=1, @@ -258,6 +263,7 @@ def validate_commandline_settings( config[0].commands_rename, config[0].commands_export_to_zip, config[0].commands_only_set_cv_key, + config[0].runtime_no_gui, ] ) @@ -282,14 +288,9 @@ def validate_commandline_settings( if config[0].commands_copy: if not config[0].runtime_type: parser.exit(message="Please specify the type to copy to with -t\n", status=1) - if len(config[0].commands_copy) > 1: - parser.exit(message="Please specify only one type to copy to with -c\n", status=1) - config[0].commands_copy = config[0].commands_copy[0] if config[0].runtime_recursive: - config[0].runtime_file_list = utils.get_recursive_filelist(config[0].runtime_files) - else: - config[0].runtime_file_list = config[0].runtime_files + config[0].runtime_files = utils.get_recursive_filelist(config[0].runtime_files) # take a crack at finding rar exe if it's not in the path if not utils.which("rar"): diff --git a/comictaggerlib/ctsettings/file.py b/comictaggerlib/ctsettings/file.py index 7fd8140..fb3e0d4 100644 --- a/comictaggerlib/ctsettings/file.py +++ b/comictaggerlib/ctsettings/file.py @@ -5,7 +5,7 @@ import uuid import settngs -from comictaggerlib.ctsettings.types import AppendAction +from comictaggerlib.ctsettings.settngs_namespace import settngs_namespace as ct_ns from comictaggerlib.defaults import DEFAULT_REPLACEMENTS, Replacement, Replacements @@ -43,8 +43,9 @@ def identifier(parser: settngs.Manager) -> None: parser.add_setting( "--publisher-filter", default=["Panini Comics", "Abril", "Planeta DeAgostini", "Editorial Televisa", "Dino Comics"], - action=AppendAction, - help="When enabled filters the listed publishers from all search results", + action="extend", + nargs="+", + help="When enabled, filters the listed publishers from all search results. Ending a publisher with a '-' removes a publisher from this list", ) parser.add_setting("--series-match-search-thresh", default=90, type=int) parser.add_setting( @@ -216,8 +217,22 @@ def autotag(parser: settngs.Manager) -> None: ) -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()] +def validate_file_settings(config: settngs.Config[ct_ns]) -> settngs.Config[ct_ns]: + new_filter = [] + remove = [] + for x in config[0].identifier_publisher_filter: + x = x.strip() + if x: # ignore empty arguments + if x[-1] == "-": # this publisher needs to be removed. We remove after all publishers have been enumerated + remove.append(x.strip("-")) + else: + if x not in new_filter: + new_filter.append(x) + for x in remove: # remove publishers + if x in new_filter: + new_filter.remove(x) + config[0].identifier_publisher_filter = new_filter + config[0].rename_replacements = Replacements( [Replacement(x[0], x[1], x[2]) for x in config[0].rename_replacements[0]], [Replacement(x[0], x[1], x[2]) for x in config[0].rename_replacements[1]], diff --git a/comictaggerlib/ctsettings/plugin.py b/comictaggerlib/ctsettings/plugin.py index 6debffa..8cd1d4e 100644 --- a/comictaggerlib/ctsettings/plugin.py +++ b/comictaggerlib/ctsettings/plugin.py @@ -2,11 +2,13 @@ from __future__ import annotations import logging import os +from typing import cast import settngs import comicapi.comicarchive import comictaggerlib.ctsettings +from comictaggerlib.ctsettings.settngs_namespace import settngs_namespace as ct_ns logger = logging.getLogger("comictagger") @@ -47,10 +49,10 @@ def register_talker_settings(manager: settngs.Manager) -> None: logger.exception("Failed to register settings for %s", talker_id) -def validate_archive_settings(config: settngs.Config[settngs.Namespace]) -> settngs.Config[settngs.Namespace]: +def validate_archive_settings(config: settngs.Config[ct_ns]) -> settngs.Config[ct_ns]: if "archiver" not in config[1]: return config - cfg = settngs.normalize_config(config, file=True, cmdline=True, defaults=False) + cfg = settngs.normalize_config(config, file=True, cmdline=True, default=False) for archiver in comicapi.comicarchive.archivers: exe_name = settngs.sanitize_name(archiver.exe) if exe_name in cfg[0]["archiver"] and cfg[0]["archiver"][exe_name]: @@ -62,7 +64,7 @@ def validate_archive_settings(config: settngs.Config[settngs.Namespace]) -> sett return config -def validate_talker_settings(config: settngs.Config[settngs.Namespace]) -> settngs.Config[settngs.Namespace]: +def validate_talker_settings(config: settngs.Config[ct_ns]) -> settngs.Config[ct_ns]: # Apply talker settings from config file cfg = settngs.normalize_config(config, True, True) for talker_id, talker in list(comictaggerlib.ctsettings.talkers.items()): @@ -73,10 +75,10 @@ def validate_talker_settings(config: settngs.Config[settngs.Namespace]) -> settn del comictaggerlib.ctsettings.talkers[talker_id] logger.exception("Failed to initialize talker settings: %s", e) - return settngs.get_namespace(cfg) + return cast(settngs.Config[ct_ns], settngs.get_namespace(cfg, file=True, cmdline=True)) -def validate_plugin_settings(config: settngs.Config[settngs.Namespace]) -> settngs.Config[settngs.Namespace]: +def validate_plugin_settings(config: settngs.Config[ct_ns]) -> settngs.Config[ct_ns]: config = validate_archive_settings(config) config = validate_talker_settings(config) return config diff --git a/comictaggerlib/ctsettings/settngs_namespace.py b/comictaggerlib/ctsettings/settngs_namespace.py new file mode 100644 index 0000000..453d56a --- /dev/null +++ b/comictaggerlib/ctsettings/settngs_namespace.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +import settngs + +import comicapi.genericmetadata +import comictaggerlib.ctsettings.types +import comictaggerlib.defaults + + +class settngs_namespace(settngs.TypedNS): + commands_version: bool + commands_print: bool + commands_delete: bool + commands_copy: int + commands_save: bool + commands_rename: bool + commands_export_to_zip: bool + commands_only_set_cv_key: bool + + runtime_config: comictaggerlib.ctsettings.types.ComicTaggerPaths + runtime_verbose: int + runtime_abort_on_conflict: bool + runtime_delete_after_zip_export: bool + runtime_parse_filename: bool + runtime_issue_id: str + runtime_online: bool + runtime_metadata: comicapi.genericmetadata.GenericMetadata + runtime_interactive: bool + runtime_abort_on_low_confidence: bool + runtime_summary: bool + runtime_raw: bool + runtime_recursive: bool + runtime_script: str + runtime_split_words: bool + runtime_dryrun: bool + runtime_darkmode: bool + runtime_glob: bool + runtime_quiet: bool + runtime_type: list[int] + runtime_overwrite: bool + runtime_no_gui: bool + runtime_files: list[str] + + general_check_for_new_version: bool + + internal_install_id: str + internal_save_data_style: int + internal_load_data_style: int + internal_last_opened_folder: str + internal_window_width: int + internal_window_height: int + internal_window_x: int + internal_window_y: int + internal_form_width: int + internal_list_width: int + internal_sort_column: int + internal_sort_direction: int + + identifier_series_match_identify_thresh: int + identifier_border_crop_percent: int + identifier_publisher_filter: list[str] + identifier_series_match_search_thresh: int + identifier_clear_metadata_on_import: bool + identifier_auto_imprint: bool + identifier_sort_series_by_year: bool + identifier_exact_series_matches_first: bool + identifier_always_use_publisher_filter: bool + identifier_clear_form_before_populating: bool + + dialog_show_disclaimer: bool + dialog_dont_notify_about_this_version: str + dialog_ask_about_usage_stats: bool + + filename_complicated_parser: bool + filename_remove_c2c: bool + filename_remove_fcbd: bool + filename_remove_publisher: bool + + talker_source: str + + cbl_assume_lone_credit_is_primary: bool + cbl_copy_characters_to_tags: bool + cbl_copy_teams_to_tags: bool + cbl_copy_locations_to_tags: bool + cbl_copy_storyarcs_to_tags: bool + cbl_copy_notes_to_comments: bool + cbl_copy_weblink_to_comments: bool + cbl_apply_transform_on_import: bool + cbl_apply_transform_on_bulk_operation: bool + + rename_template: str + rename_issue_number_padding: int + rename_use_smart_string_cleanup: bool + rename_set_extension_based_on_archive: bool + rename_dir: str + rename_move_to_dir: bool + rename_strict: bool + rename_replacements: comictaggerlib.defaults.Replacements + + autotag_save_on_low_confidence: bool + autotag_dont_use_year_when_identifying: bool + autotag_assume_1_if_no_issue_num: bool + autotag_ignore_leading_numbers_in_filename: bool + autotag_remove_archive_after_successful_match: bool + autotag_wait_and_retry_on_rate_limit: bool diff --git a/comictaggerlib/ctsettings/types.py b/comictaggerlib/ctsettings/types.py index aee408d..07b00e0 100644 --- a/comictaggerlib/ctsettings/types.py +++ b/comictaggerlib/ctsettings/types.py @@ -2,8 +2,6 @@ from __future__ import annotations import argparse import pathlib -from collections.abc import Sequence -from typing import Any, Callable from appdirs import AppDirs @@ -59,6 +57,13 @@ class ComicTaggerPaths(AppDirs): return pathlib.Path(super().site_config_dir) +def metadata_type_single(types: str) -> int: + result = metadata_type(types) + if len(result) > 1: + raise argparse.ArgumentTypeError(f"invalid choice: {result} (only one metadata style allowed)") + return result[0] + + def metadata_type(types: str) -> list[int]: result = [] types = types.casefold() @@ -71,71 +76,6 @@ def metadata_type(types: str) -> list[int]: return result -def _copy_items(items: Sequence[Any] | None) -> Sequence[Any]: - if items is None: - return [] - # The copy module is used only in the 'append' and 'append_const' - # actions, and it is needed only when the default value isn't a list. - # Delay its import for speeding up the common case. - if type(items) is list: - return items[:] - import copy - - return copy.copy(items) - - -class AppendAction(argparse.Action): - def __init__( - self, - option_strings: list[str], - dest: str, - nargs: str | None = None, - const: Any = None, - default: Any = None, - type: Callable[[str], Any] | None = None, # noqa: A002 - choices: list[Any] | None = None, - required: bool = False, - help: str | None = None, # noqa: A002 - metavar: str | None = None, - ): - self.called = False - if nargs == 0: - raise ValueError( - "nargs for append actions must be != 0; if arg " - "strings are not supplying the value to append, " - "the append const action may be more appropriate" - ) - if const is not None and nargs != argparse.OPTIONAL: - raise ValueError("nargs must be %r to supply const" % argparse.OPTIONAL) - super().__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar, - ) - - def __call__( - self, - parser: argparse.ArgumentParser, - namespace: argparse.Namespace, - values: str | Sequence[Any] | None, - option_string: str | None = None, - ) -> None: - if values: - if not self.called: - setattr(namespace, self.dest, []) - items = getattr(namespace, self.dest, None) - items = _copy_items(items) - items.append(values) # type: ignore - setattr(namespace, self.dest, items) - - def parse_metadata_from_string(mdstr: str) -> GenericMetadata: """The metadata string is a comma separated list of name-value pairs The names match the attributes of the internal metadata struct (for now) diff --git a/comictaggerlib/fileselectionlist.py b/comictaggerlib/fileselectionlist.py index a8a6970..7c7ecb5 100644 --- a/comictaggerlib/fileselectionlist.py +++ b/comictaggerlib/fileselectionlist.py @@ -20,11 +20,11 @@ import os import platform from typing import Callable, cast -import settngs from PyQt5 import QtCore, QtWidgets, uic from comicapi import utils from comicapi.comicarchive import ComicArchive +from comictaggerlib.ctsettings import ct_ns from comictaggerlib.graphics import graphics_path from comictaggerlib.optionalmsgdialog import OptionalMessageDialog from comictaggerlib.settingswindow import linuxRarHelp, macRarHelp, windowsRarHelp @@ -57,7 +57,7 @@ class FileSelectionList(QtWidgets.QWidget): dataColNum = fileColNum def __init__( - self, parent: QtWidgets.QWidget, config: settngs.Namespace, dirty_flag_verification: Callable[[str, str], bool] + self, parent: QtWidgets.QWidget, config: ct_ns, dirty_flag_verification: Callable[[str, str], bool] ) -> None: super().__init__(parent) diff --git a/comictaggerlib/gui.py b/comictaggerlib/gui.py index 541c647..ccd33aa 100644 --- a/comictaggerlib/gui.py +++ b/comictaggerlib/gui.py @@ -9,6 +9,7 @@ import types import settngs +from comictaggerlib.ctsettings import ct_ns from comictaggerlib.graphics import graphics_path from comictalker.comictalker import ComicTalker @@ -83,7 +84,7 @@ except ImportError: def open_tagger_window( - talkers: dict[str, ComicTalker], config: settngs.Config[settngs.Namespace], error: tuple[str, bool] | None + talkers: dict[str, ComicTalker], config: settngs.Config[ct_ns], error: tuple[str, bool] | None ) -> None: os.environ["QtWidgets.QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" args = [] diff --git a/comictaggerlib/issueidentifier.py b/comictaggerlib/issueidentifier.py index 0808b50..d0514b7 100644 --- a/comictaggerlib/issueidentifier.py +++ b/comictaggerlib/issueidentifier.py @@ -20,13 +20,13 @@ import logging import sys from typing import Any, Callable -import settngs from typing_extensions import NotRequired, TypedDict from comicapi import utils from comicapi.comicarchive import ComicArchive from comicapi.genericmetadata import GenericMetadata from comicapi.issuestring import IssueString +from comictaggerlib.ctsettings import ct_ns from comictaggerlib.imagefetcher import ImageFetcher, ImageFetcherException from comictaggerlib.imagehasher import ImageHasher from comictaggerlib.resulttypes import IssueResult @@ -72,7 +72,7 @@ class IssueIdentifier: result_one_good_match = 4 result_multiple_good_matches = 5 - def __init__(self, comic_archive: ComicArchive, config: settngs.Namespace, talker: ComicTalker) -> None: + def __init__(self, comic_archive: ComicArchive, config: ct_ns, talker: ComicTalker) -> None: self.config = config self.talker = talker self.comic_archive: ComicArchive = comic_archive diff --git a/comictaggerlib/issueselectionwindow.py b/comictaggerlib/issueselectionwindow.py index f1b9e9c..58101b2 100644 --- a/comictaggerlib/issueselectionwindow.py +++ b/comictaggerlib/issueselectionwindow.py @@ -17,11 +17,11 @@ from __future__ import annotations import logging -import settngs from PyQt5 import QtCore, QtGui, QtWidgets, uic from comicapi.issuestring import IssueString from comictaggerlib.coverimagewidget import CoverImageWidget +from comictaggerlib.ctsettings import ct_ns from comictaggerlib.ui import ui_path from comictaggerlib.ui.qtutils import reduce_widget_font_size from comictalker.comictalker import ComicTalker, TalkerError @@ -42,7 +42,7 @@ class IssueSelectionWindow(QtWidgets.QDialog): def __init__( self, parent: QtWidgets.QWidget, - config: settngs.Namespace, + config: ct_ns, talker: ComicTalker, series_id: str, issue_number: str, diff --git a/comictaggerlib/main.py b/comictaggerlib/main.py index ea8b070..9ed7852 100644 --- a/comictaggerlib/main.py +++ b/comictaggerlib/main.py @@ -24,12 +24,15 @@ import os import signal import subprocess import sys +from typing import cast import settngs -import comicapi +import comicapi.comicarchive +import comicapi.utils import comictalker from comictaggerlib import cli, ctsettings +from comictaggerlib.ctsettings import ct_ns from comictaggerlib.ctversion import version from comictaggerlib.log import setup_logging @@ -95,7 +98,7 @@ def configure_locale() -> None: sys.stdin.reconfigure(encoding=sys.getdefaultencoding()) # type: ignore[attr-defined] -def update_publishers(config: settngs.Config[settngs.Namespace]) -> None: +def update_publishers(config: settngs.Config[ct_ns]) -> None: json_file = config[0].runtime_config.user_config_dir / "publishers.json" if json_file.exists(): try: @@ -108,7 +111,7 @@ class App: """docstring for App""" def __init__(self) -> None: - self.config: settngs.Config[settngs.Namespace] + self.config: settngs.Config[ct_ns] self.initial_arg_parser = ctsettings.initial_commandline_parser() self.config_load_success = False @@ -141,9 +144,11 @@ class App: ctsettings.register_file_settings(self.manager) ctsettings.register_plugin_settings(self.manager) - 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) + def parse_settings(self, config_paths: ctsettings.ComicTaggerPaths, *args: str) -> settngs.Config[ct_ns]: + cfg, self.config_load_success = self.manager.parse_config( + config_paths.user_config_dir / "settings.json", list(args) or None + ) + config = cast(settngs.Config[ct_ns], self.manager.get_namespace(cfg, file=True, cmdline=True)) config = ctsettings.validate_commandline_settings(config, self.manager) config = ctsettings.validate_file_settings(config) @@ -192,8 +197,8 @@ class App: # manage the CV API key # None comparison is used so that the empty string can unset the value if not error and ( - self.config[0].talker_comicvine_comicvine_key is not None - or self.config[0].talker_comicvine_comicvine_url is not None + self.config[0].talker_comicvine_comicvine_key is not None # type: ignore[attr-defined] + or self.config[0].talker_comicvine_comicvine_url is not None # type: ignore[attr-defined] ): settings_path = self.config[0].runtime_config.user_config_dir / "settings.json" if self.config_load_success: diff --git a/comictaggerlib/matchselectionwindow.py b/comictaggerlib/matchselectionwindow.py index 6383eeb..d69211b 100644 --- a/comictaggerlib/matchselectionwindow.py +++ b/comictaggerlib/matchselectionwindow.py @@ -18,11 +18,11 @@ from __future__ import annotations import logging import os -import settngs from PyQt5 import QtCore, QtWidgets, uic from comicapi.comicarchive import ComicArchive from comictaggerlib.coverimagewidget import CoverImageWidget +from comictaggerlib.ctsettings import ct_ns from comictaggerlib.resulttypes import IssueResult from comictaggerlib.ui import ui_path from comictaggerlib.ui.qtutils import reduce_widget_font_size @@ -37,7 +37,7 @@ class MatchSelectionWindow(QtWidgets.QDialog): parent: QtWidgets.QWidget, matches: list[IssueResult], comic_archive: ComicArchive, - config: settngs.Namespace, + config: ct_ns, talker: ComicTalker, ) -> None: super().__init__(parent) diff --git a/comictaggerlib/renamewindow.py b/comictaggerlib/renamewindow.py index 9bc31a4..7531d83 100644 --- a/comictaggerlib/renamewindow.py +++ b/comictaggerlib/renamewindow.py @@ -23,6 +23,7 @@ from PyQt5 import QtCore, QtWidgets, uic from comicapi import utils from comicapi.comicarchive import ComicArchive, MetaDataStyle from comicapi.genericmetadata import GenericMetadata +from comictaggerlib.ctsettings import ct_ns from comictaggerlib.filerenamer import FileRenamer, get_rename_dir from comictaggerlib.settingswindow import SettingsWindow from comictaggerlib.ui import ui_path @@ -38,7 +39,7 @@ class RenameWindow(QtWidgets.QDialog): parent: QtWidgets.QWidget, comic_archive_list: list[ComicArchive], data_style: int, - config: settngs.Config[settngs.Namespace], + config: settngs.Config[ct_ns], talkers: dict[str, ComicTalker], ) -> None: super().__init__(parent) @@ -124,6 +125,7 @@ class RenameWindow(QtWidgets.QDialog): "" "https://github.com/comictagger/comictagger", ) + return row = self.twList.rowCount() self.twList.insertRow(row) diff --git a/comictaggerlib/seriesselectionwindow.py b/comictaggerlib/seriesselectionwindow.py index 9a208bb..938975b 100644 --- a/comictaggerlib/seriesselectionwindow.py +++ b/comictaggerlib/seriesselectionwindow.py @@ -19,7 +19,6 @@ import itertools import logging from collections import deque -import settngs from PyQt5 import QtCore, QtGui, QtWidgets, uic from PyQt5.QtCore import pyqtSignal @@ -27,6 +26,7 @@ from comicapi import utils from comicapi.comicarchive import ComicArchive from comicapi.genericmetadata import GenericMetadata from comictaggerlib.coverimagewidget import CoverImageWidget +from comictaggerlib.ctsettings import ct_ns from comictaggerlib.issueidentifier import IssueIdentifier from comictaggerlib.issueselectionwindow import IssueSelectionWindow from comictaggerlib.matchselectionwindow import MatchSelectionWindow @@ -106,7 +106,7 @@ class SeriesSelectionWindow(QtWidgets.QDialog): issue_count: int | None, cover_index_list: list[int], comic_archive: ComicArchive | None, - config: settngs.Namespace, + config: ct_ns, talker: ComicTalker, autoselect: bool = False, literal: bool = False, diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py index 1b7b356..a71ff2a 100644 --- a/comictaggerlib/settingswindow.py +++ b/comictaggerlib/settingswindow.py @@ -20,7 +20,7 @@ import logging import os import pathlib import platform -from typing import Any +from typing import Any, cast import settngs from PyQt5 import QtCore, QtGui, QtWidgets, uic @@ -29,6 +29,7 @@ import comictaggerlib.ui.talkeruigenerator from comicapi import utils from comicapi.genericmetadata import md_test from comictaggerlib import ctsettings +from comictaggerlib.ctsettings import ct_ns from comictaggerlib.ctversion import version from comictaggerlib.filerenamer import FileRenamer, Replacement, Replacements from comictaggerlib.imagefetcher import ImageFetcher @@ -133,7 +134,7 @@ Spider-Geddon #1 - New Players; Check In class SettingsWindow(QtWidgets.QDialog): def __init__( - self, parent: QtWidgets.QWidget, config: settngs.Config[settngs.Namespace], talkers: dict[str, ComicTalker] + self, parent: QtWidgets.QWidget, config: settngs.Config[ct_ns], talkers: dict[str, ComicTalker] ) -> None: super().__init__(parent) @@ -413,7 +414,7 @@ class SettingsWindow(QtWidgets.QDialog): 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].archiver_rar: + if self.config[0].archiver_rar: # type: ignore[attr-defined] utils.add_to_path(os.path.dirname(str(self.leRarExePath.text()))) if not str(self.leIssueNumPadding.text()).isdigit(): @@ -480,7 +481,7 @@ class SettingsWindow(QtWidgets.QDialog): QtWidgets.QMessageBox.information(self, self.name, "Cache has been cleared.") def reset_settings(self) -> None: - self.config = settngs.get_namespace(settngs.defaults(self.config[1])) + self.config = cast(settngs.Config[ct_ns], settngs.get_namespace(settngs.defaults(self.config[1]))) self.settings_to_form() QtWidgets.QMessageBox.information(self, self.name, self.name + " have been returned to default values.") diff --git a/comictaggerlib/taggerwindow.py b/comictaggerlib/taggerwindow.py index 2488681..704f953 100644 --- a/comictaggerlib/taggerwindow.py +++ b/comictaggerlib/taggerwindow.py @@ -48,6 +48,7 @@ from comictaggerlib.autotagstartwindow import AutoTagStartWindow from comictaggerlib.cbltransformer import CBLTransformer from comictaggerlib.coverimagewidget import CoverImageWidget from comictaggerlib.crediteditorwindow import CreditEditorWindow +from comictaggerlib.ctsettings import ct_ns from comictaggerlib.exportwindow import ExportConflictOpts, ExportWindow from comictaggerlib.fileselectionlist import FileInfo, FileSelectionList from comictaggerlib.graphics import graphics_path @@ -79,7 +80,7 @@ class TaggerWindow(QtWidgets.QMainWindow): def __init__( self, file_list: list[str], - config: settngs.Config[settngs.Namespace], + config: settngs.Config[ct_ns], talkers: dict[str, ComicTalker], parent: QtWidgets.QWidget | None = None, ) -> None: diff --git a/comictaggerlib/ui/talkeruigenerator.py b/comictaggerlib/ui/talkeruigenerator.py index b6d8f1d..6f6528b 100644 --- a/comictaggerlib/ui/talkeruigenerator.py +++ b/comictaggerlib/ui/talkeruigenerator.py @@ -8,6 +8,7 @@ from typing import Any, NamedTuple import settngs from PyQt5 import QtCore, QtGui, QtWidgets +from comictaggerlib.ctsettings import ct_ns from comictaggerlib.graphics import graphics_path from comictalker.comictalker import ComicTalker @@ -26,7 +27,7 @@ class PasswordEdit(QtWidgets.QLineEdit): Based on this example https://kushaldas.in/posts/creating-password-input-widget-in-pyqt.html by Kushal Das. """ - def __init__(self, show_visibility=True, *args, **kwargs): + def __init__(self, show_visibility: bool = True, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self.visibleIcon = QtGui.QIcon(str(graphics_path / "eye.svg")) @@ -42,7 +43,7 @@ class PasswordEdit(QtWidgets.QLineEdit): self.password_shown = False - def on_toggle_password_Action(self): + def on_toggle_password_Action(self) -> None: if not self.password_shown: self.setEchoMode(QtWidgets.QLineEdit.Normal) self.password_shown = True @@ -58,7 +59,7 @@ class PasswordEdit(QtWidgets.QLineEdit): def generate_api_widgets( talker_id: str, sources: dict[str, QtWidgets.QWidget], - config: settngs.Config[settngs.Namespace], + config: settngs.Config[ct_ns], layout: QtWidgets.QGridLayout, talkers: dict[str, ComicTalker], ) -> None: @@ -168,7 +169,7 @@ def generate_password_textbox(option: settngs.Setting, layout: QtWidgets.QGridLa return widget -def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[settngs.Namespace]) -> None: +def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[ct_ns]) -> None: # Set the active talker via id in sources combo box sources["cbx_select_talker"].setCurrentIndex(sources["cbx_select_talker"].findData(config[0].talker_source)) @@ -187,7 +188,7 @@ def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settn logger.debug("Failed to set value of %s", name) -def form_settings_to_config(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[settngs.Namespace]) -> None: +def form_settings_to_config(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[ct_ns]) -> None: # Source combo box value config[0].talker_source = sources["cbx_select_talker"].currentData() @@ -206,7 +207,7 @@ def form_settings_to_config(sources: dict[str, QtWidgets.QWidget], config: settn def generate_source_option_tabs( comic_talker_tab: QtWidgets.QWidget, - config: settngs.Config[settngs.Namespace], + config: settngs.Config[ct_ns], talkers: dict[str, ComicTalker], ) -> dict[str, QtWidgets.QWidget]: """ diff --git a/comictaggerlib/versionchecker.py b/comictaggerlib/versionchecker.py index d696dbb..72bea35 100644 --- a/comictaggerlib/versionchecker.py +++ b/comictaggerlib/versionchecker.py @@ -40,9 +40,9 @@ class VersionChecker: headers={"user-agent": "comictagger/" + ctversion.version}, ).json() except Exception: - return ("", "") + return "", "" new_version = release["tag_name"] if new_version is None or new_version == "": - return ("", "") - return (new_version.strip(), release["name"]) + return "", "" + return new_version.strip(), release["name"] diff --git a/setup.cfg b/setup.cfg index 035b7cf..4fdc2b6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,7 +42,7 @@ install_requires = pycountry rapidfuzz>=2.12.0 requests==2.* - settngs==0.6.3 + settngs==0.7.1 text2digits typing-extensions>=4.3.0 wordninja @@ -280,3 +280,4 @@ extend-ignore = E203, E501, A003 extend-exclude = venv, scripts, build, dist, comictaggerlib/ctversion.py per-file-ignores = comictaggerlib/cli.py: T20 + build-tools/generate_settngs.py: T20 diff --git a/tests/comiccacher_test.py b/tests/comiccacher_test.py index 878b4d0..6fb77ee 100644 --- a/tests/comiccacher_test.py +++ b/tests/comiccacher_test.py @@ -9,7 +9,7 @@ from testing.comicdata import search_results def test_create_cache(config, mock_version): config, definitions = config comictalker.comiccacher.ComicCacher(config.runtime_config.user_cache_dir, mock_version[0]) - assert (config.runtime_config.user_cache_dir).exists() + assert config.runtime_config.user_cache_dir.exists() def test_search_results(comic_cache): diff --git a/tests/conftest.py b/tests/conftest.py index 27eac51..c1ca4fe 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,7 +9,6 @@ from typing import Any import pytest import requests -import settngs from PIL import Image import comicapi.comicarchive @@ -128,7 +127,7 @@ def mock_version(monkeypatch): monkeypatch.setattr(comictaggerlib.ctversion, "__version__", version) monkeypatch.setattr(comictaggerlib.ctversion, "version_tuple", version_tuple) monkeypatch.setattr(comictaggerlib.ctversion, "__version_tuple__", version_tuple) - yield (version, version_tuple) + yield version, version_tuple @pytest.fixture @@ -154,11 +153,13 @@ def seed_all_publishers(monkeypatch): @pytest.fixture -def config(settings_manager, tmp_path): - comictaggerlib.ctsettings.register_commandline_settings(settings_manager) - comictaggerlib.ctsettings.register_file_settings(settings_manager) - defaults = settings_manager.get_namespace(settings_manager.defaults()) - defaults[0].runtime_config = comictaggerlib.ctsettings.ComicTaggerPaths(tmp_path / "config") +def config(tmp_path): + from comictaggerlib.main import App + + app = App() + app.register_settings() + + defaults = app.parse_settings(comictaggerlib.ctsettings.ComicTaggerPaths(tmp_path / "config"), "") defaults[0].runtime_config.user_data_dir.mkdir(parents=True, exist_ok=True) defaults[0].runtime_config.user_config_dir.mkdir(parents=True, exist_ok=True) defaults[0].runtime_config.user_cache_dir.mkdir(parents=True, exist_ok=True) @@ -167,12 +168,6 @@ def config(settings_manager, tmp_path): yield defaults -@pytest.fixture -def settings_manager(): - manager = settngs.Manager() - yield manager - - @pytest.fixture def comic_cache(config, mock_version) -> Generator[comictalker.comiccacher.ComicCacher, Any, None]: yield comictalker.comiccacher.ComicCacher(config[0].runtime_config.user_cache_dir, mock_version[0])