Skip to content

Commit 46ef8ca

Browse files
Improve questionary titles
Replace rich.prompt with questionary
1 parent fdb9e5f commit 46ef8ca

11 files changed

Lines changed: 418 additions & 380 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/astral-sh/ruff-pre-commit
3-
rev: v0.13.1
3+
rev: v0.13.2
44
hooks:
55
- id: ruff-format
66
- id: ruff-check

perdoo/__init__.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@
66
"get_state_root",
77
"setup_logging",
88
]
9-
__version__ = "0.6.0"
9+
__version__ = "0.6.1"
1010

1111
import logging
1212
import os
1313
from functools import cache
1414
from pathlib import Path
1515

1616
from rich.logging import RichHandler
17-
from rich.traceback import install
1817

1918
from perdoo.console import CONSOLE
2019

@@ -52,15 +51,14 @@ def get_state_root() -> Path:
5251

5352

5453
def setup_logging(debug: bool = False) -> None:
55-
install(show_locals=debug, max_frames=3, console=CONSOLE)
56-
5754
console_handler = RichHandler(
5855
rich_tracebacks=True,
59-
tracebacks_show_locals=True,
56+
tracebacks_show_locals=debug,
57+
tracebacks_max_frames=3,
6058
omit_repeated_times=False,
6159
show_level=True,
6260
show_time=False,
63-
show_path=True,
61+
show_path=debug,
6462
console=CONSOLE,
6563
)
6664
console_handler.setLevel(logging.DEBUG if debug else logging.INFO)

perdoo/__main__.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,6 @@ def load(value: str) -> "SyncOption":
4444
return entry
4545
raise ValueError(f"'{value}' isn't a valid SyncOption")
4646

47-
def __str__(self) -> str:
48-
return self.value
49-
5047

5148
@app.callback(invoke_without_command=True)
5249
def common(
@@ -109,30 +106,30 @@ def _create_search_from_metron(metron_info: MetronInfo) -> Search:
109106
)
110107

111108

112-
def _create_search_from_comic_info(comic_info: ComicInfo) -> Search:
109+
def _create_search_from_comic_info(comic_info: ComicInfo, filename: str) -> Search:
113110
volume = comic_info.volume if comic_info.volume else None
114111
year = volume if volume and volume > 1900 else None
115112
volume = volume if volume and volume < 1900 else None
116113
return Search(
117-
series=SeriesSearch(name=comic_info.series, volume=volume, year=year),
114+
series=SeriesSearch(name=comic_info.series or filename, volume=volume, year=year),
118115
issue=IssueSearch(number=comic_info.number),
119116
)
120117

121118

122-
def _create_search_from_filename(fallback_title: str) -> Search:
123-
series_name = comicfn2dict(fallback_title).get("series", fallback_title).replace("-", " ")
119+
def _create_search_from_filename(filename: str) -> Search:
120+
series_name = comicfn2dict(filename).get("series", filename).replace("-", " ")
124121
return Search(series=SeriesSearch(name=series_name), issue=IssueSearch())
125122

126123

127124
def get_search_details(
128-
metadata: tuple[MetronInfo | None, ComicInfo | None], fallback_title: str
125+
metadata: tuple[MetronInfo | None, ComicInfo | None], filename: str
129126
) -> Search:
130127
metron_info, comic_info = metadata
131128
if metron_info and metron_info.series and metron_info.series.name:
132-
return _create_search_from_metron(metron_info)
129+
return _create_search_from_metron(metron_info=metron_info)
133130
if comic_info and comic_info.series:
134-
return _create_search_from_comic_info(comic_info)
135-
return _create_search_from_filename(fallback_title)
131+
return _create_search_from_comic_info(comic_info=comic_info, filename=filename)
132+
return _create_search_from_filename(filename=filename)
136133

137134

138135
def load_page_info(entry: Comic, comic_info: ComicInfo) -> list[Page]:
@@ -181,7 +178,7 @@ def run(
181178
case_sensitive=False,
182179
help="Sync ComicInfo/MetronInfo with online services.",
183180
),
184-
] = SyncOption.OUTDATED.value,
181+
] = SyncOption.OUTDATED,
185182
skip_clean: Annotated[
186183
bool,
187184
Option(
@@ -251,7 +248,8 @@ def run(
251248
metadata: tuple[MetronInfo | None, ComicInfo | None] = (entry.metron_info, entry.comic_info)
252249

253250
if sync != SyncOption.SKIP:
254-
search = get_search_details(metadata=metadata, fallback_title=entry.path.stem)
251+
search = get_search_details(metadata=metadata, filename=entry.path.stem)
252+
search.filename = entry.path.stem
255253
last_modified = date(1900, 1, 1)
256254
if sync == SyncOption.OUTDATED:
257255
metron_info, _ = metadata

perdoo/comic.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
ComicMetadataError,
2121
MetadataFormat,
2222
)
23+
from natsort import humansorted, ns
2324

2425
from perdoo.metadata import ComicInfo, MetronInfo
2526
from perdoo.settings import Naming
@@ -101,17 +102,17 @@ def read_metadata(self, metadata_format: MetadataFormat) -> None:
101102
raise ComicMetadataError(f"Unsupported metadata format: {metadata_format}")
102103

103104
def convert(self, extension: Literal["cbt", "cbz"]) -> None:
104-
check, archiver = {
105-
"cbt": (self.is_cbt, TarArchiver),
106-
"cbz": (self.is_cbz, ZipArchiver),
107-
}.get(extension)
105+
check, archiver = {"cbt": (self.is_cbt, TarArchiver), "cbz": (self.is_cbz, ZipArchiver)}[
106+
extension
107+
]
108108
if check():
109109
return
110110
output_file = self.path.with_suffix(f".{extension}")
111111
with self.archive as source, archiver(path=output_file) as destination:
112112
LOGGER.debug("Converting '%s' to '%s'", source.path.name, destination.path.name)
113113
if destination.copy_from_archive(other_archive=source):
114114
self._archiver = destination
115+
source.path.unlink()
115116

116117
def clean_archive(self) -> None:
117118
with self.archive as source:
@@ -148,11 +149,14 @@ def _get_filepath_from_metadata(self, naming: Naming) -> str | None:
148149

149150
def _rename_images(self, base_name: str) -> None:
150151
with self.archive as source:
151-
files = [
152-
x
153-
for x in source.get_filename_list()
154-
if Path(x).suffix.lower() in SUPPORTED_IMAGE_EXTENSIONS
155-
]
152+
files = humansorted(
153+
[
154+
x
155+
for x in source.get_filename_list()
156+
if Path(x).suffix.lower() in SUPPORTED_IMAGE_EXTENSIONS
157+
],
158+
alg=ns.NA | ns.G | ns.P,
159+
)
156160
pad_count = len(str(len(files))) if files else 1
157161
for idx, filename in enumerate(files):
158162
img_file = Path(filename)
@@ -171,7 +175,7 @@ def rename(self, naming: Naming, output_folder: Path) -> None:
171175
new_filepath = new_filepath.lstrip("/")
172176

173177
output = output_folder / f"{new_filepath}.cbz"
174-
if output == self.path:
178+
if output.relative_to(output_folder) == self.path.resolve().relative_to(output_folder):
175179
return
176180
if output.exists():
177181
LOGGER.warning("'%s' already exists, skipping", output.relative_to(output_folder))

perdoo/metadata/comic_info.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,12 +343,12 @@ def get_filename(self, settings: Naming) -> str:
343343
"series-id": lambda _: None,
344344
"series-name": lambda x: x.series,
345345
"series-sort-name": lambda _: None,
346-
"series-year": lambda x: x.volume if x.volume > 1900 else None,
346+
"series-year": lambda x: x.volume if x.volume and x.volume > 1900 else None,
347347
"store-date": lambda _: None,
348348
"store-day": lambda _: None,
349349
"store-month": lambda _: None,
350350
"store-year": lambda _: None,
351351
"title": lambda x: x.title,
352352
"upc": lambda _: None,
353-
"volume": lambda x: x.volume if x.volume < 1900 else None,
353+
"volume": lambda x: x.volume if x.volume and x.volume < 1900 else None,
354354
}

perdoo/services/_base.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
__all__ = ["BaseService"]
22

3-
from abc import abstractmethod
3+
from abc import ABC, abstractmethod
44
from typing import Generic, TypeVar
55

66
from perdoo.metadata import ComicInfo, MetronInfo
@@ -10,20 +10,20 @@
1010
C = TypeVar("C")
1111

1212

13-
class BaseService(Generic[S, C]):
13+
class BaseService(ABC, Generic[S, C]):
1414
@abstractmethod
1515
def _search_series(
16-
self, name: str | None, volume: int | None, year: int | None
16+
self, name: str | None, volume: int | None, year: int | None, filename: str
1717
) -> int | None: ...
1818

1919
@abstractmethod
20-
def fetch_series(self, search: SeriesSearch) -> S | None: ...
20+
def fetch_series(self, search: SeriesSearch, filename: str) -> S | None: ...
2121

2222
@abstractmethod
23-
def _search_issue(self, series_id: int, number: str | None) -> int | None: ...
23+
def _search_issue(self, series_id: int, number: str | None, filename: str) -> int | None: ...
2424

2525
@abstractmethod
26-
def fetch_issue(self, series_id: int, search: IssueSearch) -> C | None: ...
26+
def fetch_issue(self, series_id: int, search: IssueSearch, filename: str) -> C | None: ...
2727

2828
@abstractmethod
2929
def _process_metron_info(self, series: S, issue: C) -> MetronInfo | None: ...

perdoo/services/comicvine.py

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,15 @@
66

77
from natsort import humansorted, ns
88
from prompt_toolkit.styles import Style
9-
from questionary import Choice, select
9+
from questionary import Choice, confirm, select, text
1010
from requests.exceptions import JSONDecodeError
11-
from rich.prompt import Confirm, Prompt
1211
from simyan.comicvine import Comicvine as Simyan
1312
from simyan.exceptions import ServiceError
1413
from simyan.schemas.issue import Issue
1514
from simyan.schemas.volume import Volume
1615
from simyan.sqlite_cache import SQLiteCache
1716

1817
from perdoo import get_cache_root
19-
from perdoo.console import CONSOLE
2018
from perdoo.metadata import ComicInfo, MetronInfo
2119
from perdoo.metadata.metron_info import InformationSource
2220
from perdoo.services._base import BaseService
@@ -32,8 +30,10 @@ def __init__(self, settings: ComicvineSettings):
3230
cache = SQLiteCache(path=get_cache_root() / "simyan.sqlite", expiry=14)
3331
self.session = Simyan(api_key=settings.api_key, cache=cache)
3432

35-
def _search_series(self, name: str | None, volume: int | None, year: int | None) -> int | None:
36-
name = name or Prompt.ask("Volume Name", console=CONSOLE)
33+
def _search_series(
34+
self, name: str | None, volume: int | None, year: int | None, filename: str
35+
) -> int | None:
36+
name = name or text(message="Volume Name").ask()
3737
try:
3838
options = sorted(
3939
self.session.list_volumes({"filter": f"name:{name}"}),
@@ -67,31 +67,31 @@ def _search_series(self, name: str | None, volume: int | None, year: int | None)
6767
]
6868
choices.append(DEFAULT_CHOICE)
6969
selected = select(
70-
f"Searching for Comicvine Volume '{search}'",
70+
f"Searching Comicvine for Volumes matching '{filename}'"
71+
if not year
72+
else f"Searching Comicvine for Volume '{search}'",
7173
default=DEFAULT_CHOICE,
7274
choices=choices,
7375
style=Style([("dim", "dim")]),
7476
).ask()
7577
if selected and selected != DEFAULT_CHOICE.title:
7678
return selected.id
7779
else:
78-
LOGGER.warning(
79-
"Unable to find any Volumes with the Name and StartYear: '%s %s'", name, year
80-
)
80+
LOGGER.warning("Unable to find any Volumes for the file: '%s'", filename)
8181
if year:
8282
LOGGER.info("Searching again without the StartYear")
83-
return self._search_series(name=name, volume=volume, year=None)
84-
if Confirm.ask("Search Again", console=CONSOLE):
85-
return self._search_series(name=None, volume=None, year=None)
83+
return self._search_series(name=name, volume=volume, year=None, filename=filename)
84+
if confirm(message="Search Again", default=False).ask():
85+
return self._search_series(name=None, volume=None, year=None, filename=filename)
8686
except ServiceError as err:
8787
LOGGER.error(err)
8888
except JSONDecodeError:
8989
LOGGER.error("Unable to get response from Comicvine")
9090
return None
9191

92-
def fetch_series(self, search: SeriesSearch) -> Volume | None:
92+
def fetch_series(self, search: SeriesSearch, filename: str) -> Volume | None:
9393
series_id = search.comicvine or self._search_series(
94-
name=search.name, volume=search.volume, year=search.year
94+
name=search.name, volume=search.volume, year=search.year, filename=filename
9595
)
9696
if not series_id:
9797
return None
@@ -106,10 +106,10 @@ def fetch_series(self, search: SeriesSearch) -> Volume | None:
106106
return None
107107
if search.comicvine:
108108
search.comicvine = None
109-
return self.fetch_series(search=search)
109+
return self.fetch_series(search=search, filename=filename)
110110
return None
111111

112-
def _search_issue(self, series_id: int, number: str | None) -> int | None:
112+
def _search_issue(self, series_id: int, number: str | None, filename: str) -> int | None:
113113
try:
114114
options = humansorted(
115115
self.session.list_issues(
@@ -120,12 +120,6 @@ def _search_issue(self, series_id: int, number: str | None) -> int | None:
120120
key=lambda x: (x.number, x.name),
121121
alg=ns.NA | ns.G,
122122
)
123-
if not options:
124-
LOGGER.warning(
125-
"Unable to find any Issues with the Volume and IssueNumber: '%s %s'",
126-
series_id,
127-
number,
128-
)
129123
if options:
130124
choices = [
131125
Choice(
@@ -140,30 +134,30 @@ def _search_issue(self, series_id: int, number: str | None) -> int | None:
140134
]
141135
choices.append(DEFAULT_CHOICE)
142136
selected = select(
143-
f"Searching for Comicvine Issue #{number}",
137+
f"Searching Comicvine for Issues matching '{filename}'"
138+
if not number
139+
else f"Searching Comicvine for Issues with number '{number}'",
144140
default=DEFAULT_CHOICE,
145141
choices=choices,
146142
style=Style([("dim", "dim")]),
147143
).ask()
148144
if selected and selected != DEFAULT_CHOICE.title:
149145
return selected.id
150146
else:
151-
LOGGER.warning(
152-
"Unable to find any Issues with the Volume and IssueNumber: '%s %s'",
153-
series_id,
154-
number,
155-
)
147+
LOGGER.warning("Unable to find any Issues for the file: '%s'", filename)
156148
if number:
157149
LOGGER.info("Searching again without the IssueNumber")
158-
return self._search_issue(series_id=series_id, number=None)
150+
return self._search_issue(series_id=series_id, number=None, filename=filename)
159151
except ServiceError as err:
160152
LOGGER.error(err)
161153
except JSONDecodeError:
162154
LOGGER.error("Unable to get response from Comicvine")
163155
return None
164156

165-
def fetch_issue(self, series_id: int, search: IssueSearch) -> Issue | None:
166-
issue_id = search.comicvine or self._search_issue(series_id=series_id, number=search.number)
157+
def fetch_issue(self, series_id: int, search: IssueSearch, filename: str) -> Issue | None:
158+
issue_id = search.comicvine or self._search_issue(
159+
series_id=series_id, number=search.number, filename=filename
160+
)
167161
if not issue_id:
168162
return None
169163
try:
@@ -177,7 +171,7 @@ def fetch_issue(self, series_id: int, search: IssueSearch) -> Issue | None:
177171
return None
178172
if search.comicvine:
179173
search.comicvine = None
180-
return self.fetch_issue(series_id=series_id, search=search)
174+
return self.fetch_issue(series_id=series_id, search=search, filename=filename)
181175
return None
182176

183177
def _process_metron_info(self, series: Volume, issue: Issue) -> MetronInfo | None:
@@ -256,11 +250,11 @@ def fetch(self, search: Search) -> tuple[MetronInfo | None, ComicInfo | None]:
256250
except (ServiceError, JSONDecodeError):
257251
pass
258252

259-
series = self.fetch_series(search=search.series)
253+
series = self.fetch_series(search=search.series, filename=search.filename)
260254
if not series:
261255
return None, None
262256

263-
issue = self.fetch_issue(series_id=series.id, search=search.issue)
257+
issue = self.fetch_issue(series_id=series.id, search=search.issue, filename=search.filename)
264258
if not issue:
265259
return None, None
266260

0 commit comments

Comments
 (0)