Skip to content

Commit fc51940

Browse files
authored
v2.1.12: 支持实体类的切片语法,JmDownloader增加下载前Filter过滤机制 (#96)
1 parent 476efc6 commit fc51940

File tree

6 files changed

+137
-34
lines changed

6 files changed

+137
-34
lines changed

src/jmcomic/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
# 被依赖方 <--- 使用方
33
# config <--- entity <--- toolkit <--- client <--- option <--- downloader
44

5-
__version__ = '2.1.11'
5+
__version__ = '2.1.12'
66

77
from .api import *

src/jmcomic/api.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
11
from .jm_downloader import *
22

33

4-
def download_album_batch(jm_album_id_iter: Union[Iterable, Generator],
5-
option=None,
6-
):
4+
def download_batch(download_api,
5+
jm_id_iter: Union[Iterable, Generator],
6+
option=None,
7+
):
78
"""
8-
批量下载album.
9-
一个album,对应一个线程,对应一个option
9+
批量下载 album / photo
10+
一个album/photo,对应一个线程,对应一个option
1011
11-
@param jm_album_id_iter: album_id的迭代器
12-
@param option: 下载选项,为空默认是 JmOption.default()
12+
@param download_api: 下载api
13+
@param jm_id_iter: jmid (album_id, photo_id) 的迭代器
14+
@param option: 下载选项,对所有的jmid使用同一个,默认是 JmOption.default()
1315
"""
1416
from common import multi_thread_launcher
1517

18+
if option is None:
19+
option = JmOption.default()
20+
1621
return multi_thread_launcher(
1722
iter_objs=set(
18-
JmcomicText.parse_to_album_id(album_id)
19-
for album_id in jm_album_id_iter
23+
JmcomicText.parse_to_album_id(jmid)
24+
for jmid in jm_id_iter
2025
),
21-
apply_each_obj_func=lambda aid: download_album(aid, option),
26+
apply_each_obj_func=lambda aid: download_api(aid, option),
2227
)
2328

2429

@@ -31,7 +36,7 @@ def download_album(jm_album_id, option=None):
3136
"""
3237

3338
if not isinstance(jm_album_id, (str, int)):
34-
return download_album_batch(jm_album_id, option)
39+
return download_batch(download_album, jm_album_id, option)
3540

3641
with new_downloader(option) as dler:
3742
dler.download_album(jm_album_id)
@@ -41,6 +46,9 @@ def download_photo(jm_photo_id, option=None):
4146
"""
4247
下载一个章节
4348
"""
49+
if not isinstance(jm_photo_id, (str, int)):
50+
return download_batch(download_photo, jm_photo_id, option)
51+
4452
with new_downloader(option) as dler:
4553
dler.download_photo(jm_photo_id)
4654

src/jmcomic/jm_downloader.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
from .jm_option import *
22

3+
# help for typing
4+
DownloadIterObjs = Union[
5+
JmAlbumDetail,
6+
Sequence[JmPhotoDetail],
7+
JmPhotoDetail,
8+
Sequence[JmImageDetail],
9+
]
10+
311

412
class JmDownloadException(Exception):
513
pass
@@ -100,10 +108,15 @@ def download_by_image_detail(self, image: JmImageDetail, client: JmcomicClient):
100108
self.after_image(image, img_save_path)
101109

102110
# noinspection PyMethodMayBeStatic
103-
def execute_by_condition(self, iter_objs, apply: Callable, count_batch: int):
111+
def execute_by_condition(self,
112+
iter_objs: DownloadIterObjs,
113+
apply: Callable,
114+
count_batch: int,
115+
):
104116
"""
105-
章节/图片的下载调度逻辑
117+
调度本子/章节的下载
106118
"""
119+
iter_objs = self.filter_iter_objs(iter_objs)
107120
count_real = len(iter_objs)
108121

109122
if count_batch >= count_real:
@@ -120,6 +133,19 @@ def execute_by_condition(self, iter_objs, apply: Callable, count_batch: int):
120133
max_workers=count_batch,
121134
)
122135

136+
# noinspection PyMethodMayBeStatic
137+
def filter_iter_objs(self, iter_objs: DownloadIterObjs):
138+
"""
139+
该方法可用于过滤本子/章节,默认不会做过滤。
140+
例如:
141+
只想下载 本子的最新一章,返回 [album[-1]]
142+
只想下载 章节的前10张图片,返回 [photo[:10]]
143+
144+
@param iter_objs: 可能是本子或者章节,需要自行使用 isinstance 判断
145+
@return: 只想要下载的 本子的章节 或 章节的图片
146+
"""
147+
return iter_objs
148+
123149
# noinspection PyUnusedLocal
124150
def client_for_album(self, jm_album_id) -> JmcomicClient:
125151
"""

src/jmcomic/jm_entity.py

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def save_to_file(self, filepath):
2121
PackerUtil.pack(self, filepath)
2222

2323

24-
class DetailEntity(JmBaseEntity, IterableEntity):
24+
class DetailEntity(JmBaseEntity):
2525

2626
@property
2727
def id(self) -> str:
@@ -31,19 +31,42 @@ def id(self) -> str:
3131
def name(self) -> str:
3232
return getattr(self, 'title')
3333

34-
@classmethod
35-
def __jm_type__(cls):
36-
# "JmAlbumDetail" -> "album" (本子)
37-
# "JmPhotoDetail" -> "photo" (章节)
38-
cls_name = cls.__name__
39-
return cls_name[cls_name.index("m") + 1: cls_name.rfind("Detail")].lower()
34+
# help for typing
35+
JMPI = Union['JmPhotoDetail', 'JmImageDetail']
36+
37+
def getindex(self, index: int) -> JMPI:
38+
raise NotImplementedError
4039

41-
def __getitem__(self, item) -> Union['JmAlbumDetail', 'JmPhotoDetail']:
40+
def __len__(self):
4241
raise NotImplementedError
4342

43+
def __iter__(self) -> Generator[JMPI, Any, None]:
44+
for index in range(len(self)):
45+
yield self.getindex(index)
46+
4447
def __str__(self):
4548
return f'{self.__class__.__name__}({self.id}-{self.name})'
4649

50+
def __getitem__(self, item) -> Union[JMPI, List[JMPI]]:
51+
if isinstance(item, slice):
52+
start = item.start or 0
53+
stop = item.stop or len(self)
54+
step = item.step or 1
55+
return [self.getindex(index) for index in range(start, stop, step)]
56+
57+
elif isinstance(item, int):
58+
return self.getindex(item)
59+
60+
else:
61+
raise TypeError(f"Invalid item type for {self.__class__}")
62+
63+
@classmethod
64+
def __alias__(cls):
65+
# "JmAlbumDetail" -> "album" (本子)
66+
# "JmPhotoDetail" -> "photo" (章节)
67+
cls_name = cls.__name__
68+
return cls_name[cls_name.index("m") + 1: cls_name.rfind("Detail")].lower()
69+
4770

4871
class JmImageDetail(JmBaseEntity):
4972

@@ -254,13 +277,16 @@ def get_data_original_query_params(self, data_original_0: StrNone) -> str:
254277

255278
return data_original_0[index + 1:]
256279

257-
def __getitem__(self, item) -> JmImageDetail:
258-
return self.create_image_detail(item)
259-
260280
@property
261281
def id(self):
262282
return self.photo_id
263283

284+
def getindex(self, index) -> JmImageDetail:
285+
return self.create_image_detail(index)
286+
287+
def __getitem__(self, item) -> Union[JmImageDetail, List[JmImageDetail]]:
288+
return super().__getitem__(item)
289+
264290
def __len__(self):
265291
return len(self.page_arr)
266292

@@ -337,12 +363,6 @@ def keywords(self) -> List[str]:
337363
def id(self):
338364
return self.album_id
339365

340-
def __len__(self):
341-
return len(self.episode_list)
342-
343-
def __getitem__(self, item) -> JmPhotoDetail:
344-
return self.create_photo_detail(item)[0]
345-
346366
@staticmethod
347367
def distinct_episode(episode_list):
348368
ret = []
@@ -360,6 +380,15 @@ def not_exist(episode):
360380

361381
return ret
362382

383+
def getindex(self, item) -> JmPhotoDetail:
384+
return self.create_photo_detail(item)[0]
385+
386+
def __getitem__(self, item) -> Union[JmPhotoDetail, List[JmPhotoDetail]]:
387+
return super().__getitem__(item)
388+
389+
def __len__(self):
390+
return len(self.episode_list)
391+
363392
def __iter__(self) -> Generator[JmPhotoDetail, Any, None]:
364393
return super().__iter__()
365394

src/jmcomic/jm_toolkit.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from .jm_entity import *
21
from PIL import Image
32

3+
from .jm_entity import *
4+
45

56
class JmcomicText:
67
pattern_jm_domain = compile('https://([\w.-]+)')

tests/test_jmcomic/test_jm_api.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ def test_batch(self):
4141
ret1 = jmcomic.download_album(case, self.option)
4242
self.assertEqual(len(ret1), len(album_ls), str(case))
4343

44-
ret2 = jmcomic.download_album_batch(case, self.option)
44+
ret2 = jmcomic.download_album(case, self.option)
4545
self.assertEqual(len(ret2), len(album_ls), str(case))
4646

4747
# 测试 Generator
48-
ret2 = jmcomic.download_album_batch((e for e in album_ls), self.option)
48+
ret2 = jmcomic.download_album((e for e in album_ls), self.option)
4949
self.assertEqual(len(ret2), len(album_ls), 'Generator')
5050

5151
def test_photo_sort(self):
@@ -130,3 +130,42 @@ def run_func_async(func):
130130
print(e)
131131

132132
raise AssertionError(exception_list)
133+
134+
def test_getitem_and_slice(self):
135+
cl: JmcomicClient = self.client
136+
cases = [
137+
['400222', 0, [400222]],
138+
['400222', 1, [413446]],
139+
['400222', (None, 1), [400222]],
140+
['400222', (1, 3), [413446, 413447]],
141+
['413447', (1, 3), [2, 3], []],
142+
]
143+
144+
for [jmid, slicearg, *args] in cases:
145+
ans = args[0]
146+
147+
if len(args) == 1:
148+
func = cl.get_album_detail
149+
else:
150+
func = cl.get_photo_detail
151+
152+
jmentity = func(jmid)
153+
154+
ls: List[Union[JmPhotoDetail, JmImageDetail]]
155+
if isinstance(slicearg, int):
156+
ls = [jmentity[slicearg]]
157+
elif len(slicearg) == 2:
158+
ls = jmentity[slicearg[0]: slicearg[1]]
159+
else:
160+
ls = jmentity[slicearg[0]: slicearg[1]: slicearg[2]]
161+
162+
if len(args) == 1:
163+
self.assertListEqual(
164+
list1=[int(e.id) for e in ls],
165+
list2=ans,
166+
)
167+
else:
168+
self.assertListEqual(
169+
list1=[int(e.img_file_name) for e in ls],
170+
list2=ans,
171+
)

0 commit comments

Comments
 (0)