Generate a namespace object for typing settngs

This commit is contained in:
Timmy Welch 2023-06-09 16:20:00 -07:00
parent a912c7392b
commit 783e10a9a1
28 changed files with 251 additions and 161 deletions

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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 = []

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

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

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

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

@ -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):
"<a href='https://github.com/comictagger/comictagger'>"
"https://github.com/comictagger/comictagger</a>",
)
return
row = self.twList.rowCount()
self.twList.insertRow(row)

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

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

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

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

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

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

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

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