jmcomic 2.5.34__tar.gz → 2.5.35__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {jmcomic-2.5.34/src/jmcomic.egg-info → jmcomic-2.5.35}/PKG-INFO +1 -1
  2. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic/__init__.py +1 -1
  3. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic/api.py +9 -3
  4. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic/jm_client_impl.py +1 -1
  5. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic/jm_downloader.py +74 -46
  6. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic/jm_entity.py +8 -4
  7. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic/jm_exception.py +9 -2
  8. {jmcomic-2.5.34 → jmcomic-2.5.35/src/jmcomic.egg-info}/PKG-INFO +1 -1
  9. {jmcomic-2.5.34 → jmcomic-2.5.35}/LICENSE +0 -0
  10. {jmcomic-2.5.34 → jmcomic-2.5.35}/README.md +0 -0
  11. {jmcomic-2.5.34 → jmcomic-2.5.35}/setup.cfg +0 -0
  12. {jmcomic-2.5.34 → jmcomic-2.5.35}/setup.py +0 -0
  13. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic/cl.py +0 -0
  14. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic/jm_client_interface.py +0 -0
  15. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic/jm_config.py +0 -0
  16. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic/jm_option.py +0 -0
  17. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic/jm_plugin.py +0 -0
  18. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic/jm_toolkit.py +0 -0
  19. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic.egg-info/SOURCES.txt +0 -0
  20. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic.egg-info/dependency_links.txt +0 -0
  21. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic.egg-info/entry_points.txt +0 -0
  22. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic.egg-info/requires.txt +0 -0
  23. {jmcomic-2.5.34 → jmcomic-2.5.35}/src/jmcomic.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jmcomic
3
- Version: 2.5.34
3
+ Version: 2.5.35
4
4
  Summary: Python API For JMComic (禁漫天堂)
5
5
  Home-page: https://github.com/hect0x7/JMComic-Crawler-Python
6
6
  Author: hect0x7
@@ -2,7 +2,7 @@
2
2
  # 被依赖方 <--- 使用方
3
3
  # config <--- entity <--- toolkit <--- client <--- option <--- downloader
4
4
 
5
- __version__ = '2.5.34'
5
+ __version__ = '2.5.35'
6
6
 
7
7
  from .api import *
8
8
  from .jm_plugin import *
@@ -48,6 +48,7 @@ def download_album(jm_album_id,
48
48
  option=None,
49
49
  downloader=None,
50
50
  callback=None,
51
+ check_exception=True,
51
52
  ) -> Union[__DOWNLOAD_API_RET, Set[__DOWNLOAD_API_RET]]:
52
53
  """
53
54
  下载一个本子(album),包含其所有的章节(photo)
@@ -58,6 +59,7 @@ def download_album(jm_album_id,
58
59
  :param option: 下载选项
59
60
  :param downloader: 下载器类
60
61
  :param callback: 返回值回调函数,可以拿到 album 和 downloader
62
+ :param check_exception: 是否检查异常, 如果为True,会检查downloader是否有下载异常,并上抛PartialDownloadFailedException
61
63
  :return: 对于的本子实体类,下载器(如果是上述的批量情况,返回值为download_batch的返回值)
62
64
  """
63
65
 
@@ -69,14 +71,17 @@ def download_album(jm_album_id,
69
71
 
70
72
  if callback is not None:
71
73
  callback(album, dler)
72
-
74
+ if check_exception:
75
+ dler.raise_if_has_exception()
73
76
  return album, dler
74
77
 
75
78
 
76
79
  def download_photo(jm_photo_id,
77
80
  option=None,
78
81
  downloader=None,
79
- callback=None):
82
+ callback=None,
83
+ check_exception=True,
84
+ ):
80
85
  """
81
86
  下载一个章节(photo),参数同 download_album
82
87
  """
@@ -88,7 +93,8 @@ def download_photo(jm_photo_id,
88
93
 
89
94
  if callback is not None:
90
95
  callback(photo, dler)
91
-
96
+ if check_exception:
97
+ dler.raise_if_has_exception()
92
98
  return photo, dler
93
99
 
94
100
 
@@ -566,7 +566,7 @@ class JmHtmlClient(AbstractJmClient):
566
566
 
567
567
  cls.raise_request_error(
568
568
  resp,
569
- f'{reason}'
569
+ f'{reason}({content})'
570
570
  + (f': {url}' if url is not None else '')
571
571
  )
572
572
 
@@ -1,6 +1,31 @@
1
1
  from .jm_option import *
2
2
 
3
3
 
4
+ def catch_exception(func):
5
+ from functools import wraps
6
+
7
+ @wraps(func)
8
+ def wrapper(self, *args, **kwargs):
9
+ self: JmDownloader
10
+ try:
11
+ return func(self, *args, **kwargs)
12
+ except Exception as e:
13
+ detail: JmBaseEntity = args[0]
14
+ if detail.is_image():
15
+ detail: JmImageDetail
16
+ jm_log('image.failed', f'图片下载失败: [{detail.download_url}], 异常: [{e}]')
17
+ self.download_failed_image.append((detail, e))
18
+
19
+ elif detail.is_photo():
20
+ detail: JmPhotoDetail
21
+ jm_log('photo.failed', f'章节下载失败: [{detail.id}], 异常: [{e}]')
22
+ self.download_failed_photo.append((detail, e))
23
+
24
+ raise e
25
+
26
+ return wrapper
27
+
28
+
4
29
  # noinspection PyMethodMayBeStatic
5
30
  class DownloadCallback:
6
31
 
@@ -50,48 +75,50 @@ class JmDownloader(DownloadCallback):
50
75
 
51
76
  def __init__(self, option: JmOption) -> None:
52
77
  self.option = option
78
+ self.client = option.build_jm_client()
53
79
  # 下载成功的记录dict
54
80
  self.download_success_dict: Dict[JmAlbumDetail, Dict[JmPhotoDetail, List[Tuple[str, JmImageDetail]]]] = {}
55
81
  # 下载失败的记录list
56
- self.download_failed_list: List[Tuple[JmImageDetail, BaseException]] = []
82
+ self.download_failed_image: List[Tuple[JmImageDetail, BaseException]] = []
83
+ self.download_failed_photo: List[Tuple[JmPhotoDetail, BaseException]] = []
57
84
 
58
85
  def download_album(self, album_id):
59
- client = self.client_for_album(album_id)
60
- album = client.get_album_detail(album_id)
61
- self.download_by_album_detail(album, client)
86
+ album = self.client.get_album_detail(album_id)
87
+ self.download_by_album_detail(album)
62
88
  return album
63
89
 
64
- def download_by_album_detail(self, album: JmAlbumDetail, client: JmcomicClient):
90
+ def download_by_album_detail(self, album: JmAlbumDetail):
65
91
  self.before_album(album)
66
92
  if album.skip:
67
93
  return
68
- self.execute_by_condition(
94
+ self.execute_on_condition(
69
95
  iter_objs=album,
70
- apply=lambda photo: self.download_by_photo_detail(photo, client),
96
+ apply=self.download_by_photo_detail,
71
97
  count_batch=self.option.decide_photo_batch_count(album)
72
98
  )
73
99
  self.after_album(album)
74
100
 
75
101
  def download_photo(self, photo_id):
76
- client = self.client_for_photo(photo_id)
77
- photo = client.get_photo_detail(photo_id)
78
- self.download_by_photo_detail(photo, client)
102
+ photo = self.client.get_photo_detail(photo_id)
103
+ self.download_by_photo_detail(photo)
79
104
  return photo
80
105
 
81
- def download_by_photo_detail(self, photo: JmPhotoDetail, client: JmcomicClient):
82
- client.check_photo(photo)
106
+ @catch_exception
107
+ def download_by_photo_detail(self, photo: JmPhotoDetail):
108
+ self.client.check_photo(photo)
83
109
 
84
110
  self.before_photo(photo)
85
111
  if photo.skip:
86
112
  return
87
- self.execute_by_condition(
113
+ self.execute_on_condition(
88
114
  iter_objs=photo,
89
- apply=lambda image: self.download_by_image_detail(image, client),
115
+ apply=self.download_by_image_detail,
90
116
  count_batch=self.option.decide_image_batch_count(photo)
91
117
  )
92
118
  self.after_photo(photo)
93
119
 
94
- def download_by_image_detail(self, image: JmImageDetail, client: JmcomicClient):
120
+ @catch_exception
121
+ def download_by_image_detail(self, image: JmImageDetail):
95
122
  img_save_path = self.option.decide_image_filepath(image)
96
123
 
97
124
  image.save_path = img_save_path
@@ -110,22 +137,15 @@ class JmDownloader(DownloadCallback):
110
137
  if use_cache is True and image.exists:
111
138
  return
112
139
 
113
- try:
114
- client.download_by_image_detail(
115
- image,
116
- img_save_path,
117
- decode_image=decode_image,
118
- )
119
- except BaseException as e:
120
- jm_log('image.failed', f'图片下载失败: [{image.download_url}], 异常: {e}')
121
- # 保存失败记录
122
- self.download_failed_list.append((image, e))
123
- raise
140
+ self.client.download_by_image_detail(
141
+ image,
142
+ img_save_path,
143
+ decode_image=decode_image,
144
+ )
124
145
 
125
146
  self.after_image(image, img_save_path)
126
147
 
127
- # noinspection PyMethodMayBeStatic
128
- def execute_by_condition(self,
148
+ def execute_on_condition(self,
129
149
  iter_objs: DetailEntity,
130
150
  apply: Callable,
131
151
  count_batch: int,
@@ -166,20 +186,6 @@ class JmDownloader(DownloadCallback):
166
186
  """
167
187
  return detail
168
188
 
169
- # noinspection PyUnusedLocal
170
- def client_for_album(self, jm_album_id) -> JmcomicClient:
171
- """
172
- 默认情况下,所有的JmDownloader共用一个JmcomicClient
173
- """
174
- return self.option.build_jm_client()
175
-
176
- # noinspection PyUnusedLocal
177
- def client_for_photo(self, jm_photo_id) -> JmcomicClient:
178
- """
179
- 默认情况下,所有的JmDownloader共用一个JmcomicClient
180
- """
181
- return self.option.build_jm_client()
182
-
183
189
  @property
184
190
  def all_success(self) -> bool:
185
191
  """
@@ -189,7 +195,7 @@ class JmDownloader(DownloadCallback):
189
195
 
190
196
  注意!如果使用了filter机制,例如通过filter只下载3张图片,那么all_success也会为False
191
197
  """
192
- if len(self.download_failed_list) != 0:
198
+ if self.has_download_failures:
193
199
  return False
194
200
 
195
201
  for album, photo_dict in self.download_success_dict.items():
@@ -202,6 +208,10 @@ class JmDownloader(DownloadCallback):
202
208
 
203
209
  return True
204
210
 
211
+ @property
212
+ def has_download_failures(self):
213
+ return len(self.download_failed_image) != 0 or len(self.download_failed_photo) != 0
214
+
205
215
  # 下面是回调方法
206
216
 
207
217
  def before_album(self, album: JmAlbumDetail):
@@ -259,6 +269,23 @@ class JmDownloader(DownloadCallback):
259
269
  downloader=self,
260
270
  )
261
271
 
272
+ def raise_if_has_exception(self):
273
+ if not self.has_download_failures:
274
+ return
275
+ msg_ls = ['部分下载失败', '', '']
276
+
277
+ if len(self.download_failed_photo) != 0:
278
+ msg_ls[1] = f'共{len(self.download_failed_photo)}个章节下载失败: {self.download_failed_photo}'
279
+
280
+ if len(self.download_failed_image) != 0:
281
+ msg_ls[2] = f'共{len(self.download_failed_image)}个图片下载失败: {self.download_failed_image}'
282
+
283
+ ExceptionTool.raises(
284
+ '\n'.join(msg_ls),
285
+ {'downloader': self},
286
+ PartialDownloadFailedException,
287
+ )
288
+
262
289
  # 下面是对with语法的支持
263
290
 
264
291
  def __enter__(self):
@@ -283,7 +310,7 @@ class DoNotDownloadImage(JmDownloader):
283
310
  不会下载任何图片的Downloader,用作测试
284
311
  """
285
312
 
286
- def download_by_image_detail(self, image: JmImageDetail, client: JmcomicClient):
313
+ def download_by_image_detail(self, image: JmImageDetail):
287
314
  # ensure make dir
288
315
  self.option.decide_image_filepath(image)
289
316
 
@@ -297,12 +324,13 @@ class JustDownloadSpecificCountImage(JmDownloader):
297
324
  count_lock = Lock()
298
325
  count = 0
299
326
 
300
- def download_by_image_detail(self, image: JmImageDetail, client: JmcomicClient):
327
+ @catch_exception
328
+ def download_by_image_detail(self, image: JmImageDetail):
301
329
  # ensure make dir
302
330
  self.option.decide_image_filepath(image)
303
331
 
304
332
  if self.try_countdown():
305
- return super().download_by_image_detail(image, client)
333
+ return super().download_by_image_detail(image)
306
334
 
307
335
  def try_countdown(self):
308
336
  if self.count < 0:
@@ -125,10 +125,9 @@ class DetailEntity(JmBaseEntity, IndexedEntity):
125
125
  return f'[{self.id}] {self.oname}'
126
126
 
127
127
  def __str__(self):
128
- return f'{self.__class__.__name__}' \
129
- '{' \
130
- f'{self.id}: {self.title}' \
131
- '}'
128
+ return f'''{self.__class__.__name__}({self.__alias__()}-{self.id}: "{self.title}")'''
129
+
130
+ __repr__ = __str__
132
131
 
133
132
  @classmethod
134
133
  def __alias__(cls):
@@ -258,6 +257,11 @@ class JmImageDetail(JmBaseEntity, Downloadable):
258
257
  def is_image(cls):
259
258
  return True
260
259
 
260
+ def __str__(self):
261
+ return f'''{self.__class__.__name__}(image-[{self.download_url}])'''
262
+
263
+ __repr__ = __str__
264
+
261
265
 
262
266
  class JmPhotoDetail(DetailEntity, Downloadable):
263
267
 
@@ -15,6 +15,7 @@ class JmcomicException(Exception):
15
15
  def __str__(self):
16
16
  return self.msg
17
17
 
18
+
18
19
  class ResponseUnexpectedException(JmcomicException):
19
20
  description = '响应不符合预期异常'
20
21
 
@@ -44,7 +45,6 @@ class RegularNotMatchException(JmcomicException):
44
45
 
45
46
  class JsonResolveFailException(ResponseUnexpectedException):
46
47
  description = 'Json解析异常'
47
- pass
48
48
 
49
49
 
50
50
  class MissingAlbumPhotoException(ResponseUnexpectedException):
@@ -57,9 +57,15 @@ class MissingAlbumPhotoException(ResponseUnexpectedException):
57
57
 
58
58
  class RequestRetryAllFailException(JmcomicException):
59
59
  description = '请求重试全部失败异常'
60
- pass
61
60
 
62
61
 
62
+ class PartialDownloadFailedException(JmcomicException):
63
+ description = '部分章节或图片下载失败异常'
64
+
65
+ @property
66
+ def downloader(self):
67
+ return self.from_context(ExceptionTool.CONTEXT_KEY_DOWNLOADER)
68
+
63
69
  class ExceptionTool:
64
70
  """
65
71
  抛异常的工具
@@ -71,6 +77,7 @@ class ExceptionTool:
71
77
  CONTEXT_KEY_HTML = 'html'
72
78
  CONTEXT_KEY_RE_PATTERN = 'pattern'
73
79
  CONTEXT_KEY_MISSING_JM_ID = 'missing_jm_id'
80
+ CONTEXT_KEY_DOWNLOADER = 'downloader'
74
81
 
75
82
  @classmethod
76
83
  def raises(cls,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jmcomic
3
- Version: 2.5.34
3
+ Version: 2.5.35
4
4
  Summary: Python API For JMComic (禁漫天堂)
5
5
  Home-page: https://github.com/hect0x7/JMComic-Crawler-Python
6
6
  Author: hect0x7
File without changes
File without changes
File without changes
File without changes
File without changes