From 31cf687e2f9278a7c774f4a8b2233c8da54cac33 Mon Sep 17 00:00:00 2001 From: Timmy Welch Date: Thu, 22 Jun 2023 20:11:40 -0700 Subject: [PATCH] Reduce startup time --- comicapi/archivers/__init__.py | 4 +--- comicapi/comicarchive.py | 32 +++++++++++-------------- comicapi/utils.py | 44 +++++++++++++++++++++++----------- comictaggerlib/imagefetcher.py | 23 +++++++++++------- comictaggerlib/main.py | 37 +++++++++++++--------------- comictaggerlib/taggerwindow.py | 4 ++-- comictalker/talker_utils.py | 4 ++-- tests/comicarchive_test.py | 1 + 8 files changed, 81 insertions(+), 68 deletions(-) diff --git a/comicapi/archivers/__init__.py b/comicapi/archivers/__init__.py index c445eb5..8b5b931 100644 --- a/comicapi/archivers/__init__.py +++ b/comicapi/archivers/__init__.py @@ -2,8 +2,6 @@ from __future__ import annotations from comicapi.archivers.archiver import Archiver from comicapi.archivers.folder import FolderArchiver -from comicapi.archivers.rar import RarArchiver -from comicapi.archivers.sevenzip import SevenZipArchiver from comicapi.archivers.zip import ZipArchiver @@ -12,4 +10,4 @@ class UnknownArchiver(Archiver): return "Unknown" -__all__ = ["Archiver", "UnknownArchiver", "FolderArchiver", "RarArchiver", "ZipArchiver", "SevenZipArchiver"] +__all__ = ["Archiver", "UnknownArchiver", "FolderArchiver", "ZipArchiver"] diff --git a/comicapi/comicarchive.py b/comicapi/comicarchive.py index ca089a2..1433de7 100644 --- a/comicapi/comicarchive.py +++ b/comicapi/comicarchive.py @@ -22,8 +22,6 @@ import shutil import sys from typing import cast -import wordninja - from comicapi import filenamelexer, filenameparser, utils from comicapi.archivers import Archiver, UnknownArchiver, ZipArchiver from comicapi.comet import CoMet @@ -31,28 +29,17 @@ from comicapi.comicbookinfo import ComicBookInfo from comicapi.comicinfoxml import ComicInfoXml from comicapi.genericmetadata import GenericMetadata, PageType -if sys.version_info < (3, 10): - from importlib_metadata import entry_points -else: - from importlib.metadata import entry_points - -try: - from PIL import Image - - pil_available = True -except ImportError: - pil_available = False - logger = logging.getLogger(__name__) -if not pil_available: - logger.error("PIL unavalable") - archivers: list[type[Archiver]] = [] def load_archive_plugins() -> None: if not archivers: + if sys.version_info < (3, 10): + from importlib_metadata import entry_points + else: + from importlib.metadata import entry_points builtin: list[type[Archiver]] = [] for arch in entry_points(group="comicapi.archiver"): try: @@ -77,6 +64,7 @@ class MetaDataStyle: class ComicArchive: logo_data = b"" + pil_available = True def __init__(self, path: pathlib.Path | str, default_image_path: pathlib.Path | str | None = None) -> None: self.cbi_md: GenericMetadata | None = None @@ -517,7 +505,13 @@ class ComicArchive: if calc_page_sizes: for index, p in enumerate(md.pages): idx = int(p["Image"]) - if pil_available: + if self.pil_available: + try: + from PIL import Image + + self.pil_available = True + except ImportError: + self.pil_available = False if "ImageSize" not in p or "ImageHeight" not in p or "ImageWidth" not in p: data = self.get_page(idx) if data: @@ -552,6 +546,8 @@ class ComicArchive: filename = self.path.name if split_words: + import wordninja + filename = " ".join(wordninja.split(self.path.stem)) + self.path.suffix if complicated_parser: diff --git a/comicapi/utils.py b/comicapi/utils.py index 99bcad9..8e041bd 100644 --- a/comicapi/utils.py +++ b/comicapi/utils.py @@ -25,10 +25,6 @@ from collections.abc import Iterable, Mapping from shutil import which # noqa: F401 from typing import Any -import natsort -import pycountry -import rapidfuzz.fuzz - import comicapi.data try: @@ -43,6 +39,8 @@ logger = logging.getLogger(__name__) def _custom_key(tup): + import natsort + lst = [] for x in natsort.os_sort_keygen()(tup): ret = x @@ -54,6 +52,8 @@ def _custom_key(tup): def os_sorted(lst: Iterable) -> Iterable: + import natsort + key = _custom_key if icu_available or platform.system() == "Windows": key = natsort.os_sort_keygen() @@ -198,6 +198,8 @@ def sanitize_title(text: str, basic: bool = False) -> str: def titles_match(search_title: str, record_title: str, threshold: int = 90) -> bool: + import rapidfuzz.fuzz + sanitized_search = sanitize_title(search_title) sanitized_record = sanitize_title(record_title) ratio = int(rapidfuzz.fuzz.ratio(sanitized_search, sanitized_record)) @@ -221,26 +223,40 @@ def unique_file(file_name: pathlib.Path) -> pathlib.Path: counter += 1 -languages: dict[str | None, str | None] = defaultdict(lambda: None) +_languages: dict[str | None, str | None] = defaultdict(lambda: None) -countries: dict[str | None, str | None] = defaultdict(lambda: None) +_countries: dict[str | None, str | None] = defaultdict(lambda: None) -for c in pycountry.countries: - if "alpha_2" in c._fields: - countries[c.alpha_2] = c.name -for lng in pycountry.languages: - if "alpha_2" in lng._fields: - languages[lng.alpha_2] = lng.name +def countries() -> dict[str | None, str | None]: + if not _countries: + import pycountry + + for c in pycountry.countries: + if "alpha_2" in c._fields: + _countries[c.alpha_2] = c.name + return _countries + + +def languages() -> dict[str | None, str | None]: + if not _languages: + import pycountry + + for lng in pycountry.languages: + if "alpha_2" in lng._fields: + _languages[lng.alpha_2] = lng.name + return _languages def get_language_from_iso(iso: str | None) -> str | None: - return languages[iso] + return languages()[iso] def get_language_iso(string: str | None) -> str | None: if string is None: return None + import pycountry + # Return current string if all else fails lang = string.casefold() @@ -252,7 +268,7 @@ def get_language_iso(string: str | None) -> str | None: def get_country_from_iso(iso: str | None) -> str | None: - return countries[iso] + return countries()[iso] def get_publisher(publisher: str) -> tuple[str, str]: diff --git a/comictaggerlib/imagefetcher.py b/comictaggerlib/imagefetcher.py index 05b1afe..02bcbc0 100644 --- a/comictaggerlib/imagefetcher.py +++ b/comictaggerlib/imagefetcher.py @@ -22,18 +22,15 @@ import pathlib import shutil import sqlite3 as lite import tempfile +from typing import TYPE_CHECKING import requests from comictaggerlib import ctversion -try: +if TYPE_CHECKING: from PyQt5 import QtCore, QtNetwork - qt_available = True -except ImportError: - qt_available = False - logger = logging.getLogger(__name__) @@ -47,6 +44,7 @@ def fetch_complete(url: str, image_data: bytes | QtCore.QByteArray) -> None: class ImageFetcher: image_fetch_complete = fetch_complete + qt_available = True def __init__(self, cache_folder: pathlib.Path) -> None: self.db_file = cache_folder / "image_url_cache.db" @@ -55,10 +53,17 @@ class ImageFetcher: self.user_data = None self.fetched_url = "" + if self.qt_available: + try: + from PyQt5 import QtNetwork + + self.qt_available = True + except ImportError: + self.qt_available = False if not os.path.exists(self.db_file): self.create_image_db() - if qt_available: + if self.qt_available: self.nam = QtNetwork.QNetworkAccessManager() def clear_cache(self) -> None: @@ -79,7 +84,7 @@ class ImageFetcher: # first look in the DB image_data = self.get_image_from_cache(url) # Async for retrieving covers seems to work well - if blocking or not qt_available: + if blocking or not self.qt_available: if not image_data: try: image_data = requests.get(url, headers={"user-agent": "comictagger/" + ctversion.version}).content @@ -91,7 +96,9 @@ class ImageFetcher: ImageFetcher.image_fetch_complete(url, image_data) return image_data - if qt_available: + if self.qt_available: + from PyQt5 import QtCore, QtNetwork + # if we found it, just emit the signal asap if image_data: ImageFetcher.image_fetch_complete(url, QtCore.QByteArray(image_data)) diff --git a/comictaggerlib/main.py b/comictaggerlib/main.py index 9ed7852..455df3e 100644 --- a/comictaggerlib/main.py +++ b/comictaggerlib/main.py @@ -43,14 +43,6 @@ else: logger = logging.getLogger("comictagger") -try: - from comictaggerlib import gui - - qt_available = gui.qt_available -except Exception: - logger.exception("Qt unavailable") - qt_available = False - logger.setLevel(logging.DEBUG) @@ -190,10 +182,6 @@ class App: comicapi.utils.load_publishers() update_publishers(self.config) - if not qt_available and not self.config[0].runtime_no_gui: - self.config[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 not error and ( @@ -215,16 +203,23 @@ class App: True, ) - if self.config[0].runtime_no_gui: - if error and error[1]: - print(f"A fatal error occurred please check the log for more information: {error[0]}") # noqa: T201 - raise SystemExit(1) + if not self.config[0].runtime_no_gui: try: - cli.CLI(self.config[0], talkers).run() - except Exception: - logger.exception("CLI mode failed") - else: - gui.open_tagger_window(talkers, self.config, error) + from comictaggerlib import gui + + return gui.open_tagger_window(talkers, self.config, error) + except ImportError: + self.config[0].runtime_no_gui = True + logger.warning("PyQt5 is not available. ComicTagger is limited to command-line mode.") + + # GUI mode is not available or CLI mode was requested + if error and error[1]: + print(f"A fatal error occurred please check the log for more information: {error[0]}") # noqa: T201 + raise SystemExit(1) + try: + cli.CLI(self.config[0], talkers).run() + except Exception: + logger.exception("CLI mode failed") def main() -> None: diff --git a/comictaggerlib/taggerwindow.py b/comictaggerlib/taggerwindow.py index 3e3b701..33963b4 100644 --- a/comictaggerlib/taggerwindow.py +++ b/comictaggerlib/taggerwindow.py @@ -1398,13 +1398,13 @@ class TaggerWindow(QtWidgets.QMainWindow): # Add the entries to the country combobox self.cbCountry.addItem("", "") - for f in natsort.humansorted(utils.countries.items(), operator.itemgetter(1)): + for f in natsort.humansorted(utils.countries().items(), operator.itemgetter(1)): self.cbCountry.addItem(f[1], f[0]) # Add the entries to the language combobox self.cbLanguage.addItem("", "") - for f in natsort.humansorted(utils.languages.items(), operator.itemgetter(1)): + for f in natsort.humansorted(utils.languages().items(), operator.itemgetter(1)): self.cbLanguage.addItem(f[1], f[0]) # Add the entries to the manga combobox diff --git a/comictalker/talker_utils.py b/comictalker/talker_utils.py index eba47e1..b1b94c9 100644 --- a/comictalker/talker_utils.py +++ b/comictalker/talker_utils.py @@ -18,8 +18,6 @@ import posixpath import re from urllib.parse import urlsplit -from bs4 import BeautifulSoup - from comicapi import utils from comicapi.genericmetadata import GenericMetadata from comicapi.issuestring import IssueString @@ -132,6 +130,8 @@ def cleanup_html(string: str, remove_html_tables: bool = False) -> str: """Cleans HTML code from any text. Will remove any HTML tables with remove_html_tables""" if string is None: return "" + from bs4 import BeautifulSoup + # find any tables soup = BeautifulSoup(string, "html.parser") tables = soup.findAll("table") diff --git a/tests/comicarchive_test.py b/tests/comicarchive_test.py index cec116a..2c49525 100644 --- a/tests/comicarchive_test.py +++ b/tests/comicarchive_test.py @@ -6,6 +6,7 @@ import shutil import pytest from importlib_metadata import entry_points +import comicapi.archivers.rar import comicapi.comicarchive import comicapi.genericmetadata from testing.filenames import datadir