parsehub 2.1.2__tar.gz → 2.1.3__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 (73) hide show
  1. {parsehub-2.1.2/src/parsehub.egg-info → parsehub-2.1.3}/PKG-INFO +2 -2
  2. {parsehub-2.1.2 → parsehub-2.1.3}/pyproject.toml +3 -2
  3. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/base/ytdlp.py +2 -2
  4. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/bilibili.py +6 -5
  5. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/douyin.py +26 -13
  6. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/bilibili.py +4 -4
  7. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/coolapk.py +4 -4
  8. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/douyin.py +1 -1
  9. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/instagram.py +5 -5
  10. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/twitter.py +1 -1
  11. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/zhihu.py +4 -4
  12. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/types/result.py +13 -13
  13. {parsehub-2.1.2 → parsehub-2.1.3/src/parsehub.egg-info}/PKG-INFO +2 -2
  14. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub.egg-info/requires.txt +1 -1
  15. {parsehub-2.1.2 → parsehub-2.1.3}/LICENSE +0 -0
  16. {parsehub-2.1.2 → parsehub-2.1.3}/README.md +0 -0
  17. {parsehub-2.1.2 → parsehub-2.1.3}/setup.cfg +0 -0
  18. {parsehub-2.1.2 → parsehub-2.1.3}/src/__init__.py +0 -0
  19. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/__init__.py +0 -0
  20. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/cli.py +0 -0
  21. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/cli_config.py +0 -0
  22. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/config/__init__.py +0 -0
  23. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/config/config.py +0 -0
  24. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/errors.py +0 -0
  25. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/__init__.py +0 -0
  26. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/base/__init__.py +0 -0
  27. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/base/base.py +0 -0
  28. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/__init__.py +0 -0
  29. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/coolapk.py +0 -0
  30. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/facebook.py +0 -0
  31. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/instagram.py +0 -0
  32. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/kuaishou.py +0 -0
  33. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/pipix.py +0 -0
  34. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/snapchat.py +0 -0
  35. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/threads.py +0 -0
  36. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/tieba.py +0 -0
  37. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/tiktok.py +0 -0
  38. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/twitter.py +0 -0
  39. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/weibo.py +0 -0
  40. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/weixin.py +0 -0
  41. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/xhs.py +0 -0
  42. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/xiaoheihe.py +0 -0
  43. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/youtube.py +0 -0
  44. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/zhihu.py +0 -0
  45. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/parsers/parser/zuiyou.py +0 -0
  46. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/__init__.py +0 -0
  47. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/kuaishou.py +0 -0
  48. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/pipix.py +0 -0
  49. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/threads.py +0 -0
  50. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/tieba.py +0 -0
  51. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/tiktok.py +0 -0
  52. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/weibo.py +0 -0
  53. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/weixin.py +0 -0
  54. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/xhs.py +0 -0
  55. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/xiaoheihe.py +0 -0
  56. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/provider_api/zuiyou.py +0 -0
  57. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/types/__init__.py +0 -0
  58. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/types/callback.py +0 -0
  59. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/types/media_file.py +0 -0
  60. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/types/media_ref.py +0 -0
  61. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/types/platform.py +0 -0
  62. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/types/post.py +0 -0
  63. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/utils/downloader.py +0 -0
  64. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/utils/helpers.py +0 -0
  65. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub/utils/media_info.py +0 -0
  66. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub.egg-info/SOURCES.txt +0 -0
  67. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub.egg-info/dependency_links.txt +0 -0
  68. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub.egg-info/entry_points.txt +0 -0
  69. {parsehub-2.1.2 → parsehub-2.1.3}/src/parsehub.egg-info/top_level.txt +0 -0
  70. {parsehub-2.1.2 → parsehub-2.1.3}/test/test_cli.py +0 -0
  71. {parsehub-2.1.2 → parsehub-2.1.3}/test/test_cli_config.py +0 -0
  72. {parsehub-2.1.2 → parsehub-2.1.3}/test/test_core_offline.py +0 -0
  73. {parsehub-2.1.2 → parsehub-2.1.3}/test/test_downloader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: parsehub
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: 轻量、异步、开箱即用的社交媒体聚合解析库
5
5
  Author-email: 梓澪 <zilingmio@gmail.com>
6
6
  License: MIT
@@ -34,7 +34,7 @@ Requires-Dist: python-slugify[unidecode]>=8.0.4
34
34
  Requires-Dist: opencv-python-headless>=4.13.0.92
35
35
  Requires-Dist: cryptography>=49.0.0
36
36
  Requires-Dist: gmssl>=3.2.2
37
- Requires-Dist: SignerPy>=0.12.0
37
+ Requires-Dist: signerpy>=0.12.0
38
38
  Provides-Extra: cli
39
39
  Requires-Dist: argcomplete>=3.6.3; extra == "cli"
40
40
  Requires-Dist: platformdirs>=4.5.1; extra == "cli"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "parsehub"
3
- version = "2.1.2"
3
+ version = "2.1.3"
4
4
  description = "轻量、异步、开箱即用的社交媒体聚合解析库"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12.0"
@@ -37,7 +37,7 @@ dependencies = [
37
37
  "opencv-python-headless>=4.13.0.92",
38
38
  "cryptography>=49.0.0",
39
39
  "gmssl>=3.2.2",
40
- "SignerPy>=0.12.0",
40
+ "signerpy>=0.12.0",
41
41
  ]
42
42
 
43
43
  [project.scripts]
@@ -66,6 +66,7 @@ Issues = "https://github.com/z-mio/parsehub/issues"
66
66
 
67
67
  [tool.ruff]
68
68
  line-length = 120
69
+ exclude = ["test/_*.py"]
69
70
 
70
71
  [tool.ruff.lint]
71
72
  select = [
@@ -445,9 +445,9 @@ class YtVideoParseResult(VideoParseResult):
445
445
  def __init__(
446
446
  self,
447
447
  dl: "YtVideoInfo",
448
- title: str | None,
448
+ title: str = "",
449
449
  video: VideoRef | None = None,
450
- content: str | None = None,
450
+ content: str = "",
451
451
  ):
452
452
  """dl: yt-dlp解析结果"""
453
453
  self.dl = dl
@@ -106,7 +106,6 @@ class BiliParse(BaseParser):
106
106
  view = data["View"]
107
107
 
108
108
  cid = view["cid"]
109
- part = ""
110
109
  duration = view["duration"]
111
110
  dimension = view["dimension"]
112
111
  desc = view["desc"]
@@ -114,7 +113,6 @@ class BiliParse(BaseParser):
114
113
  if p != 1 and (pages := view.get("pages")):
115
114
  if page_info := next((i for i in pages if i["page"] == p), None):
116
115
  cid = page_info["cid"]
117
- part = page_info["part"]
118
116
  duration = page_info["duration"]
119
117
  dimension = page_info["dimension"]
120
118
 
@@ -123,9 +121,12 @@ class BiliParse(BaseParser):
123
121
 
124
122
  durl = video_playurl["data"]["durl"][0]
125
123
  video_url = self.change_source(durl["backup_url"][0]) if durl.get("backup_url") else durl["url"]
124
+ content = desc.strip()
125
+ if content == "-":
126
+ content = ""
126
127
  return BiliVideoParseResult(
127
128
  title=data["View"]["title"],
128
- content=f"P{p}: {part}" if part else (desc.strip() or ""),
129
+ content=content,
129
130
  video=VideoRef(
130
131
  url=video_url,
131
132
  thumb_url=data["View"]["pic"],
@@ -147,9 +148,9 @@ class BiliParse(BaseParser):
147
148
  )
148
149
 
149
150
  @staticmethod
150
- def hashtag_handler(desc: str | None) -> str | None:
151
+ def hashtag_handler(desc: str) -> str:
151
152
  if not desc:
152
- return None
153
+ return ""
153
154
  hashtags = re.findall(r" ?#[^#]+# ?", desc)
154
155
  for hashtag in hashtags:
155
156
  desc = desc.replace(hashtag, f" {hashtag.strip().removesuffix('#')} ")
@@ -117,13 +117,17 @@ def remove_video_watermark(url: str) -> str:
117
117
  return url.replace("playwm", "play")
118
118
 
119
119
 
120
- def parse_video_info(video_data: dict) -> dict:
121
- bit_rates = video_data.get("bit_rate")
122
- if not bit_rates:
123
- raise ParseError("抖音解析失败: 未获取到视频下载地址")
120
+ def _p_play_addr(video_data: dict) -> tuple[str | None, int, int]:
121
+ play_addr = video_data["play_addr"]
122
+ url_list = play_addr["url_list"]
123
+ if not url_list:
124
+ return None, 0, 0
125
+ return url_list[0], play_addr.get("width", 0), play_addr.get("height", 0)
126
+
124
127
 
125
- # 按分辨率、文件大小、码率降序排列,选择最高质量。
126
- # Story/日常的移动端 default 播放地址通常没有 br 参数,但 data_size 最大。
128
+ def _p_bit_rates(video_data: dict) -> tuple[str, int, int]:
129
+ bit_rates: list = video_data.get("bit_rate", [])
130
+ bit_rates = list(filter(lambda i: i.get("is_bytevc1", 0) == 0, bit_rates))
127
131
  bit_rates.sort(
128
132
  key=lambda x: (
129
133
  x.get("play_addr", {}).get("width", 0) * x.get("play_addr", {}).get("height", 0),
@@ -135,11 +139,20 @@ def parse_video_info(video_data: dict) -> dict:
135
139
  best_quality = bit_rates[0]
136
140
 
137
141
  play_addr = best_quality.get("play_addr", {})
138
- video_url_list = play_addr.get("url_list", [])
139
- if not video_url_list:
140
- raise ParseError("抖音解析失败: 视频下载地址为空")
142
+ url_list = play_addr.get("url_list", [])
143
+ return url_list[0], play_addr.get("width", 0), play_addr.get("height", 0)
144
+
145
+
146
+ def parse_video_info(video_data: dict) -> dict:
147
+ u, w, h = None, 0, 0
148
+ is_bytevc1 = video_data.get("is_bytevc1")
149
+ # is_bytevc1 = 1 | 2 为字节自研视频编码 bytevc1 | bytevc2
150
+ if is_bytevc1 == 0:
151
+ u, w, h = _p_play_addr(video_data)
152
+ if not u:
153
+ u, w, h = _p_bit_rates(video_data)
141
154
 
142
- video_url = remove_video_watermark(video_url_list[0])
155
+ video_url = remove_video_watermark(u)
143
156
 
144
157
  cover = video_data.get("cover", {})
145
158
  thumb_url_list = cover.get("url_list", [])
@@ -148,9 +161,9 @@ def parse_video_info(video_data: dict) -> dict:
148
161
  return {
149
162
  "video_url": video_url,
150
163
  "thumb_url": thumb_url,
151
- "duration": best_quality.get("duration", 0),
152
- "width": play_addr.get("width", 0),
153
- "height": play_addr.get("height", 0),
164
+ "duration": video_data.get("duration", 0),
165
+ "width": w,
166
+ "height": h,
154
167
  }
155
168
 
156
169
 
@@ -245,8 +245,8 @@ class BiliImage:
245
245
 
246
246
  @dataclass(kw_only=True)
247
247
  class BiliDynamic:
248
- title: str | None = None
249
- content: str | None = None
248
+ title: str = ""
249
+ content: str = ""
250
250
  images: list[BiliImage] | None = None
251
251
 
252
252
  @classmethod
@@ -361,10 +361,10 @@ class BiliDynamic:
361
361
  return cls(title=title)
362
362
 
363
363
  @staticmethod
364
- def _get_desc_text(module_dynamic: dict) -> str | None:
364
+ def _get_desc_text(module_dynamic: dict) -> str:
365
365
  if desc := module_dynamic["desc"]:
366
366
  return str(desc["text"]).strip()
367
- return None
367
+ return ""
368
368
 
369
369
  @staticmethod
370
370
  def _get_major_cover(major_content: dict) -> list[BiliImage] | None:
@@ -10,9 +10,9 @@ from ..utils.helpers import UA
10
10
 
11
11
  @dataclass
12
12
  class Coolapk:
13
- title: str | None = None
14
- markdown_content: str | None = None
15
- text_content: str | None = None
13
+ title: str = ""
14
+ markdown_content: str = ""
15
+ text_content: str = ""
16
16
  imgs: list[str] | None = None
17
17
 
18
18
  @classmethod
@@ -37,6 +37,6 @@ class Coolapk:
37
37
  if feed_element and (feed_content := feed_element.text.strip()):
38
38
  message_image_group = soup.find(class_="message-image-group")
39
39
  imgs = [f"https:{i['src']}" for i in message_image_group.find_all("img")] if message_image_group else []
40
- return cls(None, None, feed_content, imgs)
40
+ return cls("", "", feed_content, imgs)
41
41
 
42
42
  raise ValueError("获取内容失败, 分享时请保留 shareKey 或 s 参数")
@@ -1147,7 +1147,7 @@ class DouyinMobileCrawler:
1147
1147
  return cast(dict[str, Any], payload)
1148
1148
  last_error = f"{host} missing aweme_detail: {payload.get('status_msg') or payload}"
1149
1149
  await asyncio.sleep(0.15)
1150
- raise ParseError(f"获取抖音 Story/日常失败: {last_error}")
1150
+ raise ParseError(f"获取抖音作品失败: {last_error}")
1151
1151
 
1152
1152
  async def parse(self, raw_url: str) -> dict:
1153
1153
  aweme_id = await self.get_aweme_id(raw_url)
@@ -65,17 +65,17 @@ class InstagramPost:
65
65
  return bool(self._field("is_video"))
66
66
 
67
67
  @property
68
- def title(self) -> str | None:
69
- return self._node.get("title")
68
+ def title(self) -> str:
69
+ return str(self._node.get("title", ""))
70
70
 
71
71
  @property
72
- def caption(self) -> str | None:
72
+ def caption(self) -> str:
73
73
  caption_edges = self._node.get("edge_media_to_caption", {}).get("edges") or []
74
74
  if caption_edges:
75
75
  if text := caption_edges[0].get("node", {}).get("text"):
76
76
  return str(text)
77
- return None
78
- return self._node.get("caption")
77
+ return ""
78
+ return str(self._node.get("caption", ""))
79
79
 
80
80
  @property
81
81
  def url(self) -> str:
@@ -173,7 +173,7 @@ class TwitterTweet:
173
173
  def __init__(
174
174
  self,
175
175
  tweet_id: str,
176
- full_text: str | None = None,
176
+ full_text: str = "",
177
177
  media: list[TwitterVideo | TwitterPhoto | TwitterAni] | None = None,
178
178
  article: TwitterArticle | None = None,
179
179
  ):
@@ -91,8 +91,8 @@ class ZhihuQA:
91
91
  class ZhihuZhuanLan:
92
92
  title: str
93
93
  imgs: list[str]
94
- markdown_content: str | None = None
95
- plaintext_content: str | None = None
94
+ markdown_content: str = ""
95
+ plaintext_content: str = ""
96
96
 
97
97
  @classmethod
98
98
  def parse(cls, data: dict) -> Self:
@@ -122,8 +122,8 @@ class ZhihuPin:
122
122
  type: ZhihuPinType
123
123
  title: str
124
124
  media: list[ZhihuMedia]
125
- markdown_content: str | None = None
126
- plaintext_content: str | None = None
125
+ markdown_content: str = ""
126
+ plaintext_content: str = ""
127
127
 
128
128
  @classmethod
129
129
  def parse(cls, result: dict) -> "ZhihuPin":
@@ -30,8 +30,8 @@ class ParseResult(ABC): # noqa: B024
30
30
 
31
31
  def __init__(
32
32
  self,
33
- title: str | None = "",
34
- content: str | None = "",
33
+ title: str = "",
34
+ content: str = "",
35
35
  media: Sequence[AnyMediaRef] | AnyMediaRef | None = None,
36
36
  platform: Platform | None = None,
37
37
  ):
@@ -42,8 +42,8 @@ class ParseResult(ABC): # noqa: B024
42
42
  :param platform: 平台
43
43
  """
44
44
  self.raw_url: str = ""
45
- self.title = (title or "").strip()
46
- self.content = (content or "").strip()
45
+ self.title = title.strip()
46
+ self.content = content.strip()
47
47
  self.media = media
48
48
  self.platform = platform
49
49
  self.name = slugify(
@@ -294,9 +294,9 @@ class VideoParseResult(ParseResult):
294
294
 
295
295
  def __init__(
296
296
  self,
297
- title: str | None = "",
297
+ title: str = "",
298
298
  video: str | VideoRef | None = None,
299
- content: str | None = "",
299
+ content: str = "",
300
300
  ):
301
301
  video = VideoRef(url=video) if isinstance(video, str) else video
302
302
  super().__init__(
@@ -313,9 +313,9 @@ class ImageParseResult(ParseResult):
313
313
 
314
314
  def __init__(
315
315
  self,
316
- title: str | None = "",
316
+ title: str = "",
317
317
  photo: Sequence[str | ImageRef | AniRef | LivePhotoRef] | None = None,
318
- content: str | None = "",
318
+ content: str = "",
319
319
  ):
320
320
  media = [ImageRef(url=p) if isinstance(p, str) else p for p in photo] if photo else None
321
321
  super().__init__(title=title, media=media, content=content)
@@ -328,9 +328,9 @@ class MultimediaParseResult(ParseResult):
328
328
 
329
329
  def __init__(
330
330
  self,
331
- title: str | None = "",
331
+ title: str = "",
332
332
  media: Sequence[AnyMediaRef] | None = None,
333
- content: str | None = "",
333
+ content: str = "",
334
334
  ):
335
335
  super().__init__(title=title, media=media, content=content)
336
336
 
@@ -342,16 +342,16 @@ class RichTextParseResult(ParseResult):
342
342
 
343
343
  def __init__(
344
344
  self,
345
- title: str | None = "",
345
+ title: str = "",
346
346
  media: Sequence[AnyMediaRef] | None = None,
347
- markdown_content: str | None = "",
347
+ markdown_content: str = "",
348
348
  ):
349
349
  """
350
350
  :param title: 标题
351
351
  :param media: 文章中的媒体
352
352
  :param markdown_content: markdown 格式正文
353
353
  """
354
- self.markdown_content = markdown_content or ""
354
+ self.markdown_content = markdown_content
355
355
  super().__init__(title=title, media=media, content=self.plaintext_content)
356
356
 
357
357
  def __repr__(self) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: parsehub
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: 轻量、异步、开箱即用的社交媒体聚合解析库
5
5
  Author-email: 梓澪 <zilingmio@gmail.com>
6
6
  License: MIT
@@ -34,7 +34,7 @@ Requires-Dist: python-slugify[unidecode]>=8.0.4
34
34
  Requires-Dist: opencv-python-headless>=4.13.0.92
35
35
  Requires-Dist: cryptography>=49.0.0
36
36
  Requires-Dist: gmssl>=3.2.2
37
- Requires-Dist: SignerPy>=0.12.0
37
+ Requires-Dist: signerpy>=0.12.0
38
38
  Provides-Extra: cli
39
39
  Requires-Dist: argcomplete>=3.6.3; extra == "cli"
40
40
  Requires-Dist: platformdirs>=4.5.1; extra == "cli"
@@ -17,7 +17,7 @@ python-slugify[unidecode]>=8.0.4
17
17
  opencv-python-headless>=4.13.0.92
18
18
  cryptography>=49.0.0
19
19
  gmssl>=3.2.2
20
- SignerPy>=0.12.0
20
+ signerpy>=0.12.0
21
21
 
22
22
  [cli]
23
23
  argcomplete>=3.6.3
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes