xtn-tools-pro 1.0.0.0.4__py3-none-any.whl → 1.0.0.0.6__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.
@@ -8,11 +8,11 @@
8
8
  # --------------------------------------------------------------------------------------------------
9
9
  # 2024/4/17 xiatn V00.01.000 新建
10
10
  # --------------------------------------------------------------------------------------------------
11
- from xtn_tools_pro.tools_time import *
12
11
  from urllib import parse
12
+ from typing import List, Dict, Optional
13
+ from xtn_tools_pro.utils.time_utils import *
13
14
  from pymongo import MongoClient as _MongoClient
14
15
  from pymongo.database import Database as _Database
15
- from typing import List, Dict, Optional
16
16
  from pymongo.collection import Collection as _Collection
17
17
  from pymongo.errors import DuplicateKeyError, BulkWriteError
18
18
 
@@ -0,0 +1,397 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # 说明:
5
+ # 程序说明xxxxxxxxxxxxxxxxxxx
6
+ # History:
7
+ # Date Author Version Modification
8
+ # --------------------------------------------------------------------------------------------------
9
+ # 2024/5/11 xiatn V00.01.000 新建
10
+ # --------------------------------------------------------------------------------------------------
11
+ import json
12
+ import pymysql
13
+ import datetime
14
+ from pymysql import err
15
+ from urllib import parse
16
+ from pymysql import cursors
17
+ from typing import List, Dict
18
+ from dbutils.pooled_db import PooledDB
19
+ from xtn_tools_pro.utils.log import Log
20
+ from xtn_tools_pro.utils.sql import get_insert_sql, get_insert_batch_sql, get_update_sql
21
+
22
+ log = Log(name="MysqlDB", color=True)
23
+
24
+
25
+ def auto_retry(func):
26
+ def wapper(*args, **kwargs):
27
+ for i in range(3):
28
+ try:
29
+ return func(*args, **kwargs)
30
+ except (err.InterfaceError, err.OperationalError) as e:
31
+ log.error(
32
+ """
33
+ error:%s
34
+ sql: %s
35
+ """
36
+ % (e, kwargs.get("sql") or args[1])
37
+ )
38
+
39
+ return wapper
40
+
41
+
42
+ class MysqlDBPro:
43
+ def __init__(self, ip, port, db, user_name, user_pass, **kwargs):
44
+ try:
45
+ self.connect_pool = PooledDB(
46
+ creator=pymysql, # 指定数据库连接的创建方法,这里使用的是pymysql作为创建方法
47
+ mincached=1, # 连接池中空闲连接的初始数量
48
+ maxcached=100, # 连接池中空闲连接的最大数量
49
+ maxconnections=100, # 连接池允许的最大连接数
50
+ blocking=True, # 当连接池达到最大连接数时,是否阻塞等待连接释放
51
+ ping=7, # 连接池中的连接在重新使用之前,是否需要进行ping操作来验证连接的有效性,这里的7是一个时间间隔,表示每隔7秒会对连接进行一次ping操作
52
+ host=ip, # 数据库主机的IP地址
53
+ port=port, # 数据库的端口号
54
+ user=user_name, # 连接数据库的用户名
55
+ passwd=user_pass, # 连接数据库的密码
56
+ db=db, # 连接的数据库名
57
+ charset="utf8mb4", # 连接数据库时使用的字符编码
58
+ cursorclass=cursors.SSCursor, # 指定使用的游标类,这里使用的是cursors.SSCursor,该游标类在多线程下大批量插入数据时可以减少内存的使用
59
+ ) # cursorclass 使用服务的游标,默认的在多线程下大批量插入数据会使内存递增
60
+
61
+ except Exception as e:
62
+ log.error(
63
+ """
64
+ 连接失败:
65
+ ip: {}
66
+ port: {}
67
+ db: {}
68
+ user_name: {}
69
+ user_pass: {}
70
+ exception: {}
71
+ """.format(
72
+ ip, port, db, user_name, user_pass, e
73
+ )
74
+ )
75
+ else:
76
+ log.debug("连接到mysql数据库 %s : %s" % (ip, db))
77
+
78
+ @classmethod
79
+ def from_url(cls, url, **kwargs):
80
+ """
81
+
82
+ Args:
83
+ url: mysql://username:password@ip:port/db?charset=utf8mb4
84
+ url: mysql://username:password@127.0.0.1:port/db?charset=utf8mb4
85
+ **kwargs:
86
+
87
+ Returns:
88
+
89
+ """
90
+ url_parsed = parse.urlparse(url)
91
+
92
+ db_type = url_parsed.scheme.strip()
93
+ if db_type != "mysql":
94
+ raise Exception(
95
+ "url error, expect mysql://username:ip:port/db?charset=utf8mb4, but get {}".format(
96
+ url
97
+ )
98
+ )
99
+
100
+ connect_params = {
101
+ "ip": url_parsed.hostname.strip(),
102
+ "port": url_parsed.port,
103
+ "user_name": url_parsed.username.strip(),
104
+ "user_pass": url_parsed.password.strip(),
105
+ "db": url_parsed.path.strip("/").strip(),
106
+ }
107
+
108
+ connect_params.update(kwargs)
109
+
110
+ return cls(**connect_params)
111
+
112
+ @staticmethod
113
+ def unescape_string(value):
114
+ if not isinstance(value, str):
115
+ return value
116
+ value = value.replace("\\0", "\0")
117
+ value = value.replace("\\\\", "\\")
118
+ value = value.replace("\\n", "\n")
119
+ value = value.replace("\\r", "\r")
120
+ value = value.replace("\\Z", "\032")
121
+ value = value.replace('\\"', '"')
122
+ value = value.replace("\\'", "'")
123
+ return value
124
+
125
+ def get_connection(self):
126
+ conn = self.connect_pool.connection(shareable=False)
127
+ # cursor = conn.cursor(cursors.SSCursor)
128
+ cursor = conn.cursor()
129
+
130
+ return conn, cursor
131
+
132
+ def close_connection(self, conn, cursor):
133
+ """
134
+ 关闭数据库连接和游标对象
135
+ :param conn:
136
+ :param cursor:
137
+ :return:
138
+ """
139
+ if conn:
140
+ conn.close()
141
+ if cursor:
142
+ cursor.close()
143
+
144
+ def execute(self, sql):
145
+ """
146
+ 执行sql
147
+ :param sql:
148
+ :return:
149
+ """
150
+ conn, cursor = None, None
151
+ try:
152
+ conn, cursor = self.get_connection()
153
+ cursor.execute(sql)
154
+ conn.commit()
155
+ except Exception as e:
156
+ log.error(
157
+ """
158
+ error:%s
159
+ sql: %s
160
+ """
161
+ % (e, sql)
162
+ )
163
+ return False
164
+ else:
165
+ return True
166
+ finally:
167
+ self.close_connection(conn, cursor)
168
+
169
+ def add(self, sql, exception_callfunc=None):
170
+ """
171
+ 单条 传入sql执行插入语句
172
+ :param sql: sql
173
+ :param exception_callfunc: 异常回调函数
174
+ :return: 添加行数
175
+ """
176
+ affect_count = None
177
+ conn, cursor = None, None
178
+
179
+ try:
180
+ conn, cursor = self.get_connection()
181
+ affect_count = cursor.execute(sql)
182
+ conn.commit()
183
+
184
+ except Exception as e:
185
+ log.error(
186
+ """
187
+ error:%s
188
+ sql: %s
189
+ """
190
+ % (e, sql)
191
+ )
192
+ if exception_callfunc:
193
+ exception_callfunc(e)
194
+ finally:
195
+ self.close_connection(conn, cursor)
196
+
197
+ return affect_count
198
+
199
+ def add_smart(self, table, data: Dict, **kwargs):
200
+ """
201
+ 单条 添加数据, 直接传递json格式的数据,不用拼sql
202
+ :param table: 表
203
+ :param data: 字典 {"xxx":"xxx"}
204
+ :param kwargs:
205
+ :return: 添加行数
206
+ """
207
+ sql = get_insert_sql(table, data, **kwargs)
208
+ return self.add(sql)
209
+
210
+ def add_batch(self, sql, datas: List[Dict]):
211
+ """
212
+ 批量 添加数据
213
+ 建议配合 get_insert_batch_sql() 生成sql
214
+ get_insert_batch_sql("user_copy1", [{"auth": 2, "id": "9", "email": "999"}]
215
+ :param sql:
216
+ insert ignore into `表` (字段1,字段2) values (%s, %s)
217
+ insert into `表` (`字段1`,`字段2`,`字段3`) values (%s, %s, %s)
218
+ 这里有多少个字段,values后面就要有多少个%s
219
+ :param datas: 列表 [{}, {}, {}]
220
+ :return:
221
+ """
222
+ affect_count = None
223
+ conn, cursor = None, None
224
+ try:
225
+ conn, cursor = self.get_connection()
226
+ affect_count = cursor.executemany(sql, datas)
227
+ conn.commit()
228
+
229
+ except Exception as e:
230
+ log.error(
231
+ """
232
+ error:%s
233
+ sql: %s
234
+ """
235
+ % (e, sql)
236
+ )
237
+ finally:
238
+ self.close_connection(conn, cursor)
239
+
240
+ return affect_count
241
+
242
+ def add_batch_smart(self, table, datas: List[Dict], **kwargs):
243
+ """
244
+ 批量 直接传递list格式的数据,不用拼sql
245
+ :param table: 表名
246
+ :param datas: 列表 [{}, {}, {}]
247
+ :param kwargs:
248
+ :return: 添加行数
249
+ """
250
+ sql, datas = get_insert_batch_sql(table, datas, **kwargs)
251
+ return self.add_batch(sql, datas)
252
+
253
+ def update(self, sql):
254
+ """
255
+ 更新
256
+ :param sql:
257
+ :return:
258
+ """
259
+ conn, cursor = None, None
260
+
261
+ try:
262
+ conn, cursor = self.get_connection()
263
+ cursor.execute(sql)
264
+ conn.commit()
265
+ except Exception as e:
266
+ log.error(
267
+ """
268
+ error:%s
269
+ sql: %s
270
+ """
271
+ % (e, sql)
272
+ )
273
+ return False
274
+ else:
275
+ return True
276
+ finally:
277
+ self.close_connection(conn, cursor)
278
+
279
+ def update_smart(self, table, data: Dict, condition):
280
+ """
281
+ 更新 无需拼sql
282
+ :param table: 表名
283
+ :param data: 数据 {"xxx":"xxx"}
284
+ :param condition: 更新条件 where后面的条件,如 condition='status=1'
285
+ :return:
286
+ """
287
+ sql = get_update_sql(table, data, condition)
288
+ return self.update(sql)
289
+
290
+ def delete(self, sql):
291
+ """
292
+ 删除
293
+ :param sql:
294
+ :return:
295
+ """
296
+ conn, cursor = None, None
297
+ try:
298
+ conn, cursor = self.get_connection()
299
+ cursor.execute(sql)
300
+ conn.commit()
301
+ except Exception as e:
302
+ log.error(
303
+ """
304
+ error:%s
305
+ sql: %s
306
+ """
307
+ % (e, sql)
308
+ )
309
+ return False
310
+ else:
311
+ return True
312
+ finally:
313
+ self.close_connection(conn, cursor)
314
+
315
+ @auto_retry
316
+ def find(self, sql, limit=0, to_json=False, conver_col=True):
317
+ """
318
+ 查询
319
+ 无数据: 返回None或[] 取决于limit
320
+ 有数据: 如果limit=1 则返回 一条数据(字段1, 字段2) 其余返回[(字段1, 字段2),(字段1, 字段2)]
321
+ :param sql:
322
+ :param limit:
323
+ :param to_json: 是否将查询结果转为json
324
+ :param conver_col: 是否处理查询结果,如date类型转字符串,json字符串转成json。仅当to_json=True时生效
325
+ :return:
326
+ """
327
+ conn, cursor = self.get_connection()
328
+
329
+ cursor.execute(sql)
330
+
331
+ if limit == 1:
332
+ result = cursor.fetchone() # 全部查出来,截取 不推荐使用
333
+ elif limit > 1:
334
+ result = cursor.fetchmany(limit) # 全部查出来,截取 不推荐使用
335
+ else:
336
+ result = cursor.fetchall()
337
+
338
+ if result is None:
339
+ return result
340
+
341
+ if to_json:
342
+ columns = [i[0] for i in cursor.description]
343
+
344
+ # 处理数据
345
+ def convert(col):
346
+ if isinstance(col, (datetime.date, datetime.time)):
347
+ return str(col)
348
+ elif isinstance(col, str) and (
349
+ col.startswith("{") or col.startswith("[")
350
+ ):
351
+ try:
352
+ # col = self.unescape_string(col)
353
+ return json.loads(col)
354
+ except:
355
+ return col
356
+ else:
357
+ # col = self.unescape_string(col)
358
+ return col
359
+
360
+ if limit == 1:
361
+ if conver_col:
362
+ result = [convert(col) for col in result]
363
+ result = dict(zip(columns, result))
364
+ else:
365
+ if conver_col:
366
+ result = [[convert(col) for col in row] for row in result]
367
+ result = [dict(zip(columns, r)) for r in result]
368
+
369
+ self.close_connection(conn, cursor)
370
+
371
+ return result
372
+
373
+
374
+ if __name__ == '__main__':
375
+ pass
376
+ # mysql_db = MysqlDBPro(ip="127.0.0.1", port=3306, db="xtn_home", user_name="root", user_pass="xtn-kk")
377
+ # sql = """insert into `user_copy1` (`id`, `email`, `auth`) values (8, '888', 2)"""
378
+ # print(mysql_db.add(sql))
379
+ # print(mysql_db.add_smart("user_copy1", {"id": "9", "email": "999"}))
380
+ # sql = "insert ignore into `user_copy1` (`id`,`email`) values (%s, %s)"
381
+ # sql, datas = get_insert_batch_sql("user_copy1", [{"auth": 2, "id": "9", "email": "999"}])
382
+ # print(mysql_db.add_batch(sql, datas))
383
+
384
+ # print(mysql_db.add_batch_smart("user_copy1", [{"auth": 2, "id": "9", "email": "999"},
385
+ # {"auth": 2, "id": "10", "email": "10"},
386
+ # {"id": "11", "auth": 1, "email": "11"},
387
+ # {"auth": 2, "id": "12", "email": "12"}]))
388
+
389
+ # 更新案例
390
+ # sql = "UPDATE user_copy1 SET status = '2', auth = 1 WHERE id = 2;"
391
+ # print(mysql_db.update(sql))
392
+
393
+ # 更新 无需拼接sql案例
394
+ # print(mysql_db.update_smart("user_copy1", {"email": "123", "status": 4}, "id=22"))
395
+
396
+ # 查询案例
397
+ # print(mysql_db.find("select * from user_copy1 where id=11",1,True))
@@ -10,7 +10,7 @@
10
10
  # --------------------------------------------------------------------------------------------------
11
11
  import requests, time, random
12
12
  from xtn_tools_pro.db.RedisDB import RedisDBPro
13
- from xtn_tools_pro.tools_time import get_time_now_timestamp, get_time_now_day59_timestamp
13
+ from xtn_tools_pro.utils.time_utils import get_time_now_timestamp, get_time_now_day59_timestamp
14
14
 
15
15
  import warnings
16
16
  from urllib3.exceptions import InsecureRequestWarning
xtn_tools_pro/tools.py CHANGED
@@ -8,125 +8,15 @@
8
8
  # --------------------------------------------------------------------------------------------------
9
9
  # 2024/4/17 xiatn V00.01.000 新建
10
10
  # --------------------------------------------------------------------------------------------------
11
- import hashlib, json, math
12
- from urllib.parse import urlencode
13
11
 
14
12
 
15
- def get_md5_32(s, is_upper=False):
13
+ def split_image(img):
16
14
  """
17
- 获取文本的md5值 32位
18
- :param s: 文本
19
- :param is_upper: 是否转大写 默认False
15
+ 切割图片
16
+ :param img:
20
17
  :return:
21
18
  """
22
- # s.encode()#变成bytes类型才能加密
23
- m = hashlib.md5(s.encode()) # 长度是32
24
- if is_upper:
25
- return m.hexdigest().upper()
26
- return m.hexdigest()
27
-
28
-
29
- def get_md5_16(s, is_upper=False):
30
- """
31
- 获取文本的md5值 16位
32
- :param s: 文本
33
- :param is_upper: 是否转大写 默认False
34
- :return:
35
- """
36
- result = get_md5_32(s, is_upper)
37
- return result[8:24]
38
-
39
-
40
- def get_binary_content_md5_32(content, is_upper=False):
41
- """
42
- 二进制内容md5 例如图片
43
- :param content: 二进制内容
44
- :param is_upper: 是否转大写 默认False
45
- :return:
46
- """
47
- md5_hash = hashlib.md5(content)
48
- md5_hexdigest = md5_hash.hexdigest()
49
- if is_upper:
50
- return md5_hexdigest.upper()
51
- return md5_hexdigest
52
-
53
-
54
- def get_binary_content_md5_16(content, is_upper=False):
55
- """
56
- 二进制内容md5 例如图片
57
- :param content: 二进制内容
58
- :param is_upper: 是否转大写 默认False
59
- :return:
60
- """
61
- result = get_binary_content_md5_32(content, is_upper)
62
- return result[8:24]
63
-
64
-
65
- def get_file_md5_32(file_path, is_upper=False):
66
- """
67
- 获取文件md5值
68
- :param file_path: 文件路径
69
- :param is_upper: 是否转大写 默认False
70
- :return:
71
- """
72
- with open(file_path, 'rb') as file:
73
- data = file.read()
74
- md5_hash = hashlib.md5(data).hexdigest()
75
- if is_upper:
76
- return md5_hash.upper()
77
- return md5_hash
78
-
79
-
80
- def get_file_md5_16(file_path, is_upper=False):
81
- """
82
- 获取文件md5值
83
- :param file_path: 文件路径
84
- :param is_upper: 是否转大写 默认False
85
- :return:
86
- """
87
- result = get_file_md5_32(file_path, is_upper)
88
- return result[8:24]
89
-
90
-
91
- def get_str_to_json(str_json):
92
- """
93
- 字符串类型的json格式 转 json
94
- :param str_json: 字符串json
95
- :return:
96
- """
97
- try:
98
- new_str_json = str_json.replace("'", '"'). \
99
- replace("None", "null").replace("True", "true"). \
100
- replace("False", "false")
101
- return json.loads(new_str_json)
102
- except Exception as e:
103
- return {}
104
-
105
-
106
- def get_build_url_with_params(url, params):
107
- """
108
- 传入url和params拼接完整的url ->效果 https://wwww.xxxx.com/?xxx1=1&xxx2=2
109
- :param url:
110
- :param params:
111
- :return:
112
- """
113
- encoded_params = urlencode(params)
114
- full_url = url + "?" + encoded_params
115
- return full_url
116
-
117
-
118
- def get_calculate_total_page(total, limit):
119
- """
120
- 根据total和limit计算出一共有多少页
121
- :param total:
122
- :param limit:
123
- :return:
124
- """
125
- if limit <= 0:
126
- return 0
127
- # 根据总条数和limit计算总页数
128
- total_pages = math.ceil(total / limit)
129
- return total_pages
19
+ pass
130
20
 
131
21
 
132
22
  if __name__ == '__main__':
@@ -38,4 +38,4 @@ def get_file_check_filename(file_name):
38
38
  if __name__ == '__main__':
39
39
  pass
40
40
  print(get_file_extension('file/2024-04-19/BOSCH GEX 125-1A/125-1AE砂磨机操作说明书:[1]_jingyan.txt'))
41
- print(get_check_filename('file/2024-04-19/BOSCH GEX 125-1A/125-1AE砂磨机操作说明书:[1]_jingyan.txt'))
41
+ print(get_file_check_filename('file/2024-04-19/BOSCH GEX 125-1A/125-1AE砂磨机操作说明书:[1]_jingyan.txt'))
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # 说明:
5
+ # 程序说明xxxxxxxxxxxxxxxxxxx
6
+ # History:
7
+ # Date Author Version Modification
8
+ # --------------------------------------------------------------------------------------------------
9
+ # 2024/5/12 xiatn V00.01.000 新建
10
+ # --------------------------------------------------------------------------------------------------
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # 说明:
5
+ # 加解密、编解码
6
+ # History:
7
+ # Date Author Version Modification
8
+ # --------------------------------------------------------------------------------------------------
9
+ # 2024/5/13 xiatn V00.01.000 新建
10
+ # --------------------------------------------------------------------------------------------------
11
+ import hashlib
12
+
13
+
14
+ def get_md5_32(s, is_upper=False):
15
+ """
16
+ 获取文本的md5值 32位
17
+ :param s: 文本
18
+ :param is_upper: 是否转大写 默认False
19
+ :return:
20
+ """
21
+ # s.encode()#变成bytes类型才能加密
22
+ m = hashlib.md5(s.encode()) # 长度是32
23
+ if is_upper:
24
+ return m.hexdigest().upper()
25
+ return m.hexdigest()
26
+
27
+
28
+ def get_md5_16(s, is_upper=False):
29
+ """
30
+ 获取文本的md5值 16位
31
+ :param s: 文本
32
+ :param is_upper: 是否转大写 默认False
33
+ :return:
34
+ """
35
+ result = get_md5_32(s, is_upper)
36
+ return result[8:24]
37
+
38
+
39
+ def get_binary_content_md5_32(content, is_upper=False):
40
+ """
41
+ 二进制内容md5 例如图片
42
+ :param content: 二进制内容
43
+ :param is_upper: 是否转大写 默认False
44
+ :return:
45
+ """
46
+ md5_hash = hashlib.md5(content)
47
+ md5_hexdigest = md5_hash.hexdigest()
48
+ if is_upper:
49
+ return md5_hexdigest.upper()
50
+ return md5_hexdigest
51
+
52
+
53
+ def get_binary_content_md5_16(content, is_upper=False):
54
+ """
55
+ 二进制内容md5 例如图片
56
+ :param content: 二进制内容
57
+ :param is_upper: 是否转大写 默认False
58
+ :return:
59
+ """
60
+ result = get_binary_content_md5_32(content, is_upper)
61
+ return result[8:24]
62
+
63
+
64
+ def get_file_md5_32(file_path, is_upper=False):
65
+ """
66
+ 获取文件md5值
67
+ :param file_path: 文件路径
68
+ :param is_upper: 是否转大写 默认False
69
+ :return:
70
+ """
71
+ with open(file_path, 'rb') as file:
72
+ data = file.read()
73
+ md5_hash = hashlib.md5(data).hexdigest()
74
+ if is_upper:
75
+ return md5_hash.upper()
76
+ return md5_hash
77
+
78
+
79
+ def get_file_md5_16(file_path, is_upper=False):
80
+ """
81
+ 获取文件md5值
82
+ :param file_path: 文件路径
83
+ :param is_upper: 是否转大写 默认False
84
+ :return:
85
+ """
86
+ result = get_file_md5_32(file_path, is_upper)
87
+ return result[8:24]
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # 说明:
5
+ # 文件
6
+ # History:
7
+ # Date Author Version Modification
8
+ # --------------------------------------------------------------------------------------------------
9
+ # 2024/5/13 xiatn V00.01.000 新建
10
+ # --------------------------------------------------------------------------------------------------
11
+ import os
12
+ import re
13
+
14
+
15
+ def get_file_extension(file_name):
16
+ """
17
+ 根据文件名获取文件扩展名/后缀名
18
+ :param file_name: 文件名称
19
+ :return:
20
+ """
21
+ _, file_extension = os.path.splitext(file_name)
22
+ return file_extension
23
+
24
+
25
+ def get_file_check_filename(file_name):
26
+ """
27
+ 传入文件名返回一个合法的文件名 会替换掉一些特殊符号 常用于爬虫写文件时文件名中带有特殊符号的情况...
28
+ :param filename: 文件名
29
+ :return:
30
+ """
31
+ file_extension = get_file_extension(file_name)
32
+ # 删除非法字符
33
+ sanitized_filename = re.sub(r'[\/:*?"<>|]', '', file_name)
34
+ max_length = 255 # 操作系统限制文件名的最大长度为255个
35
+ sanitized_filename = sanitized_filename[:max_length]
36
+ return sanitized_filename
37
+
38
+
39
+ if __name__ == '__main__':
40
+ pass
41
+ print(get_file_extension('file/2024-04-19/BOSCH GEX 125-1A/125-1AE砂磨机操作说明书:[1]_jingyan.txt'))
42
+ print(get_file_check_filename('file/2024-04-19/BOSCH GEX 125-1A/125-1AE砂磨机操作说明书:[1]_jingyan.txt'))