pyscriptbase 1.1.5__tar.gz → 1.2.0__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.
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/PKG-INFO +8 -2
- pyscriptbase-1.2.0/pyscriptbase/ocr.py +109 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase/pusher.py +2 -2
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase.egg-info/PKG-INFO +8 -2
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase.egg-info/SOURCES.txt +3 -1
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase.egg-info/requires.txt +1 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/setup.py +2 -1
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/test/test.py +1 -1
- pyscriptbase-1.2.0/test/test_ocr.py +92 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/README.md +0 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase/__init__.py +0 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase/app.py +0 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase/cipher.py +0 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase/database.py +0 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase/env.py +0 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase/net.py +0 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase/panel.py +0 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase/script.py +0 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase/sms.py +0 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase/util.py +0 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase/webview.py +0 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase.egg-info/dependency_links.txt +0 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/pyscriptbase.egg-info/top_level.txt +0 -0
- {pyscriptbase-1.1.5 → pyscriptbase-1.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyscriptbase
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: python script base library
|
|
5
5
|
Home-page:
|
|
6
6
|
Author: ASMan
|
|
@@ -19,6 +19,12 @@ Requires-Dist: beautifulsoup4
|
|
|
19
19
|
Requires-Dist: requests
|
|
20
20
|
Requires-Dist: lxml
|
|
21
21
|
Requires-Dist: brotli
|
|
22
|
+
Requires-Dist: pyperclip
|
|
23
|
+
Dynamic: author
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: description-content-type
|
|
26
|
+
Dynamic: requires-dist
|
|
27
|
+
Dynamic: summary
|
|
22
28
|
|
|
23
29
|
# Instructions
|
|
24
30
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# encoding=utf8
|
|
2
|
+
|
|
3
|
+
from urllib.parse import urlsplit
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from . import env as Env
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OCRError(RuntimeError):
|
|
11
|
+
"""
|
|
12
|
+
OCR 服务返回异常或无效响应
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OCRClient:
|
|
17
|
+
def __init__(
|
|
18
|
+
self, host: str = None, ocrHost: str = None, timeout: float = 30
|
|
19
|
+
) -> None:
|
|
20
|
+
"""
|
|
21
|
+
@host:pyai 服务地址,默认读取 PYAI_HOST,未配置时使用 http://192.168.8.103:8000
|
|
22
|
+
@ocrHost:pyocr 服务地址,默认读取 PYOCR_HOST,调用文字识别前需配置
|
|
23
|
+
@timeout:请求超时时间,单位为秒
|
|
24
|
+
"""
|
|
25
|
+
if host is None:
|
|
26
|
+
host = Env.get("PYAI_HOST", "http://192.168.8.103:8000")
|
|
27
|
+
if ocrHost is None:
|
|
28
|
+
ocrHost = Env.get("PYOCR_HOST")
|
|
29
|
+
self.host = self.__validAddress__(host)
|
|
30
|
+
self.ocrHost = self.__validAddress__(ocrHost) if ocrHost else None
|
|
31
|
+
self.timeout = timeout
|
|
32
|
+
|
|
33
|
+
def __validAddress__(self, address: str) -> str:
|
|
34
|
+
address = address.strip()
|
|
35
|
+
if not address:
|
|
36
|
+
raise ValueError("OCR 服务地址不能为空")
|
|
37
|
+
if "://" not in address:
|
|
38
|
+
address = f"http://{address}"
|
|
39
|
+
parsed = urlsplit(address)
|
|
40
|
+
if parsed.scheme not in ("http", "https") or not parsed.netloc:
|
|
41
|
+
raise ValueError("OCR 服务地址必须为有效的 HTTP 或 HTTPS 地址")
|
|
42
|
+
if parsed.query or parsed.fragment:
|
|
43
|
+
raise ValueError("OCR 服务地址不能包含查询参数或片段")
|
|
44
|
+
return address.rstrip("/")
|
|
45
|
+
|
|
46
|
+
def __request__(self, host: str, path: str, data: dict):
|
|
47
|
+
response = requests.post(
|
|
48
|
+
url=f"{host}{path}", json=data, timeout=self.timeout
|
|
49
|
+
)
|
|
50
|
+
response.raise_for_status()
|
|
51
|
+
try:
|
|
52
|
+
result = response.json()
|
|
53
|
+
except ValueError as e:
|
|
54
|
+
raise OCRError("OCR 服务返回的不是有效 JSON") from e
|
|
55
|
+
if not isinstance(result, dict):
|
|
56
|
+
raise OCRError("OCR 服务返回格式错误")
|
|
57
|
+
return result
|
|
58
|
+
|
|
59
|
+
def classification(self, b64Data: str, model: str = "tencent") -> dict:
|
|
60
|
+
"""
|
|
61
|
+
验证码识别,model 支持 tencent(腾讯数据模型)和 origin(原始模型)
|
|
62
|
+
"""
|
|
63
|
+
if model not in ("tencent", "origin"):
|
|
64
|
+
raise ValueError("验证码模型仅支持 tencent 和 origin")
|
|
65
|
+
return self.__request__(
|
|
66
|
+
self.host, f"/api/recognize/classification/{model}", {"data": b64Data}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def getPredictCode(self, b64Data: str) -> dict:
|
|
70
|
+
"""
|
|
71
|
+
腾讯模型验证码识别,返回完整 JSON 响应
|
|
72
|
+
"""
|
|
73
|
+
return self.classification(b64Data)
|
|
74
|
+
|
|
75
|
+
def rotate(self, b64Data: str) -> dict:
|
|
76
|
+
"""
|
|
77
|
+
识别图片旋转角度,返回完整 JSON 响应
|
|
78
|
+
"""
|
|
79
|
+
return self.__request__(
|
|
80
|
+
self.host, "/api/recognize/rotate", {"data": b64Data}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def match(self, target: str, background: str) -> dict:
|
|
84
|
+
"""
|
|
85
|
+
背景缺口匹配,target 和 background 均为 Base64 图片,返回完整 JSON 响应
|
|
86
|
+
"""
|
|
87
|
+
return self.__request__(
|
|
88
|
+
self.host,
|
|
89
|
+
"/api/recognize/match/origin",
|
|
90
|
+
{"target": target, "background": background},
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def detect(self, b64Data: str) -> dict:
|
|
94
|
+
"""
|
|
95
|
+
检测图片中的文字区域,返回完整 JSON 响应
|
|
96
|
+
"""
|
|
97
|
+
return self.__request__(
|
|
98
|
+
self.host, "/api/recognize/detect/origin", {"data": b64Data}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def ocr(self, b64Data: str) -> dict:
|
|
102
|
+
"""
|
|
103
|
+
pyocr 文字识别,返回完整 JSON 响应,保留 cost 等字段
|
|
104
|
+
"""
|
|
105
|
+
if not self.ocrHost:
|
|
106
|
+
raise ValueError("文字识别需要配置 pyocr 服务地址 ocrHost")
|
|
107
|
+
return self.__request__(
|
|
108
|
+
self.ocrHost, "/api/paddlex/orc", {"data": b64Data}
|
|
109
|
+
)
|
|
@@ -165,7 +165,7 @@ def sendQQMail(
|
|
|
165
165
|
|
|
166
166
|
# 连接到SMTP服务器并发送邮件
|
|
167
167
|
try:
|
|
168
|
-
server = smtplib.SMTP(smtp_server, port)
|
|
168
|
+
server = smtplib.SMTP(smtp_server, port, timeout=20)
|
|
169
169
|
server.starttls() # 启动TLS模式
|
|
170
170
|
server.login(sender, password) # 登录你的账户
|
|
171
171
|
server.sendmail(sender, receiver, message.as_string()) # 发送邮件
|
|
@@ -207,7 +207,7 @@ def sendMail(
|
|
|
207
207
|
|
|
208
208
|
# 连接到SMTP服务器并发送邮件
|
|
209
209
|
try:
|
|
210
|
-
server = smtplib.SMTP(smtp_server, port)
|
|
210
|
+
server = smtplib.SMTP(smtp_server, port, timeout=20)
|
|
211
211
|
server.starttls() # 启动TLS模式
|
|
212
212
|
server.login(sender, password) # 登录你的账户
|
|
213
213
|
server.sendmail(sender, receiver, message.as_string()) # 发送邮件
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyscriptbase
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: python script base library
|
|
5
5
|
Home-page:
|
|
6
6
|
Author: ASMan
|
|
@@ -19,6 +19,12 @@ Requires-Dist: beautifulsoup4
|
|
|
19
19
|
Requires-Dist: requests
|
|
20
20
|
Requires-Dist: lxml
|
|
21
21
|
Requires-Dist: brotli
|
|
22
|
+
Requires-Dist: pyperclip
|
|
23
|
+
Dynamic: author
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: description-content-type
|
|
26
|
+
Dynamic: requires-dist
|
|
27
|
+
Dynamic: summary
|
|
22
28
|
|
|
23
29
|
# Instructions
|
|
24
30
|
|
|
@@ -6,6 +6,7 @@ pyscriptbase/cipher.py
|
|
|
6
6
|
pyscriptbase/database.py
|
|
7
7
|
pyscriptbase/env.py
|
|
8
8
|
pyscriptbase/net.py
|
|
9
|
+
pyscriptbase/ocr.py
|
|
9
10
|
pyscriptbase/panel.py
|
|
10
11
|
pyscriptbase/pusher.py
|
|
11
12
|
pyscriptbase/script.py
|
|
@@ -17,4 +18,5 @@ pyscriptbase.egg-info/SOURCES.txt
|
|
|
17
18
|
pyscriptbase.egg-info/dependency_links.txt
|
|
18
19
|
pyscriptbase.egg-info/requires.txt
|
|
19
20
|
pyscriptbase.egg-info/top_level.txt
|
|
20
|
-
test/test.py
|
|
21
|
+
test/test.py
|
|
22
|
+
test/test_ocr.py
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="pyscriptbase", # 包名
|
|
5
|
-
version="1.
|
|
5
|
+
version="1.2.0", # 版本号
|
|
6
6
|
packages=find_packages(), # 自动查找包
|
|
7
7
|
author="ASMan",
|
|
8
8
|
author_email="",
|
|
@@ -24,5 +24,6 @@ setup(
|
|
|
24
24
|
"requests",
|
|
25
25
|
"lxml",
|
|
26
26
|
"brotli",
|
|
27
|
+
"pyperclip",
|
|
27
28
|
], # 依赖包列表
|
|
28
29
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from pyscriptbase.pusher import SMTPParams, sendMail, HtmlTableEmail
|
|
2
2
|
|
|
3
3
|
if __name__ == "__main__":
|
|
4
|
-
params = SMTPParams("smtp.qq.com", 587, "
|
|
4
|
+
params = SMTPParams("smtp.qq.com", 587, "dotoolsender@qq.com", "swecxgotxpfxdahe")
|
|
5
5
|
table = HtmlTableEmail("Pupu", ["备注", "朴分", "红包"])
|
|
6
6
|
table.rows.append(["测试", "100", "3.54"])
|
|
7
7
|
table.rows.append(["测试", "100", "3.54"])
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import unittest
|
|
3
|
+
from unittest.mock import Mock, patch
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from pyscriptbase.ocr import OCRClient, OCRError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OCRClientTest(unittest.TestCase):
|
|
11
|
+
@patch.dict(os.environ, {}, clear=True)
|
|
12
|
+
def test_default_host(self):
|
|
13
|
+
self.assertEqual(OCRClient().host, "http://192.168.8.103:8000")
|
|
14
|
+
|
|
15
|
+
@patch.dict(os.environ, {"PYAI_HOST": "https://ai.example/", "PYOCR_HOST": "https://ocr.example/"})
|
|
16
|
+
def test_environment_and_explicit_hosts(self):
|
|
17
|
+
client = OCRClient()
|
|
18
|
+
self.assertEqual(client.host, "https://ai.example")
|
|
19
|
+
self.assertEqual(client.ocrHost, "https://ocr.example")
|
|
20
|
+
client = OCRClient("localhost:8000/", "https://custom.example/")
|
|
21
|
+
self.assertEqual(client.host, "http://localhost:8000")
|
|
22
|
+
self.assertEqual(client.ocrHost, "https://custom.example")
|
|
23
|
+
|
|
24
|
+
@patch("pyscriptbase.ocr.requests.post")
|
|
25
|
+
def test_routes(self, post):
|
|
26
|
+
client = OCRClient("https://ai.example/", "https://ocr.example/", timeout=12)
|
|
27
|
+
cases = [
|
|
28
|
+
(lambda: client.classification("image"), "https://ai.example/api/recognize/classification/tencent", {"data": "image"}, "abcd"),
|
|
29
|
+
(lambda: client.classification("image", "origin"), "https://ai.example/api/recognize/classification/origin", {"data": "image"}, "1234"),
|
|
30
|
+
(lambda: client.getPredictCode("image"), "https://ai.example/api/recognize/classification/tencent", {"data": "image"}, "abcd"),
|
|
31
|
+
(lambda: client.rotate("image"), "https://ai.example/api/recognize/rotate", {"data": "image"}, 0),
|
|
32
|
+
(lambda: client.match("target", "background"), "https://ai.example/api/recognize/match/origin", {"target": "target", "background": "background"}, {"target": [1, 2, 3, 4]}),
|
|
33
|
+
(lambda: client.detect("image"), "https://ai.example/api/recognize/detect/origin", {"data": "image"}, []),
|
|
34
|
+
(lambda: client.ocr("image"), "https://ocr.example/api/paddlex/orc", {"data": "image"}, [{"text": "文字", "score": 0.99, "x": 1, "y": 2, "w": 3, "h": 4}]),
|
|
35
|
+
]
|
|
36
|
+
for call, url, data, expected in cases:
|
|
37
|
+
with self.subTest(url=url):
|
|
38
|
+
post.return_value = Mock()
|
|
39
|
+
result = {"code": 0, "data": expected, "msg": "", "cost": 0.25}
|
|
40
|
+
post.return_value.json.return_value = result
|
|
41
|
+
self.assertIs(call(), result)
|
|
42
|
+
post.assert_called_with(url=url, json=data, timeout=12)
|
|
43
|
+
post.return_value.raise_for_status.assert_called_once_with()
|
|
44
|
+
|
|
45
|
+
@patch("pyscriptbase.ocr.requests.post")
|
|
46
|
+
def test_invalid_responses(self, post):
|
|
47
|
+
client = OCRClient()
|
|
48
|
+
for result in [{"code": 200, "data": None, "msg": "not ready"}, {"code": 201}, {"data": "abcd"}, {"code": 0}, {"code": 0, "data": None}]:
|
|
49
|
+
with self.subTest(result=result):
|
|
50
|
+
post.return_value.json.return_value = result
|
|
51
|
+
self.assertIs(client.classification("image"), result)
|
|
52
|
+
self.assertIs(client.getPredictCode("image"), result)
|
|
53
|
+
post.return_value.json.return_value = []
|
|
54
|
+
with self.assertRaises(OCRError):
|
|
55
|
+
client.classification("image")
|
|
56
|
+
post.return_value.json.side_effect = ValueError("无效 JSON")
|
|
57
|
+
with self.assertRaises(OCRError):
|
|
58
|
+
client.classification("image")
|
|
59
|
+
with self.assertRaises(OCRError):
|
|
60
|
+
client.getPredictCode("image")
|
|
61
|
+
|
|
62
|
+
@patch("pyscriptbase.ocr.requests.post")
|
|
63
|
+
def test_request_failures(self, post):
|
|
64
|
+
client = OCRClient()
|
|
65
|
+
post.side_effect = requests.Timeout()
|
|
66
|
+
with self.assertRaises(requests.Timeout):
|
|
67
|
+
client.rotate("image")
|
|
68
|
+
with self.assertRaises(requests.Timeout):
|
|
69
|
+
client.getPredictCode("image")
|
|
70
|
+
post.side_effect = None
|
|
71
|
+
post.return_value.raise_for_status.side_effect = requests.HTTPError()
|
|
72
|
+
with self.assertRaises(requests.HTTPError):
|
|
73
|
+
client.classification("image")
|
|
74
|
+
with self.assertRaises(requests.HTTPError):
|
|
75
|
+
client.getPredictCode("image")
|
|
76
|
+
|
|
77
|
+
@patch.dict(os.environ, {}, clear=True)
|
|
78
|
+
@patch("pyscriptbase.ocr.requests.post")
|
|
79
|
+
def test_invalid_configuration(self, post):
|
|
80
|
+
for host in ["", " ", "ftp://example", "https://", "https://example?x=1"]:
|
|
81
|
+
with self.subTest(host=host), self.assertRaises(ValueError):
|
|
82
|
+
OCRClient(host)
|
|
83
|
+
client = OCRClient()
|
|
84
|
+
with self.assertRaises(ValueError):
|
|
85
|
+
client.ocr("image")
|
|
86
|
+
with self.assertRaises(ValueError):
|
|
87
|
+
client.classification("image", "unknown")
|
|
88
|
+
post.assert_not_called()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|