jwf-common-python 1.0.0__py3-none-any.whl
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.
- jwf_common_python/__init__.py +14 -0
- jwf_common_python/db_utils/__init__.py +6 -0
- jwf_common_python/db_utils/clickhouse.py +124 -0
- jwf_common_python/db_utils/elasticsearch.py +196 -0
- jwf_common_python/db_utils/mysql.py +64 -0
- jwf_common_python/eudic/__init__.py +5 -0
- jwf_common_python/eudic/wordbook.py +211 -0
- jwf_common_python/file_storage/__init__.py +2 -0
- jwf_common_python/file_storage/file_interface.py +29 -0
- jwf_common_python/file_storage/local.py +50 -0
- jwf_common_python/file_storage/tencent_cos.py +65 -0
- jwf_common_python/geography/__init__.py +8 -0
- jwf_common_python/geography/district_amap.py +181 -0
- jwf_common_python/geography/search_amap.py +84 -0
- jwf_common_python/github/__init__.py +5 -0
- jwf_common_python/github/webhooks.py +87 -0
- jwf_common_python/language_utils/__init__.py +34 -0
- jwf_common_python/language_utils/core.py +203 -0
- jwf_common_python/language_utils/files.py +52 -0
- jwf_common_python/language_utils/web.py +117 -0
- jwf_common_python/llm/__init__.py +1 -0
- jwf_common_python/llm/aliyun_qwen.py +10 -0
- jwf_common_python/llm/azure_gpt.py +66 -0
- jwf_common_python/llm/baidu_ernie.py +112 -0
- jwf_common_python/message_queue/__init__.py +7 -0
- jwf_common_python/message_queue/interface.py +49 -0
- jwf_common_python/message_queue/kafka.py +145 -0
- jwf_common_python/message_queue/rabbitmq.py +113 -0
- jwf_common_python/oauth2/__init__.py +0 -0
- jwf_common_python/oauth2/google.py +158 -0
- jwf_common_python/parse_subtitle/__init__.py +6 -0
- jwf_common_python/parse_subtitle/aa.vtt +2364 -0
- jwf_common_python/parse_subtitle/parse_srt_file.py +60 -0
- jwf_common_python/pdf/__init__.py +0 -0
- jwf_common_python/pdf/aaa.py +38 -0
- jwf_common_python/pdf/local_parse.py +259 -0
- jwf_common_python/sms/__init__.py +1 -0
- jwf_common_python/sms/interface.py +7 -0
- jwf_common_python/sms/tencent_sms.py +117 -0
- jwf_common_python/translate/__init__.py +152 -0
- jwf_common_python/translate/_youdao.py +234 -0
- jwf_common_python/translate/microsoft.py +696 -0
- jwf_common_python/translate/tencent.py +194 -0
- jwf_common_python/tts/__init__.py +0 -0
- jwf_common_python/tts/microsoft.py +87 -0
- jwf_common_python/utils/__init__.py +0 -0
- jwf_common_python/utils/image_utils.py +18 -0
- jwf_common_python/utils/tencent_utils.py +51 -0
- jwf_common_python/utils/utils.py +46 -0
- jwf_common_python/video/__init__.py +5 -0
- jwf_common_python/video/merge.py +276 -0
- jwf_common_python/weather/__init__.py +0 -0
- jwf_common_python/weather/amap.py +127 -0
- jwf_common_python/weather/wanwei.py +58 -0
- jwf_common_python/youdao/__init__.py +13 -0
- jwf_common_python/youdao/wordbook.py +88 -0
- jwf_common_python-1.0.0.dist-info/METADATA +669 -0
- jwf_common_python-1.0.0.dist-info/RECORD +60 -0
- jwf_common_python-1.0.0.dist-info/WHEEL +4 -0
- jwf_common_python-1.0.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jwf-common-python
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 封装了一些常用功能,GitHub: https://github.com/jiangwenfan/common_packages/tree/main
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: evans.jiang
|
|
8
|
+
Author-email: evans.jiang@gmail.com
|
|
9
|
+
Requires-Python: >=3.11,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
17
|
+
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# jwf-common-python
|
|
21
|
+
封装了一些通用的功能
|
|
22
|
+
|
|
23
|
+
**安装**
|
|
24
|
+
```bash
|
|
25
|
+
pip install jwf-common-python
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**从旧包迁移**
|
|
29
|
+
|
|
30
|
+
从 `1.0.0` 起,安装名改为 `jwf-common-python`,导入名改为 `jwf_common_python`。
|
|
31
|
+
不再提供 `common_packages` 或 `common_python` 导入路径。调用项目需要更新依赖声明、锁文件及所有导入。
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip uninstall common-packages
|
|
35
|
+
pip install jwf-common-python
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
例如,将 `from common_packages.translate import WordInfo` 改为
|
|
39
|
+
`from jwf_common_python.translate import WordInfo`。
|
|
40
|
+
|
|
41
|
+
[**pypi首页**](https://pypi.org/project/jwf-common-python/#description)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
> 可以 README 使用右边的目录进行快速浏览
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## 1. db utils
|
|
49
|
+
### [x] mysql
|
|
50
|
+
**安装依赖**
|
|
51
|
+
```bash
|
|
52
|
+
```
|
|
53
|
+
**使用**
|
|
54
|
+
```python
|
|
55
|
+
# mysql配置
|
|
56
|
+
mysql_config = {
|
|
57
|
+
"host": "xxx"
|
|
58
|
+
"port": xxx
|
|
59
|
+
"user" : "xxx"
|
|
60
|
+
"password" :"xxx"
|
|
61
|
+
"database" : "xxx"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
mysql_op = MysqlOperator(**mysql_config)
|
|
65
|
+
|
|
66
|
+
# 接口1: 获取所有表名
|
|
67
|
+
tables: set[str] = mysql_op.fetch_all_tables()
|
|
68
|
+
|
|
69
|
+
# 接口2: 执行`查询sql`,返回执行结果
|
|
70
|
+
data: DataFrame, columns: list[str] = mysql_op.fetch_specify_sql_data("SELECT * FROM customers")
|
|
71
|
+
|
|
72
|
+
# 接口3: 执行`插入sql`,使用指定dataframe数据,指定表名
|
|
73
|
+
need_insert_data = [
|
|
74
|
+
{"customer_name": "test1", "email": "test1@gmail.com"},
|
|
75
|
+
{"customer_name": "test2", "email": "test2@gmail.com"},
|
|
76
|
+
]
|
|
77
|
+
need_insert_dataframe = pd.DataFrame(need_insert_data)
|
|
78
|
+
rows: int = mysql_op.insert_database_mysql(need_insert_dataframe, "customers")
|
|
79
|
+
```
|
|
80
|
+
## 2. file storage
|
|
81
|
+
### [x] local storage
|
|
82
|
+
```python
|
|
83
|
+
# 接口对象
|
|
84
|
+
local_config = {
|
|
85
|
+
"storage_home_dir" = "xxx"
|
|
86
|
+
}
|
|
87
|
+
local = LocalStorage(**local_config)
|
|
88
|
+
|
|
89
|
+
# 接口1: 写入本地文件内容
|
|
90
|
+
file_name: str = "test_save_file.txt"
|
|
91
|
+
content: bytes = "abc测试123!@!@".encode()
|
|
92
|
+
|
|
93
|
+
# file_path 是路径
|
|
94
|
+
status: bool, file_path: str = local_obj.save(file_name, content)
|
|
95
|
+
|
|
96
|
+
# 接口2: 读取本地文件内容
|
|
97
|
+
file_name: str = "test_save_file.txt"
|
|
98
|
+
content: bytes = "abc测试123!@!@".encode()
|
|
99
|
+
|
|
100
|
+
status: bool, actual_content: str = local_obj.load(file_name)
|
|
101
|
+
```
|
|
102
|
+
### [x] tencent cos storage
|
|
103
|
+
```python
|
|
104
|
+
# 接口对象
|
|
105
|
+
tencent_cos_config = {
|
|
106
|
+
"bucket": "xxx",
|
|
107
|
+
"region": "xxx",
|
|
108
|
+
"secret_key": "xxx",
|
|
109
|
+
"secret_id": "xxx"
|
|
110
|
+
}
|
|
111
|
+
tencent_cos = TencentCos(**tencent_cos_config)
|
|
112
|
+
|
|
113
|
+
# 接口1: 写入cos文件内容
|
|
114
|
+
file_name: str = "test_save_file.txt"
|
|
115
|
+
content: bytes = "abc测试123!@!@".encode()
|
|
116
|
+
|
|
117
|
+
status: bool, actual_file_name: str = tencent_cos_obj.save(
|
|
118
|
+
filename=file_name, content=content
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# 接口2: 读取cos文件内容
|
|
122
|
+
file_name: str = "test_save_file.txt"
|
|
123
|
+
|
|
124
|
+
status: bool, actual_content: bytes = tencent_cos_obj.load(file_name)
|
|
125
|
+
```
|
|
126
|
+
## 3. translation
|
|
127
|
+
> 封装翻译接口
|
|
128
|
+
>
|
|
129
|
+
|
|
130
|
+
### [x] mircrosoft 翻译
|
|
131
|
+
```python
|
|
132
|
+
from jwf_common_python.translate import SentenceInfo, WordInfo
|
|
133
|
+
from jwf_common_python.translate.microsoft import TranslateMicrosoft
|
|
134
|
+
|
|
135
|
+
# ------- 初始化 -------
|
|
136
|
+
config: dict = {
|
|
137
|
+
"text_endpoint": "xxx",
|
|
138
|
+
"word_endpoint": "xxx",
|
|
139
|
+
"word_sample_endpoint": "xxx",
|
|
140
|
+
"location_region": "xxx",
|
|
141
|
+
"key": "xxx",
|
|
142
|
+
"tts_microsoft_config": {
|
|
143
|
+
"key": "xxx",
|
|
144
|
+
"location_region": "xxx",
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
microsoft_obj = TranslateMicrosoft(**config)
|
|
148
|
+
|
|
149
|
+
# ------- 1. 单词api -------
|
|
150
|
+
# 获取原生单词翻译结果
|
|
151
|
+
raw_word_translate_res = translate_microsoft_obj.translate_word(
|
|
152
|
+
word="fly",
|
|
153
|
+
source_language_code="en",
|
|
154
|
+
target_language_code="zh-CN",
|
|
155
|
+
)
|
|
156
|
+
# 格式化为标准结果
|
|
157
|
+
res: WordInfo = translate_microsoft_obj.format_word_response(raw_word_translate_res)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ------- 2. 句子api -------
|
|
161
|
+
# 获取原生句子翻译结果
|
|
162
|
+
res_tran: dict = translate_microsoft_obj.translate_sentence(
|
|
163
|
+
text=self.text,
|
|
164
|
+
source_language_code=self.source_language_code,
|
|
165
|
+
target_language_code=self.target_language_code,
|
|
166
|
+
)
|
|
167
|
+
# 格式化为标准结果
|
|
168
|
+
res: SentenceInfo = translate_microsoft_obj.format_sentence_response(sentence_translate_data)
|
|
169
|
+
```
|
|
170
|
+
- 参考:
|
|
171
|
+
- []()
|
|
172
|
+
- 创建文本翻译服务
|
|
173
|
+
- [tts文档](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/index-text-to-speech)
|
|
174
|
+
- 创建tts服务:在`Azure` --> `Azure AI services` --> `语音服务`
|
|
175
|
+
|
|
176
|
+
- 将要支持的:
|
|
177
|
+
- [ ] google 翻译
|
|
178
|
+
- [ ] Deepl 翻译
|
|
179
|
+
- [ ] openAI 翻译
|
|
180
|
+
- 兼容已有服务:
|
|
181
|
+
- [x] tencent TMT 文本翻译,见下方用法及接口下线说明
|
|
182
|
+
- 不打算支持的:
|
|
183
|
+
- youdao 翻译
|
|
184
|
+
|
|
185
|
+
### [x] 腾讯云 TMT 文本翻译(旧接口兼容)
|
|
186
|
+
|
|
187
|
+
`jwf_common_python.translate.tencent.TencentTranslate` 保留原有文本翻译接口,
|
|
188
|
+
补齐单词、句子翻译及标准化结果。密钥、地域、项目 ID、超时均通过参数传入;
|
|
189
|
+
导入模块时不加载 SDK,函数不打印、不写文件,SDK 异常直接向调用方传播。
|
|
190
|
+
|
|
191
|
+
此模块使用官方 `CommonClient` 调用已有 TMT 文本接口,无需安装 TMT 产品 SDK,
|
|
192
|
+
也不要求降级其他产品的 SDK。依照[腾讯云下线公告](https://cloud.tencent.com/announce/detail/2482),
|
|
193
|
+
该接口计划于 2027-01-31 停止销售、2030-12-31 停止所有服务。
|
|
194
|
+
新项目应参考公告中的替代方案,本模块不自动切换接口。
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
pip install 'tencentcloud-sdk-python-common>=3.0.1042,<4'
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from jwf_common_python.translate.tencent import TencentTranslate
|
|
202
|
+
|
|
203
|
+
translator = TencentTranslate(
|
|
204
|
+
secret_id="your-secret-id", secret_key="your-secret-key",
|
|
205
|
+
region="ap-shanghai", project_id=0, timeout=30,
|
|
206
|
+
# token="temporary-credential-token", # 可选临时凭证
|
|
207
|
+
)
|
|
208
|
+
# 完整 Response 响应体,保留 RequestId 以及未被预定义的服务端字段。
|
|
209
|
+
raw = translator.translate("hello", source_language_code="auto", target_language_code="zh-CN")
|
|
210
|
+
word = translator.translate_word("apple")
|
|
211
|
+
word_info = translator.format_word_response(word)
|
|
212
|
+
sentence = translator.translate_sentence("Hello world!")
|
|
213
|
+
sentence_info = translator.format_sentence_response(sentence)
|
|
214
|
+
translator.close() # 也可使用 with TencentTranslate(...) as translator:
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`translate` 每次创建独立参数字典,支持 `untranslated_text` 指定不翻译的内容。
|
|
218
|
+
`zh-CN` 自动转换为腾讯的 `zh`,`zh-TW` 保持不变;目标语言不能为 `auto`。
|
|
219
|
+
TMT 不提供词典和音频数据,标准化结果中的词性 Other、频率 0.0 及空例句、
|
|
220
|
+
变体、音频均为占位值;原始单词响应保留在 `extended_info`。
|
|
221
|
+
`download_audio_file` 显式抛出 `NotImplementedError`,音频应由 TTS 服务提供。
|
|
222
|
+
支持 `with` 和可重复调用的 `close()`,关闭后不能继续翻译。
|
|
223
|
+
|
|
224
|
+
离线测试(无需真实密钥、不调用腾讯云服务):
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
python -m unittest discover -s tests/test_tencent_translate -v
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## 4. tts接口
|
|
231
|
+
> 封装文本转语音接口
|
|
232
|
+
|
|
233
|
+
### [x] mircrosoft 文本转语音
|
|
234
|
+
依赖:
|
|
235
|
+
- import requests
|
|
236
|
+
```python
|
|
237
|
+
tts_config = {
|
|
238
|
+
key: "xx",
|
|
239
|
+
location_region: "xx",
|
|
240
|
+
language: "en|zh"
|
|
241
|
+
}
|
|
242
|
+
tts = TTSMicrosoft(tts_config)
|
|
243
|
+
audio: bytes = tts.convert_text_to_speech("hello 123")
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## 5. Ai接口
|
|
247
|
+
> 解释句子,分析长难句
|
|
248
|
+
- [ ] openAI 翻译
|
|
249
|
+
- [ ] gemini 翻译
|
|
250
|
+
|
|
251
|
+
## 6. sms
|
|
252
|
+
腾讯云
|
|
253
|
+
```python
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
- [x] 可选导入模块,未安装时,提示安装!
|
|
258
|
+
## 7. oauth
|
|
259
|
+
[google文档](https://developers.google.com/identity/protocols/oauth2)
|
|
260
|
+
- 1. [访问控制台](https://console.developers.google.com/) , 创建相关平台的凭据,选择`OAuth 2.0 客户端 ID`
|
|
261
|
+
- 2. [调试范围支持的api](https://developers.google.com/oauthplayground/)
|
|
262
|
+
- 3. [范围支持的api](https://developers.google.com/identity/protocols/oauth2/scopes)
|
|
263
|
+
|
|
264
|
+
google oauth2:
|
|
265
|
+
配置文件:
|
|
266
|
+
```toml
|
|
267
|
+
[oauth2.google]
|
|
268
|
+
client_id = "xxx"
|
|
269
|
+
client_secret = "xxx"
|
|
270
|
+
# 认证完成之后的回调地址,必须与google console中配置的一致
|
|
271
|
+
redirect_uri = "http://localhost:9080/aaa"
|
|
272
|
+
# 访问权限
|
|
273
|
+
scope = "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"
|
|
274
|
+
```
|
|
275
|
+
使用:
|
|
276
|
+
```python
|
|
277
|
+
from jwf_common_python.oauth.google import GoogleOAuth2
|
|
278
|
+
|
|
279
|
+
config = global_config["oauth2"]["google"]
|
|
280
|
+
google_oauth2_obj = GoogleOAuth2(**config)
|
|
281
|
+
|
|
282
|
+
# 1. 获取授权地址
|
|
283
|
+
url = oauth2_google_obj.get_web_auth_url()
|
|
284
|
+
|
|
285
|
+
# 2. 获取用户信息
|
|
286
|
+
user_info = oauth2_google_obj.get_user_info("code 123")
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## 6. llm 大模型
|
|
290
|
+
百度的ernie大模型
|
|
291
|
+
配置文件:
|
|
292
|
+
```toml
|
|
293
|
+
[llm.baidu_ernie]
|
|
294
|
+
client_id = "xxxx"
|
|
295
|
+
client_secret = "xxxx"
|
|
296
|
+
```
|
|
297
|
+
使用:
|
|
298
|
+
```python
|
|
299
|
+
from jwf_common_python.llm.baidu_ernie import LLMBaiduErnie
|
|
300
|
+
|
|
301
|
+
config = global_config["llm"]["baidu_ernie"]
|
|
302
|
+
llm_baidu_ernie_obj = LLMBaiduErnie(**config)
|
|
303
|
+
|
|
304
|
+
# 1. 获取大模型回答结果
|
|
305
|
+
res: str = llm_baidu_ernie_obj.send_model_request(text)
|
|
306
|
+
|
|
307
|
+
# 2. 使用大模型判断是否为自然语言
|
|
308
|
+
status: bool = llm_baidu_ernie_obj.is_natural_language(text)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## 7. geography 地理位置
|
|
312
|
+
- [创建高德app](https://console.amap.com/dev/key/app)
|
|
313
|
+
- [api文档](https://lbs.amap.com/api/webservice/guide/api/district)
|
|
314
|
+
type分类
|
|
315
|
+
```
|
|
316
|
+
汽车服务
|
|
317
|
+
汽车销售
|
|
318
|
+
汽车维修
|
|
319
|
+
摩托车服务
|
|
320
|
+
餐饮服务
|
|
321
|
+
购物服务
|
|
322
|
+
生活服务
|
|
323
|
+
体育休闲服务
|
|
324
|
+
医疗保健服务
|
|
325
|
+
住宿服务
|
|
326
|
+
风景名胜
|
|
327
|
+
商务住宅
|
|
328
|
+
政府机构及社会团体
|
|
329
|
+
科教文化服务
|
|
330
|
+
交通设施服务
|
|
331
|
+
金融保险服务
|
|
332
|
+
公司企业
|
|
333
|
+
道路附属设施
|
|
334
|
+
地名地址信息
|
|
335
|
+
公共设施
|
|
336
|
+
事件活动
|
|
337
|
+
室内设施
|
|
338
|
+
虚拟数据
|
|
339
|
+
通行设施
|
|
340
|
+
|
|
341
|
+
汽车服务|汽车销售|汽车维修|摩托车服务|餐饮服务|购物服务|生活服务|体育休闲服务|医疗保健服务|住宿服务|风景名胜|商务住宅|政府机构及社会团体|科教文化服务|交通设施服务|金融保险服务|公司企业|道路附属设施|地名地址信息|公共设施|事件活动|室内设施|虚拟数据|通行设施
|
|
342
|
+
```
|
|
343
|
+
使用,查看单元测试文件
|
|
344
|
+
- 获取中国中国行政区
|
|
345
|
+
- 获取指定位置
|
|
346
|
+
```python
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## 英文文本与词表处理
|
|
350
|
+
|
|
351
|
+
`jwf_common_python.language_utils` 提供文本取词、章节提取、词表过滤、词频统计、
|
|
352
|
+
长度排序及网页取词,替代旧 `_other/_language_utils/language.py`。
|
|
353
|
+
导入时不扫描目录、不打印、不配置全局日志;处理结果直接返回,不写文件。
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
from jwf_common_python.language_utils import (
|
|
357
|
+
extract_words, get_chapter_words, word_frequencies, format_words,
|
|
358
|
+
read_word_lists, filter_words, fetch_words_from_pages,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
words = extract_words(
|
|
362
|
+
"Apple apple banana and pear.",
|
|
363
|
+
exclude_words=["and"], known_words=["pear"],
|
|
364
|
+
)
|
|
365
|
+
frequencies = word_frequencies(words) # [("apple", 2), ("banana", 1)]
|
|
366
|
+
word_list_text = format_words(words, top_n=100) # 返回逐行词表文本
|
|
367
|
+
chapter_words = get_chapter_words(
|
|
368
|
+
"Chapter One\nHello world!\nChapter Two\nNext chapter",
|
|
369
|
+
"Chapter One", "Chapter Two",
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# 读取文件时必须显式传入路径;不会自动读取 utils/、files/ 或当前目录词表。
|
|
373
|
+
# known = read_word_lists(["data/known.txt", "data/local_words.txt"])
|
|
374
|
+
# new_words = filter_words(words, known_words=known, unique=True)
|
|
375
|
+
# page_words = fetch_words_from_pages(["https://example.com"], known_words=known)
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
新旧功能对应:
|
|
379
|
+
|
|
380
|
+
| 旧功能 | 新接口 |
|
|
381
|
+
| --- | --- |
|
|
382
|
+
| 文本/字幕文件取词、排除无效词和已知词 | `extract_words`、`read_words`、`filter_words` |
|
|
383
|
+
| 合并本地词表、读取已掌握词 | `read_word_lists(paths)`,读取并合并所有指定文件 |
|
|
384
|
+
| 指定章节取词 | `extract_chapter`、`get_chapter_words` |
|
|
385
|
+
| 按词频写词表 | `word_frequencies` 返回计数,`format_words` 返回文本 |
|
|
386
|
+
| 长度分组、排序 | `group_words_by_length`、`sort_words_by_length` |
|
|
387
|
+
| 首字母大写判断 | `is_title_case_word` |
|
|
388
|
+
| URL 文件读取和网页取词 | `read_urls`、`parse_urls`、`fetch_words_from_pages` |
|
|
389
|
+
| 数据目录菜单 | `generate_file_menu(directory, prefix="wanglu_")` |
|
|
390
|
+
|
|
391
|
+
- 保留英文 `[A-Za-z]+` 分词规则,连字符、撇号、数字等均为分隔符,不做词典
|
|
392
|
+
校验或词形还原。文本函数也可接收逐行字符串的可迭代对象。
|
|
393
|
+
- 过滤默认忽略大小写、保留原文拼写和重复项;`unique=True` 按首次出现顺序去重,
|
|
394
|
+
`case_sensitive=True` 区分大小写。频率统计默认合并大小写,同频保留首次出现顺序;
|
|
395
|
+
`top_n=None` 返回全部,`top_n=0` 返回空结果。
|
|
396
|
+
- 章节标题按去除首尾空白后的整行匹配,默认从最后一次出现的起始标题提取,
|
|
397
|
+
跳过目录中的同名标题;可用 `occurrence` 选择其他位置。只查找起始标题之后的
|
|
398
|
+
结束标题,不包含标题本身;省略结束标题则提取到文末。
|
|
399
|
+
- `read_words`、`read_word_lists`、`read_urls` 使用显式路径,默认 UTF-8。
|
|
400
|
+
`generate_file_menu` 返回相对路径列表和菜单文本,默认递归读取 `.txt` 文件,
|
|
401
|
+
可传 `recursive=False` 限定首层。同名文件保留目录前缀,不会混淆。
|
|
402
|
+
- 网页取词返回 `{url: 单词列表}`,等待所有任务完成并保留 URL 输入顺序;
|
|
403
|
+
默认 3 个线程、30 秒超时、最短 2 字母并去重。过滤标签及脚本、样式等内容,
|
|
404
|
+
不执行 JavaScript;HTTP/网络错误直接抛出。排除词表支持一次性生成器。
|
|
405
|
+
- 已清理无实现的占位函数;词表过滤和长度处理统一由上述接口提供,
|
|
406
|
+
翻译调用使用 `jwf_common_python.translate`,不自动创建输出文件或目录。
|
|
407
|
+
|
|
408
|
+
离线测试:
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
python -m unittest discover -s tests/test_language_utils -v
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## 有道单词本
|
|
415
|
+
|
|
416
|
+
Cookie 通过参数传入,函数不读写本地文件、不打印结果。
|
|
417
|
+
导出返回每页完整的 JSON 响应,保留单词、释义及其他原始字段;
|
|
418
|
+
请求失败时抛出异常,不返回不完整的导出结果。
|
|
419
|
+
|
|
420
|
+
```python
|
|
421
|
+
from jwf_common_python.youdao import fetch_all_wordbook_data, filter_new_words
|
|
422
|
+
|
|
423
|
+
pages = fetch_all_wordbook_data(cookie="你的完整 Cookie 字符串")
|
|
424
|
+
current_words = [
|
|
425
|
+
item["word"]
|
|
426
|
+
for page in pages
|
|
427
|
+
for item in page["data"]["itemList"]
|
|
428
|
+
]
|
|
429
|
+
new_words = filter_new_words(existing_words=["hello", "world"], current_words=current_words)
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
`fetch_wordbook_data(cookie, limit=15, offset=0, timeout=30)` 可单独获取一页。
|
|
433
|
+
`fetch_all_wordbook_data(cookie, limit=15, timeout=30)` 自动分页获取全部响应。
|
|
434
|
+
`filter_new_words(existing_words, current_words)` 返回新增单词列表,按当前词表顺序
|
|
435
|
+
去重、去除首尾空白、忽略空词,大小写敏感;多行文本需先调用 `splitlines()`。
|
|
436
|
+
|
|
437
|
+
## 欧路词典生词本
|
|
438
|
+
|
|
439
|
+
`jwf_common_python.eudic.EudicClient` 提供创建生词本、列出生词本、导入单词和
|
|
440
|
+
分页导出功能,替代旧 `oulu.py`。使用项目已有的 `requests` 依赖。
|
|
441
|
+
|
|
442
|
+
```python
|
|
443
|
+
from jwf_common_python.eudic import EudicClient
|
|
444
|
+
|
|
445
|
+
# token 是完整 Authorization 值,按欧路提供的格式原样传入。
|
|
446
|
+
with EudicClient(token="NIS your-token", language="en", timeout=30) as client:
|
|
447
|
+
books = client.list_books()
|
|
448
|
+
created = client.create_book("本周新词")
|
|
449
|
+
book_id = created["data"]["id"]
|
|
450
|
+
imported = client.add_words(book_id, ["hello", "world"])
|
|
451
|
+
pages = client.export_book(book_id)
|
|
452
|
+
current_words = [item["word"] for page in pages for item in page["data"]]
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
- Token、语言、名称和生词本 ID 均由参数提供;`book_id=0` 支持默认生词本。
|
|
456
|
+
创建、列表、导入和单页查询返回完整 JSON,`export_book` 返回所有页的完整响应,
|
|
457
|
+
包括释义等原始字段,不打印、不写文件、不退出进程。
|
|
458
|
+
- `get_words(book_id, page=0, page_size=100, sort="asc")` 获取单页,
|
|
459
|
+
`export_book(book_id, page_size=100, sort="asc")` 从第 0 页自动分页。
|
|
460
|
+
接口页码上限为 50;到达上限仍为满页时抛出异常,避免将截断数据作为完整导出。
|
|
461
|
+
- `add_words` 单次接受 1~1000 个单词,不隐式创建生词本。默认使用兼容的
|
|
462
|
+
`words` 格式保留已有星级;传 `star=1~5` 时使用推荐的 `items` 格式设置本批星级。
|
|
463
|
+
两种模式均使用 `category_id`。详见[欧路官方 API 文档](https://my.eudic.net/OpenAPI/doc_api_study)。
|
|
464
|
+
- HTTP、超时及 JSON 错误直接抛出;使用 `with` 或 `close()` 释放连接。
|
|
465
|
+
|
|
466
|
+
离线测试(无需 Token、不连接欧路服务):
|
|
467
|
+
|
|
468
|
+
```bash
|
|
469
|
+
python -m unittest discover -s tests/test_eudic -v
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
## 消息队列
|
|
473
|
+
|
|
474
|
+
`jwf_common_python.message_queue` 封装 Kafka 和 RabbitMQ 的同步发送、阻塞消费。
|
|
475
|
+
按需安装后端依赖,导入模块不会自动连接服务器:
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
pip install 'kafka-python>=2.0.2,<3' # 使用 Kafka
|
|
479
|
+
pip install 'pika>=1.3.2,<2' # 使用 RabbitMQ
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
```python
|
|
483
|
+
from jwf_common_python.message_queue import KafkaQueue, RabbitmqQueue
|
|
484
|
+
|
|
485
|
+
with KafkaQueue(
|
|
486
|
+
bootstrap_servers=["localhost:9092"], topic="events", group_id="workers"
|
|
487
|
+
) as queue:
|
|
488
|
+
result = queue.enqueue(b"hello", key=b"user-1") # 返回 topic、partition、offset
|
|
489
|
+
json_result = queue.enqueue_json({"message": "你好"}, key=b"user-1")
|
|
490
|
+
# queue.consumer(handle_message) # 阻塞消费,handle_message 接收消息内容
|
|
491
|
+
|
|
492
|
+
with RabbitmqQueue(
|
|
493
|
+
queue_name="jobs", host="localhost", port=5672,
|
|
494
|
+
username="your-user", password="your-password",
|
|
495
|
+
) as queue:
|
|
496
|
+
queue.enqueue(b"hello")
|
|
497
|
+
# queue.consumer(handle_message)
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
- 发送内容使用 `bytes`,Kafka 消费回调也可能收到删除标记 `None`;
|
|
501
|
+
异常直接向调用方传播,不打印或吞掉发送失败。
|
|
502
|
+
- Kafka 的 `enqueue_json` 替代旧 `aa.py` 示例,编码为 UTF-8 JSON,等待当前消息
|
|
503
|
+
确认后返回 topic、partition、offset。无需每条消息全局 flush,不可序列化的数据和
|
|
504
|
+
NaN/Infinity 会在连接前报错;不能同时配置 `value_serializer`,避免二次序列化。
|
|
505
|
+
- 回调正常返回才确认消息;抛出异常会退出消费。Kafka 仅提交已处理分区的下一条
|
|
506
|
+
offset;RabbitMQ 默认将失败消息重新入队,可传 `requeue_on_error=False` 改为拒绝
|
|
507
|
+
(配置了死信队列时进入死信队列,否则丢弃)。回调应能处理重复投递。
|
|
508
|
+
- Kafka 连接按需创建,消费必须提供 `group_id`;`producer_config` 和
|
|
509
|
+
`consumer_config` 可透传 SSL/SASL 等 SDK 参数,不能覆盖消费的手动确认配置。
|
|
510
|
+
- RabbitMQ 默认持久化队列及消息,开启发布确认、强制路由,预取数为 1;
|
|
511
|
+
迁移已有非持久化队列时传 `durable=False`。`connection_config` 可设置
|
|
512
|
+
`virtual_host`、SSL 等参数。消费退出时会关闭该实例连接。
|
|
513
|
+
- 使用 `with` 或 `close()` 释放资源,实例不支持跨线程共享。
|
|
514
|
+
|
|
515
|
+
离线测试(需安装两个后端依赖,不需要启动服务器):
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
python -m unittest discover -s tests/test_message_queue -v
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## ClickHouse 和 Elasticsearch
|
|
522
|
+
|
|
523
|
+
工具位于 `jwf_common_python.db_utils`,导入时不加载对应 SDK,不依赖 Django
|
|
524
|
+
或业务模型;连接配置通过参数传入。所有方法直接返回结果,SDK 异常向调用方传播。
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
pip install 'clickhouse-driver>=0.2.6,<0.3' 'pandas>=2.1,<3' 'numpy>=1.26,<3'
|
|
528
|
+
pip install 'elasticsearch>=8,<9' # 以下示例使用 Elasticsearch 8.x 客户端
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
Elasticsearch 客户端主版本应与服务端匹配,参见[官方兼容性说明](https://www.elastic.co/docs/reference/elasticsearch/clients/python#compatibility)。
|
|
532
|
+
|
|
533
|
+
```python
|
|
534
|
+
from jwf_common_python.db_utils import ClickhouseOperator, ElasticsearchHandle
|
|
535
|
+
|
|
536
|
+
# ClickHouse 原生 TCP 端口为 9000,支持 secure、connect_timeout 等驱动参数。
|
|
537
|
+
with ClickhouseOperator(host="localhost", port=9000, database="analytics") as ck:
|
|
538
|
+
tables = ck.fetch_all_tables()
|
|
539
|
+
frame, columns, count = ck.fetch_specify_sql_data(
|
|
540
|
+
"SELECT * FROM events WHERE name = %(name)s",
|
|
541
|
+
params={"name": "example"},
|
|
542
|
+
replace_nonwords=False,
|
|
543
|
+
)
|
|
544
|
+
converted = ck.convert_datatype_for_ck(frame)
|
|
545
|
+
# inserted = ck.insert_specify_sql("INSERT INTO events VALUES", converted)
|
|
546
|
+
|
|
547
|
+
with ElasticsearchHandle(
|
|
548
|
+
hosts="http://localhost:9200", index="articles",
|
|
549
|
+
# basic_auth=("username", "password"), # 也支持 api_key、TLS 和超时配置
|
|
550
|
+
) as es:
|
|
551
|
+
# 输入可为生成器,由调用方从数据库或其他来源准备文档。
|
|
552
|
+
imported = es.init_data([{"id": 1, "content": "hello world"}], refresh="wait_for")
|
|
553
|
+
result = es.search_doc("hello", field="content", size=10, offset=0)
|
|
554
|
+
updated = es.update_data(1, {"content": "updated"})
|
|
555
|
+
# created = es.create_data(2, {"content": "new"})
|
|
556
|
+
# deleted = es.delete_doc(2)
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
- ClickHouse 保留原有查询、转换、写入接口,新增 `execute(sql, params=...)`。
|
|
560
|
+
category 转字符串时保留空值,返回副本,不修改传入 DataFrame。普通 SQL
|
|
561
|
+
可传 `settings={"use_numpy": False}`,无需安装 pandas/NumPy。
|
|
562
|
+
- Elasticsearch 的 `hosts` 和 `index` 必须显式传入。`init_data(documents)`
|
|
563
|
+
仅在索引不存在时创建索引,已有索引不重建;文档默认使用 `id` 字段。
|
|
564
|
+
`bulk_create` 默认每批 500 条,可配置 `id_field`、`chunk_size`、`refresh`。
|
|
565
|
+
重复 ID 抛出冲突,不覆盖已有文档;批量操作不是事务,部分成功不会回滚。
|
|
566
|
+
- Elasticsearch 增删改查返回完整响应体;`update_data` 接收部分字段字典,
|
|
567
|
+
不需要再包一层 `doc`。`search` 可直接传查询 DSL,`show_info` 保留默认搜索页行为。
|
|
568
|
+
- 两个工具均支持 `with` 和可重复调用的 `close()`;关闭后不能继续查询。
|
|
569
|
+
|
|
570
|
+
离线测试(安装上述依赖即可,无需启动服务):
|
|
571
|
+
|
|
572
|
+
```bash
|
|
573
|
+
python -m unittest discover -s tests/test_db_utils -v
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
## 视频合并
|
|
577
|
+
|
|
578
|
+
`jwf_common_python.video` 使用本机 FFmpeg/ffprobe,提供视频探测和不重新编码的
|
|
579
|
+
合并功能,替代旧 `_merge_video.py`,无需额外 Python 依赖。
|
|
580
|
+
调用前需安装 FFmpeg,并确保 `ffmpeg`、`ffprobe` 位于 PATH,或通过参数传入完整路径。
|
|
581
|
+
|
|
582
|
+
```python
|
|
583
|
+
from jwf_common_python.video import merge_videos, merge_directory, probe_video
|
|
584
|
+
|
|
585
|
+
# 输入顺序就是合并顺序。输出父目录必须已存在,返回输出的绝对 Path。
|
|
586
|
+
output = merge_videos(
|
|
587
|
+
["clips/part1.mp4", "clips/part2.mp4"],
|
|
588
|
+
"results/merged.mp4",
|
|
589
|
+
timeout=300,
|
|
590
|
+
)
|
|
591
|
+
metadata = probe_video(output) # 完整 ffprobe JSON,含 streams 和 format
|
|
592
|
+
|
|
593
|
+
# 也可合并目录首层 MP4,按自然文件名排序:clip2 在 clip10 之前。
|
|
594
|
+
# output = merge_directory("clips", "results/all.mp4", overwrite=True)
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
- `merge_videos` 保留指定顺序;重复输入表示重复该片段。
|
|
598
|
+
`merge_directory` 默认选择 `.mp4`(大小写均支持),自动排除已有目标文件;
|
|
599
|
+
可通过 `extensions=(".mp4", ".mkv")` 选择其他封装格式。
|
|
600
|
+
- 使用[FFmpeg concat demuxer](https://ffmpeg.org/ffmpeg-formats.html#concat)
|
|
601
|
+
和流复制,不再逐文件生成 TS。合并前检查流数量和顺序、编码、时间基、
|
|
602
|
+
分辨率、帧率、音频采样率及声道等主要参数;不兼容时抛出异常,不自动转码。
|
|
603
|
+
封装和时间戳仍由 FFmpeg 处理,不保证不同来源视频的无缝音画拼接。
|
|
604
|
+
- 通过参数数组执行,不经过 Shell;支持空格、中文和单引号文件名。
|
|
605
|
+
受 concat 清单格式限制,输入路径不能包含换行符。
|
|
606
|
+
- 输出格式由扩展名决定,必须支持输入流编码。默认拒绝覆盖,显式传
|
|
607
|
+
`overwrite=True` 才替换已有输出;目标不能是输入文件或其别名。
|
|
608
|
+
先在独立临时目录生成并探测结果,再提交输出,失败保留已有目标和源文件。
|
|
609
|
+
不支持硬链接的文件系统会退回独占复制;仅清理本次产生的文件。
|
|
610
|
+
- 不自动创建业务目录、不打印、不配置全局日志。缺少工具、参数不一致、
|
|
611
|
+
非零退出码、错误日志及超时均抛出异常。`CalledProcessError.stderr` 保留
|
|
612
|
+
FFmpeg 诊断;`timeout` 对每个子进程生效,合并默认不限制总处理时间。
|
|
613
|
+
- 旧 `VideoMerge.change()` 的目录流程由 `merge_directory` 替代;
|
|
614
|
+
TS 转换和全目录 TS 清理方法已移除。
|
|
615
|
+
|
|
616
|
+
测试(安装 FFmpeg 后会生成短视频,验证音轨、时长、帧顺序和失败清理):
|
|
617
|
+
|
|
618
|
+
```bash
|
|
619
|
+
python -m unittest discover -s tests/test_video -v
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
## GitHub Webhook
|
|
623
|
+
|
|
624
|
+
`jwf_common_python.github` 替代旧 `_github_hooks.py`,密钥通过参数传入,
|
|
625
|
+
不创建全局应用、不启动服务器、不打印签名或密钥、不配置全局日志。
|
|
626
|
+
验签使用标准库,不需要 Flask;可选 HTTP 接入才需要安装:
|
|
627
|
+
|
|
628
|
+
```bash
|
|
629
|
+
pip install 'Flask>=3,<4'
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
```python
|
|
633
|
+
import os
|
|
634
|
+
from jwf_common_python.github import create_webhook_app, verify_signature
|
|
635
|
+
|
|
636
|
+
secret = os.environ["GITHUB_WEBHOOK_SECRET"]
|
|
637
|
+
# 返回 Flask 应用对象,由调用方自行部署或启动。
|
|
638
|
+
app = create_webhook_app(secret, path="/hook")
|
|
639
|
+
# 也可在其他框架中调用 verify_signature(raw_body, secret, signature_header)。
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
- `verify_signature(payload, secret, signature_header)` 接收原始请求体 bytes、
|
|
643
|
+
str/bytes 密钥和 `X-Hub-Signature-256` 请求头,返回 bool。缺失、格式错误或
|
|
644
|
+
不匹配的签名返回 False;配置及参数类型错误抛出异常。不能先解析再序列化 JSON。
|
|
645
|
+
实现及测试参考[GitHub 官方验签文档](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries)。
|
|
646
|
+
- `create_webhook_app` 对 POST 请求先验签。失败返回 HTTP 403,通过后返回
|
|
647
|
+
`{"status": 200}`;GET 返回 405。可传 `handler(payload, event, delivery_id)`
|
|
648
|
+
接收原始请求体、事件名和投递 ID,只有回调成功才返回成功响应。
|
|
649
|
+
回调异常交由 Flask 处理,不被包装成成功;请求体解析由回调负责。
|
|
650
|
+
- event 和 delivery_id 来自请求头,签名仅保护请求体;事件筛选及投递去重由业务方处理。
|
|
651
|
+
只创建应用不会向 GitHub 发请求,也不会绑定默认端口。
|
|
652
|
+
|
|
653
|
+
离线测试(HTTP 部分使用 Flask 本地测试客户端):
|
|
654
|
+
|
|
655
|
+
```bash
|
|
656
|
+
python -m unittest discover -s tests/test_github -v
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
## 开发协作
|
|
660
|
+
打包
|
|
661
|
+
```bash
|
|
662
|
+
python -m build
|
|
663
|
+
```
|
|
664
|
+
单元测试:
|
|
665
|
+
```bash
|
|
666
|
+
pytest
|
|
667
|
+
pytest path/to/test_file.py::TestClassName::test_method_name
|
|
668
|
+
```
|
|
669
|
+
|