ezKit 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.
- ezKit/__init__.py +1 -0
- ezKit/bottle.py +3809 -0
- ezKit/cipher.py +74 -0
- ezKit/database.py +168 -0
- ezKit/files.py +348 -0
- ezKit/http.py +92 -0
- ezKit/mongo.py +62 -0
- ezKit/plots.py +155 -0
- ezKit/redis.py +51 -0
- ezKit/reports.py +274 -0
- ezKit/sendemail.py +146 -0
- ezKit/utils.py +1257 -0
- ezKit/weixin.py +148 -0
- ezKit/xftp.py +194 -0
- ezKit/zabbix.py +866 -0
- ezKit-1.0.0.dist-info/LICENSE +674 -0
- ezKit-1.0.0.dist-info/METADATA +10 -0
- ezKit-1.0.0.dist-info/RECORD +20 -0
- ezKit-1.0.0.dist-info/WHEEL +5 -0
- ezKit-1.0.0.dist-info/top_level.txt +1 -0
ezKit/utils.py
ADDED
@@ -0,0 +1,1257 @@
|
|
1
|
+
import datetime
|
2
|
+
import hashlib
|
3
|
+
import json
|
4
|
+
import os
|
5
|
+
import subprocess
|
6
|
+
import time
|
7
|
+
from copy import deepcopy
|
8
|
+
from multiprocessing import Pool
|
9
|
+
from multiprocessing.pool import ThreadPool
|
10
|
+
from pathlib import Path
|
11
|
+
from shutil import rmtree
|
12
|
+
from typing import Callable
|
13
|
+
from uuid import uuid4
|
14
|
+
|
15
|
+
from loguru import logger
|
16
|
+
|
17
|
+
# --------------------------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
|
20
|
+
# None Type
|
21
|
+
NoneType = type(None)
|
22
|
+
|
23
|
+
|
24
|
+
# --------------------------------------------------------------------------------------------------
|
25
|
+
|
26
|
+
|
27
|
+
def v_true(
|
28
|
+
v_instance: any = None,
|
29
|
+
v_type: any = None,
|
30
|
+
true_list: list | tuple | set | str = None,
|
31
|
+
false_list: list | tuple | set | str = None,
|
32
|
+
debug: bool = False
|
33
|
+
) -> bool:
|
34
|
+
"""
|
35
|
+
检查变量类型以及变量是否为真
|
36
|
+
"""
|
37
|
+
"""
|
38
|
+
常见类型:
|
39
|
+
|
40
|
+
Boolean bool False
|
41
|
+
Numbers int/float 0/0.0
|
42
|
+
String str ""
|
43
|
+
List list/tuple/set []/()/{}
|
44
|
+
Dictionary dict {}
|
45
|
+
|
46
|
+
函数使用 callable(func) 判断
|
47
|
+
"""
|
48
|
+
try:
|
49
|
+
if isinstance(v_instance, v_type):
|
50
|
+
if true_list is not None and false_list is None and (
|
51
|
+
isinstance(true_list, list) or
|
52
|
+
isinstance(true_list, tuple) or
|
53
|
+
isinstance(true_list, set) or
|
54
|
+
isinstance(true_list, str)
|
55
|
+
):
|
56
|
+
return True if v_instance in true_list else False
|
57
|
+
elif true_list is None and false_list is not None and (
|
58
|
+
isinstance(false_list, list) or
|
59
|
+
isinstance(false_list, tuple) or
|
60
|
+
isinstance(false_list, set) or
|
61
|
+
isinstance(false_list, str)
|
62
|
+
):
|
63
|
+
return True if v_instance not in false_list else False
|
64
|
+
elif true_list is not None and false_list is not None and (
|
65
|
+
isinstance(true_list, list) or
|
66
|
+
isinstance(true_list, tuple) or
|
67
|
+
isinstance(true_list, set) or
|
68
|
+
isinstance(true_list, str)
|
69
|
+
) and (
|
70
|
+
isinstance(false_list, list) or
|
71
|
+
isinstance(false_list, tuple) or
|
72
|
+
isinstance(false_list, set) or
|
73
|
+
isinstance(false_list, str)
|
74
|
+
):
|
75
|
+
return True if (v_instance in true_list) and (v_instance not in false_list) else False
|
76
|
+
else:
|
77
|
+
return True if v_instance not in [False, None, 0, 0.0, '', (), [], {*()}, {*[]}, {*{}}, {}] else False
|
78
|
+
else:
|
79
|
+
return False
|
80
|
+
except Exception as e:
|
81
|
+
logger.exception(e) if debug is True else next
|
82
|
+
return False
|
83
|
+
|
84
|
+
|
85
|
+
# --------------------------------------------------------------------------------------------------
|
86
|
+
|
87
|
+
|
88
|
+
def mam_of_numbers(
|
89
|
+
numbers: list | tuple = None,
|
90
|
+
dest_type: str = None,
|
91
|
+
debug: bool = False
|
92
|
+
) -> tuple[int | float, int | float, int | float] | tuple[None, None, None]:
|
93
|
+
"""
|
94
|
+
返回一组数字中的 最大值(maximum), 平均值(average), 最小值(minimum)
|
95
|
+
numbers 数字列表 (仅支持 list 和 tuple, 不支 set)
|
96
|
+
dest_type 目标类型 (将数字列表中的数字转换成统一的类型)
|
97
|
+
"""
|
98
|
+
try:
|
99
|
+
_numbers = deepcopy(numbers)
|
100
|
+
match True:
|
101
|
+
case True if dest_type == 'float':
|
102
|
+
_numbers = [float(i) for i in numbers]
|
103
|
+
case True if dest_type == 'int':
|
104
|
+
_numbers = [int(i) for i in numbers]
|
105
|
+
_num_max = max(_numbers)
|
106
|
+
_num_avg = sum(_numbers) / len(_numbers)
|
107
|
+
_num_min = min(_numbers)
|
108
|
+
return _num_max, _num_avg, _num_min
|
109
|
+
except Exception as e:
|
110
|
+
logger.exception(e) if debug is True else next
|
111
|
+
return None, None, None
|
112
|
+
|
113
|
+
|
114
|
+
def step_number_for_split_equally(
|
115
|
+
integer: int = None,
|
116
|
+
split_equally_number: int = None,
|
117
|
+
debug: bool = False
|
118
|
+
) -> int | None:
|
119
|
+
"""
|
120
|
+
平分数字的步长
|
121
|
+
integer 数字
|
122
|
+
split_equally_number 平分 integer 的数字
|
123
|
+
"""
|
124
|
+
"""
|
125
|
+
示例:
|
126
|
+
|
127
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
128
|
+
|
129
|
+
分成 2 份 -> [[1, 2, 3, 4, 5], [6, 7, 8, 9]] -> 返回 5
|
130
|
+
分成 3 份 -> [[1, 2, 3], [4, 5, 6], [7, 8, 9]] -> 返回 3
|
131
|
+
分成 4 份 -> [[1, 2, 3], [4, 5], [6, 7], [8, 9]] -> 返回 3
|
132
|
+
分成 5 份 -> [[1, 2], [3, 4], [5, 6], [7, 8], [9]] -> 返回 2
|
133
|
+
"""
|
134
|
+
try:
|
135
|
+
if integer % split_equally_number == 0:
|
136
|
+
return int(integer / split_equally_number)
|
137
|
+
else:
|
138
|
+
return int(integer / split_equally_number) + 1
|
139
|
+
except Exception as e:
|
140
|
+
logger.exception(e) if debug is True else next
|
141
|
+
return None
|
142
|
+
|
143
|
+
|
144
|
+
def division(
|
145
|
+
dividend: int | float = None,
|
146
|
+
divisor: int | float = None,
|
147
|
+
debug: bool = False
|
148
|
+
) -> float | None:
|
149
|
+
"""
|
150
|
+
除法
|
151
|
+
"""
|
152
|
+
try:
|
153
|
+
return dividend / divisor
|
154
|
+
except Exception as e:
|
155
|
+
logger.exception(e) if debug is True else next
|
156
|
+
return None
|
157
|
+
|
158
|
+
|
159
|
+
def divisor_1000(
|
160
|
+
dividend: int | float = None,
|
161
|
+
debug: bool = False
|
162
|
+
) -> float | None:
|
163
|
+
"""
|
164
|
+
除法, 除以 1000
|
165
|
+
"""
|
166
|
+
try:
|
167
|
+
return dividend / 1000
|
168
|
+
except Exception as e:
|
169
|
+
logger.exception(e) if debug is True else next
|
170
|
+
return None
|
171
|
+
|
172
|
+
|
173
|
+
def divisor_1024(
|
174
|
+
dividend: int | float = None,
|
175
|
+
debug: bool = False
|
176
|
+
) -> float | None:
|
177
|
+
"""
|
178
|
+
除法, 除以 1024
|
179
|
+
"""
|
180
|
+
try:
|
181
|
+
return dividend / 1024
|
182
|
+
except Exception as e:
|
183
|
+
logger.exception(e) if debug is True else next
|
184
|
+
return None
|
185
|
+
|
186
|
+
|
187
|
+
def divisor_square_1000(
|
188
|
+
dividend: int | float = None,
|
189
|
+
debug: bool = False
|
190
|
+
) -> float | None:
|
191
|
+
"""
|
192
|
+
除法, 除以 1000的次方
|
193
|
+
"""
|
194
|
+
try:
|
195
|
+
return dividend / (1000 * 1000)
|
196
|
+
except Exception as e:
|
197
|
+
logger.exception(e) if debug is True else next
|
198
|
+
return None
|
199
|
+
|
200
|
+
|
201
|
+
def divisor_square_1024(
|
202
|
+
dividend: int | float = None,
|
203
|
+
debug: bool = False
|
204
|
+
) -> float | None:
|
205
|
+
"""
|
206
|
+
除法, 除以 1024的次方
|
207
|
+
"""
|
208
|
+
try:
|
209
|
+
return dividend / (1024 * 1024)
|
210
|
+
except Exception as e:
|
211
|
+
logger.exception(e) if debug is True else next
|
212
|
+
return None
|
213
|
+
|
214
|
+
|
215
|
+
# --------------------------------------------------------------------------------------------------
|
216
|
+
|
217
|
+
|
218
|
+
def check_file_type(
|
219
|
+
file_object: str = None,
|
220
|
+
file_type: any = None,
|
221
|
+
debug: bool = False
|
222
|
+
) -> bool | None:
|
223
|
+
"""
|
224
|
+
检查文件类型
|
225
|
+
file_object 文件对象
|
226
|
+
file_type 文件类型
|
227
|
+
"""
|
228
|
+
try:
|
229
|
+
_file_path = Path(file_object)
|
230
|
+
match True:
|
231
|
+
case True if _file_path.exists() is False:
|
232
|
+
return False
|
233
|
+
case True if file_type == 'absolute' and _file_path.is_absolute() is True:
|
234
|
+
return True
|
235
|
+
case True if file_type == 'block_device' and _file_path.is_block_device() is True:
|
236
|
+
return True
|
237
|
+
case True if file_type == 'dir' and _file_path.is_dir() is True:
|
238
|
+
return True
|
239
|
+
case True if file_type == 'fifo' and _file_path.is_fifo() is True:
|
240
|
+
return True
|
241
|
+
case True if file_type == 'file' and _file_path.is_file() is True:
|
242
|
+
return True
|
243
|
+
case True if file_type == 'mount' and _file_path.is_mount() is True:
|
244
|
+
return True
|
245
|
+
case True if file_type == 'relative_to' and _file_path.is_relative_to() is True:
|
246
|
+
return True
|
247
|
+
case True if file_type == 'reserved' and _file_path.is_reserved() is True:
|
248
|
+
return True
|
249
|
+
case True if file_type == 'socket' and _file_path.is_socket() is True:
|
250
|
+
return True
|
251
|
+
case True if file_type == 'symlink' and _file_path.is_symlink() is True:
|
252
|
+
return True
|
253
|
+
case _:
|
254
|
+
return False
|
255
|
+
except Exception as e:
|
256
|
+
logger.exception(e) if debug is True else next
|
257
|
+
return False
|
258
|
+
|
259
|
+
|
260
|
+
# --------------------------------------------------------------------------------------------------
|
261
|
+
|
262
|
+
|
263
|
+
def list_sort(
|
264
|
+
data: list = None,
|
265
|
+
deduplication: bool = None,
|
266
|
+
debug: bool = False,
|
267
|
+
**kwargs
|
268
|
+
) -> list | None:
|
269
|
+
"""
|
270
|
+
列表排序, 示例: list_sort(['1.2.3.4', '2.3.4.5'], key=inet_aton)
|
271
|
+
"""
|
272
|
+
"""
|
273
|
+
参考文档:
|
274
|
+
https://stackoverflow.com/a/4183538
|
275
|
+
https://blog.csdn.net/u013541325/article/details/117530957
|
276
|
+
"""
|
277
|
+
try:
|
278
|
+
|
279
|
+
# from ipaddress import ip_address
|
280
|
+
# _ips = [str(i) for i in sorted(ip_address(ip.strip()) for ip in ips)]
|
281
|
+
# 注意: list.sort() 是直接改变 list, 不会返回 list
|
282
|
+
|
283
|
+
# 拷贝数据, 去重, 排序, 返回
|
284
|
+
_data = deepcopy(data)
|
285
|
+
if deduplication is True:
|
286
|
+
_data = list(set(_data))
|
287
|
+
_data.sort(**kwargs)
|
288
|
+
return _data
|
289
|
+
|
290
|
+
except Exception as e:
|
291
|
+
logger.exception(e) if debug is True else next
|
292
|
+
return None
|
293
|
+
|
294
|
+
|
295
|
+
def list_dict_sorted_by_key(
|
296
|
+
data: list | tuple = None,
|
297
|
+
key: str = None,
|
298
|
+
debug: bool = False,
|
299
|
+
**kwargs
|
300
|
+
) -> list | None:
|
301
|
+
"""
|
302
|
+
列表字典排序
|
303
|
+
"""
|
304
|
+
"""
|
305
|
+
参考文档:
|
306
|
+
https://stackoverflow.com/a/73050
|
307
|
+
"""
|
308
|
+
try:
|
309
|
+
_data = deepcopy(data)
|
310
|
+
return sorted(_data, key=lambda x: x[key], **kwargs)
|
311
|
+
except Exception as e:
|
312
|
+
logger.exception(e) if debug is True else next
|
313
|
+
return None
|
314
|
+
|
315
|
+
|
316
|
+
def list_split(
|
317
|
+
data: list = None,
|
318
|
+
number: int = None,
|
319
|
+
equally: bool = False,
|
320
|
+
debug: bool = False
|
321
|
+
) -> list | None:
|
322
|
+
"""
|
323
|
+
列表分割
|
324
|
+
"""
|
325
|
+
"""
|
326
|
+
默认: 将 list 以 number个元素为一个list 分割
|
327
|
+
|
328
|
+
data = [1, 2, 3, 4, 5, 6, 7]
|
329
|
+
|
330
|
+
list_split(data, 2) -> 将 data 以 2个元素为一个 list 分割
|
331
|
+
[[1, 2], [3, 4], [5, 6], [7]]
|
332
|
+
|
333
|
+
list_split(data, 3) -> 将 data 以 3个元素为一个 list 分割
|
334
|
+
[[1, 2, 3], [4, 5, 6], [7]]
|
335
|
+
|
336
|
+
equally 为 True 时, 将 data 平均分成 number 份
|
337
|
+
|
338
|
+
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
|
339
|
+
|
340
|
+
list_split_equally(data, 5) -> 将 data 平均分成 5 份
|
341
|
+
[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16], [17, 18, 19]]
|
342
|
+
|
343
|
+
list_split_equally(data, 6) -> 将 data 平均分成 6 份
|
344
|
+
[[1, 2, 3, 4], [5, 6, 7], [8, 9, 10], [11, 12, 13], [14, 15, 16], [17, 18, 19]]
|
345
|
+
|
346
|
+
list_split_equally(data, 7) -> 将 data 平均分成 7 份
|
347
|
+
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15], [16, 17], [18, 19]]
|
348
|
+
"""
|
349
|
+
try:
|
350
|
+
|
351
|
+
# 数据拷贝
|
352
|
+
_data_object = deepcopy(data)
|
353
|
+
# 数据长度
|
354
|
+
_data_length = len(_data_object)
|
355
|
+
# 数据平分后的结果
|
356
|
+
_data_result = []
|
357
|
+
|
358
|
+
if debug is True:
|
359
|
+
logger.info(f"data object: {_data_object}")
|
360
|
+
logger.info(f"data length: {_data_length}")
|
361
|
+
|
362
|
+
if _data_length < number:
|
363
|
+
logger.error('number must greater than data length') if debug is True else next
|
364
|
+
return None
|
365
|
+
elif _data_length == number:
|
366
|
+
_data_result = [[i] for i in _data_object]
|
367
|
+
else:
|
368
|
+
|
369
|
+
if equally is True:
|
370
|
+
|
371
|
+
# 数据平分时, 每份数据的最大长度
|
372
|
+
_step_number = step_number_for_split_equally(_data_length, number, debug=debug)
|
373
|
+
logger.info(f"step number: {_step_number}") if debug is True else next
|
374
|
+
if _data_length % number == 0:
|
375
|
+
index_number_list = list(range(0, _data_length, number))
|
376
|
+
logger.info(f"index number list: {index_number_list}") if debug is True else next
|
377
|
+
for index_number in index_number_list:
|
378
|
+
logger.info(f"index: {index_number}, data: {_data_object[index_number:index_number + number]}") if debug is True else next
|
379
|
+
_data_result.append(deepcopy(_data_object[index_number:index_number + number]))
|
380
|
+
else:
|
381
|
+
# 前一部分
|
382
|
+
previous_end_number = (_data_length % number) * _step_number
|
383
|
+
previous_index_number_list = list(range(0, previous_end_number, _step_number))
|
384
|
+
for index_number in previous_index_number_list:
|
385
|
+
_data_result.append(deepcopy(_data_object[index_number:index_number + _step_number]))
|
386
|
+
# 后一部分
|
387
|
+
next_number_list = list(range(previous_end_number, _data_length, _step_number - 1))
|
388
|
+
for index_number in next_number_list:
|
389
|
+
_data_result.append(deepcopy(_data_object[index_number:index_number + (_step_number - 1)]))
|
390
|
+
|
391
|
+
else:
|
392
|
+
|
393
|
+
for index_number in list(range(0, _data_length, number)):
|
394
|
+
_data_result.append(deepcopy(_data_object[index_number:index_number + number]))
|
395
|
+
|
396
|
+
return _data_result
|
397
|
+
|
398
|
+
except Exception as e:
|
399
|
+
logger.exception(e) if debug is True else next
|
400
|
+
return None
|
401
|
+
|
402
|
+
|
403
|
+
def list_print_by_step(
|
404
|
+
data: list = None,
|
405
|
+
number: int = None,
|
406
|
+
separator: str = None,
|
407
|
+
debug: bool = False
|
408
|
+
) -> list | None:
|
409
|
+
"""
|
410
|
+
列表按照 步长 和 分隔符 有规律的输出
|
411
|
+
"""
|
412
|
+
try:
|
413
|
+
_data_list = list_split(data, number, debug=debug)
|
414
|
+
for _item in _data_list:
|
415
|
+
print(*_item, sep=separator)
|
416
|
+
except Exception as e:
|
417
|
+
logger.exception(e) if debug is True else next
|
418
|
+
return None
|
419
|
+
|
420
|
+
|
421
|
+
def list_remove_list(
|
422
|
+
original: list = None,
|
423
|
+
remove: list = None,
|
424
|
+
debug: bool = False
|
425
|
+
) -> list | None:
|
426
|
+
try:
|
427
|
+
_original = deepcopy(original)
|
428
|
+
_remove = deepcopy(remove)
|
429
|
+
return [i for i in _original if i not in _remove]
|
430
|
+
except Exception as e:
|
431
|
+
logger.exception(e) if debug is True else next
|
432
|
+
return None
|
433
|
+
|
434
|
+
|
435
|
+
def list_merge(
|
436
|
+
data: list = None,
|
437
|
+
debug: bool = False
|
438
|
+
) -> list | None:
|
439
|
+
"""合并 List 中的 List 为一个 List"""
|
440
|
+
try:
|
441
|
+
_results = []
|
442
|
+
for i in deepcopy(data):
|
443
|
+
_results += i
|
444
|
+
return _results
|
445
|
+
except Exception as e:
|
446
|
+
logger.exception(e) if debug is True else next
|
447
|
+
return None
|
448
|
+
|
449
|
+
|
450
|
+
def list_to_file(
|
451
|
+
data: list = None,
|
452
|
+
file: str = None,
|
453
|
+
debug: bool = False
|
454
|
+
) -> bool:
|
455
|
+
try:
|
456
|
+
with open(file, 'w') as file:
|
457
|
+
for line in data:
|
458
|
+
file.write(f"{line}\n")
|
459
|
+
return True
|
460
|
+
except Exception as e:
|
461
|
+
logger.exception(e) if debug is True else next
|
462
|
+
return False
|
463
|
+
|
464
|
+
|
465
|
+
def range_zfill(
|
466
|
+
start: int = None,
|
467
|
+
stop: int = None,
|
468
|
+
step: int = None,
|
469
|
+
width: int = None,
|
470
|
+
debug: bool = False
|
471
|
+
) -> list | None:
|
472
|
+
"""生成长度相同的字符串的列表"""
|
473
|
+
# 示例: range_zfill(8, 13, 1, 2) => ['08', '09', '10', '11', '12']
|
474
|
+
# 生成 小时 列表: range_zfill(0, 24, 1, 2)
|
475
|
+
# 生成 分钟和秒 列表: range_zfill(0, 60, 1, 2)
|
476
|
+
# https://stackoverflow.com/a/733478
|
477
|
+
# the zfill() method to pad a string with zeros
|
478
|
+
try:
|
479
|
+
return [str(i).zfill(width) for i in range(start, stop, step)]
|
480
|
+
except Exception as e:
|
481
|
+
logger.exception(e) if debug is True else next
|
482
|
+
return None
|
483
|
+
|
484
|
+
|
485
|
+
# --------------------------------------------------------------------------------------------------
|
486
|
+
|
487
|
+
|
488
|
+
def dict_to_file(
|
489
|
+
data: dict = None,
|
490
|
+
file: str = None,
|
491
|
+
debug: bool = False,
|
492
|
+
**kwargs
|
493
|
+
) -> bool:
|
494
|
+
try:
|
495
|
+
with open(file, 'w') as fp:
|
496
|
+
json.dump(obj=data, fp=fp, indent=4, sort_keys=True, **kwargs)
|
497
|
+
return True
|
498
|
+
except Exception as e:
|
499
|
+
logger.exception(e) if debug is True else next
|
500
|
+
return False
|
501
|
+
|
502
|
+
|
503
|
+
def dict_nested_update(
|
504
|
+
data: dict = None,
|
505
|
+
key: str = None,
|
506
|
+
value: any = None,
|
507
|
+
debug: bool = False
|
508
|
+
) -> dict | None:
|
509
|
+
"""
|
510
|
+
dictionary nested update
|
511
|
+
https://stackoverflow.com/a/58885744
|
512
|
+
"""
|
513
|
+
try:
|
514
|
+
if v_true(data, dict, debug=debug):
|
515
|
+
for _k, _v in data.items():
|
516
|
+
# callable() 判断是非为 function
|
517
|
+
if (key is not None and key == _k) or (callable(key) is True and key() == _k):
|
518
|
+
if callable(value) is True:
|
519
|
+
data[_k] = value()
|
520
|
+
else:
|
521
|
+
data[_k] = value
|
522
|
+
elif isinstance(_v, dict) is True:
|
523
|
+
dict_nested_update(_v, key, value)
|
524
|
+
elif isinstance(_v, list) is True:
|
525
|
+
for _o in _v:
|
526
|
+
if isinstance(_o, dict):
|
527
|
+
dict_nested_update(_o, key, value)
|
528
|
+
else:
|
529
|
+
pass
|
530
|
+
else:
|
531
|
+
pass
|
532
|
+
except Exception as e:
|
533
|
+
logger.exception(e) if debug is True else next
|
534
|
+
return None
|
535
|
+
|
536
|
+
|
537
|
+
# --------------------------------------------------------------------------------------------------
|
538
|
+
|
539
|
+
|
540
|
+
def filename(
|
541
|
+
file: str = None,
|
542
|
+
split: str = '.',
|
543
|
+
debug: bool = False
|
544
|
+
) -> str | None:
|
545
|
+
"""获取文件名称"""
|
546
|
+
'''
|
547
|
+
https://stackoverflow.com/questions/678236/how-do-i-get-the-filename-without-the-extension-from-a-path-in-python
|
548
|
+
https://stackoverflow.com/questions/4152963/get-name-of-current-script-in-python
|
549
|
+
'''
|
550
|
+
try:
|
551
|
+
if debug is True:
|
552
|
+
logger.info(f"file: {file}")
|
553
|
+
logger.info(f"split: {split}")
|
554
|
+
_basename = str(os.path.basename(file))
|
555
|
+
logger.info(f"basename: {_basename}") if debug is True else next
|
556
|
+
_index_of_split = _basename.index(split)
|
557
|
+
logger.info(f"index of split: {_index_of_split}") if debug is True else next
|
558
|
+
logger.info(f"filename: {_basename[:_index_of_split]}") if debug is True else next
|
559
|
+
return _basename[:_index_of_split]
|
560
|
+
except Exception as e:
|
561
|
+
logger.exception(e) if debug is True else next
|
562
|
+
return None
|
563
|
+
|
564
|
+
|
565
|
+
def filehash(
|
566
|
+
file: str = None,
|
567
|
+
sha: str = 'md5',
|
568
|
+
debug: bool = False
|
569
|
+
) -> str | None:
|
570
|
+
"""获取文件Hash"""
|
571
|
+
"""
|
572
|
+
参考文档:
|
573
|
+
https://stackoverflow.com/a/59056837
|
574
|
+
https://stackoverflow.com/questions/22058048/hashing-a-file-in-python
|
575
|
+
"""
|
576
|
+
try:
|
577
|
+
with open(file, "rb") as _file:
|
578
|
+
match True:
|
579
|
+
case True if sha == 'sha1':
|
580
|
+
file_hash = hashlib.sha1()
|
581
|
+
case True if sha == 'sha224':
|
582
|
+
file_hash = hashlib.sha224()
|
583
|
+
case True if sha == 'sha256':
|
584
|
+
file_hash = hashlib.sha256()
|
585
|
+
case True if sha == 'sha384':
|
586
|
+
file_hash = hashlib.sha384()
|
587
|
+
case True if sha == 'sha512':
|
588
|
+
file_hash = hashlib.sha512()
|
589
|
+
case True if sha == 'sha3_224':
|
590
|
+
file_hash = hashlib.sha3_224()
|
591
|
+
case True if sha == 'sha3_256':
|
592
|
+
file_hash = hashlib.sha3_256()
|
593
|
+
case True if sha == 'sha3_384':
|
594
|
+
file_hash = hashlib.sha3_384()
|
595
|
+
case True if sha == 'sha3_512':
|
596
|
+
file_hash = hashlib.sha3_512()
|
597
|
+
case True if sha == 'shake_128':
|
598
|
+
file_hash = hashlib.shake_128()
|
599
|
+
case True if sha == 'shake_256':
|
600
|
+
file_hash = hashlib.shake_256()
|
601
|
+
case _:
|
602
|
+
file_hash = hashlib.md5()
|
603
|
+
# 建议设置为和 block size 相同的值, 多数系统默认为 4096, 可使用 stat 命令查看
|
604
|
+
# stat / (IO Block)
|
605
|
+
# stat -f / (Block size)
|
606
|
+
while chunk := _file.read(4096):
|
607
|
+
file_hash.update(chunk)
|
608
|
+
return file_hash.hexdigest()
|
609
|
+
except Exception as e:
|
610
|
+
logger.exception(e) if debug is True else next
|
611
|
+
return None
|
612
|
+
|
613
|
+
|
614
|
+
def filesize(
|
615
|
+
file: str = None,
|
616
|
+
debug: bool = False
|
617
|
+
) -> int | None:
|
618
|
+
"""获取文件大小"""
|
619
|
+
try:
|
620
|
+
return os.path.getsize(file)
|
621
|
+
except Exception as e:
|
622
|
+
logger.exception(e) if debug is True else next
|
623
|
+
return None
|
624
|
+
|
625
|
+
|
626
|
+
# --------------------------------------------------------------------------------------------------
|
627
|
+
|
628
|
+
|
629
|
+
def resolve_path() -> str | None:
|
630
|
+
"""获取当前目录名称"""
|
631
|
+
return str(Path().resolve())
|
632
|
+
|
633
|
+
|
634
|
+
def parent_path(
|
635
|
+
path: str = None,
|
636
|
+
debug: bool = False,
|
637
|
+
**kwargs
|
638
|
+
) -> str | None:
|
639
|
+
"""获取父目录名称"""
|
640
|
+
try:
|
641
|
+
return str(Path(path, **kwargs).parent.resolve()) if v_true(path, str, debug=debug) else None
|
642
|
+
except Exception as e:
|
643
|
+
logger.exception(e) if debug is True else next
|
644
|
+
return None
|
645
|
+
|
646
|
+
|
647
|
+
def real_path(
|
648
|
+
path: str = None,
|
649
|
+
debug: bool = False,
|
650
|
+
**kwargs
|
651
|
+
) -> str | None:
|
652
|
+
"""获取真实路径"""
|
653
|
+
try:
|
654
|
+
logger.info(f"path: {path}") if debug is True else next
|
655
|
+
return os.path.realpath(path, **kwargs)
|
656
|
+
except Exception as e:
|
657
|
+
logger.exception(e) if debug is True else next
|
658
|
+
return None
|
659
|
+
|
660
|
+
|
661
|
+
# --------------------------------------------------------------------------------------------------
|
662
|
+
|
663
|
+
|
664
|
+
def retry(
|
665
|
+
times: int = None,
|
666
|
+
func: Callable = None,
|
667
|
+
debug: bool = False,
|
668
|
+
**kwargs
|
669
|
+
) -> any:
|
670
|
+
"""重试"""
|
671
|
+
"""
|
672
|
+
函数传递参数: https://stackoverflow.com/a/803632
|
673
|
+
callable() 判断类型是非为函数: https://stackoverflow.com/a/624939
|
674
|
+
"""
|
675
|
+
try:
|
676
|
+
_num = 0
|
677
|
+
while True:
|
678
|
+
# 重试次数判断 (0 表示无限次数, 这里条件使用 > 0, 表示有限次数)
|
679
|
+
if times > 0:
|
680
|
+
_num += 1
|
681
|
+
if _num > times:
|
682
|
+
return
|
683
|
+
# 执行函数
|
684
|
+
try:
|
685
|
+
return func(**kwargs)
|
686
|
+
except Exception as e:
|
687
|
+
logger.exception(e) if debug is True else next
|
688
|
+
logger.success('retrying ...')
|
689
|
+
continue
|
690
|
+
# break
|
691
|
+
except Exception as e:
|
692
|
+
logger.exception(e) if debug is True else next
|
693
|
+
return None
|
694
|
+
|
695
|
+
|
696
|
+
# --------------------------------------------------------------------------------------------------
|
697
|
+
|
698
|
+
|
699
|
+
"""
|
700
|
+
日期时间有两种: UTC datetime (UTC时区日期时间) 和 Local datetime (当前时区日期时间)
|
701
|
+
|
702
|
+
Unix Timestamp 仅为 UTC datetime 的值
|
703
|
+
|
704
|
+
但是, Local datetime 可以直接转换为 Unix Timestamp, UTC datetime 需要先转换到 UTC TimeZone 再转换为 Unix Timestamp
|
705
|
+
|
706
|
+
相反, Unix Timestamp 可以直接转换为 UTC datetime, 要获得 Local datetime, 需要再将 UTC datetime 转换为 Local datetime
|
707
|
+
|
708
|
+
https://stackoverflow.com/a/13287083
|
709
|
+
https://stackoverflow.com/a/466376
|
710
|
+
https://stackoverflow.com/a/7999977
|
711
|
+
https://stackoverflow.com/a/3682808
|
712
|
+
https://stackoverflow.com/a/63920772
|
713
|
+
https://www.geeksforgeeks.org/how-to-remove-timezone-information-from-datetime-object-in-python/
|
714
|
+
|
715
|
+
pytz all timezones
|
716
|
+
|
717
|
+
https://stackoverflow.com/a/13867319
|
718
|
+
https://stackoverflow.com/a/15692958
|
719
|
+
|
720
|
+
import pytz
|
721
|
+
pytz.all_timezones
|
722
|
+
pytz.common_timezones
|
723
|
+
pytz.timezone('US/Eastern')
|
724
|
+
|
725
|
+
timezone
|
726
|
+
|
727
|
+
https://stackoverflow.com/a/39079819
|
728
|
+
https://stackoverflow.com/a/1681600
|
729
|
+
https://stackoverflow.com/a/4771733
|
730
|
+
https://stackoverflow.com/a/63920772
|
731
|
+
https://toutiao.io/posts/sin4x0/preview
|
732
|
+
|
733
|
+
其它:
|
734
|
+
|
735
|
+
dt.replace(tzinfo=timezone.utc).astimezone(tz=None)
|
736
|
+
|
737
|
+
(dt.replace(tzinfo=timezone.utc).astimezone(tz=None)).strftime(format)
|
738
|
+
datetime.fromisoformat((dt.replace(tzinfo=timezone.utc).astimezone(tz=None)).strftime(format))
|
739
|
+
string_to_datetime((dt.replace(tzinfo=timezone.utc).astimezone(tz=None)).strftime(format), format)
|
740
|
+
|
741
|
+
datetime.fromisoformat(time.strftime(format, time.gmtime(dt)))
|
742
|
+
"""
|
743
|
+
|
744
|
+
|
745
|
+
def date_to_datetime(
|
746
|
+
date_object: datetime.datetime = None,
|
747
|
+
debug: bool = False
|
748
|
+
) -> datetime.datetime | None:
|
749
|
+
"""'日期'转换为'日期时间'"""
|
750
|
+
# https://stackoverflow.com/a/1937636
|
751
|
+
try:
|
752
|
+
return datetime.datetime.combine(date_object, datetime.datetime.min.time())
|
753
|
+
except Exception as e:
|
754
|
+
logger.exception(e) if debug is True else next
|
755
|
+
return None
|
756
|
+
|
757
|
+
|
758
|
+
def datetime_to_date(
|
759
|
+
date_time: datetime.datetime = None,
|
760
|
+
debug: bool = False
|
761
|
+
) -> datetime.date | None:
|
762
|
+
"""'日期时间'转换为'日期'"""
|
763
|
+
# https://stackoverflow.com/a/3743240
|
764
|
+
try:
|
765
|
+
return date_time.date()
|
766
|
+
except Exception as e:
|
767
|
+
logger.exception(e) if debug is True else next
|
768
|
+
return None
|
769
|
+
|
770
|
+
|
771
|
+
def local_timezone():
|
772
|
+
"""获取当前时区"""
|
773
|
+
return datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo
|
774
|
+
|
775
|
+
|
776
|
+
def datetime_now(
|
777
|
+
debug: bool = False,
|
778
|
+
**kwargs
|
779
|
+
) -> datetime.datetime | None:
|
780
|
+
"""获取当前日期和时间"""
|
781
|
+
_utc = kwargs.pop("utc", False)
|
782
|
+
try:
|
783
|
+
return datetime.datetime.utcnow() if _utc is True else datetime.datetime.now(**kwargs)
|
784
|
+
except Exception as e:
|
785
|
+
logger.exception(e) if debug is True else next
|
786
|
+
return None
|
787
|
+
|
788
|
+
|
789
|
+
def datetime_offset(
|
790
|
+
date_time: datetime.datetime = None,
|
791
|
+
debug: bool = False,
|
792
|
+
**kwargs
|
793
|
+
) -> datetime.datetime | None:
|
794
|
+
"""获取'向前或向后特定日期时间'的日期和时间"""
|
795
|
+
_utc = kwargs.pop("utc", False)
|
796
|
+
try:
|
797
|
+
if isinstance(date_time, datetime.datetime):
|
798
|
+
return date_time + datetime.timedelta(**kwargs)
|
799
|
+
else:
|
800
|
+
return datetime.datetime.utcnow() + datetime.timedelta(**kwargs) if _utc is True else datetime.datetime.now() + datetime.timedelta(**kwargs)
|
801
|
+
except Exception as e:
|
802
|
+
logger.exception(e) if debug is True else next
|
803
|
+
return None
|
804
|
+
|
805
|
+
|
806
|
+
def datetime_to_string(
|
807
|
+
date_time: datetime.datetime = None,
|
808
|
+
string_format: str = '%Y-%m-%d %H:%M:%S',
|
809
|
+
debug: bool = False
|
810
|
+
) -> str | None:
|
811
|
+
"""'日期时间'转换为'字符串'"""
|
812
|
+
try:
|
813
|
+
return datetime.datetime.strftime(date_time, string_format) if isinstance(date_time, datetime.datetime) is True else None
|
814
|
+
except Exception as e:
|
815
|
+
logger.exception(e) if debug is True else next
|
816
|
+
return None
|
817
|
+
|
818
|
+
|
819
|
+
def datetime_to_timestamp(
|
820
|
+
date_time: datetime.datetime = None,
|
821
|
+
utc: bool = False,
|
822
|
+
debug: bool = False
|
823
|
+
) -> int | None:
|
824
|
+
"""
|
825
|
+
Datatime 转换为 Unix Timestamp
|
826
|
+
Local datetime 可以直接转换为 Unix Timestamp
|
827
|
+
UTC datetime 需要先替换 timezone 再转换为 Unix Timestamp
|
828
|
+
"""
|
829
|
+
try:
|
830
|
+
if isinstance(date_time, datetime.datetime):
|
831
|
+
return int(date_time.replace(tzinfo=datetime.timezone.utc).timestamp()) if utc is True else int(date_time.timestamp())
|
832
|
+
else:
|
833
|
+
return None
|
834
|
+
except Exception as e:
|
835
|
+
logger.exception(e) if debug is True else next
|
836
|
+
return None
|
837
|
+
|
838
|
+
|
839
|
+
def datetime_local_to_timezone(
|
840
|
+
date_time: datetime.datetime = None,
|
841
|
+
tz: datetime.timezone = datetime.timezone.utc,
|
842
|
+
debug: bool = False
|
843
|
+
) -> datetime.datetime | None:
|
844
|
+
"""
|
845
|
+
Local datetime to TimeZone datetime (默认转换为 UTC datetime)
|
846
|
+
replace(tzinfo=None) 移除结尾的时区信息
|
847
|
+
"""
|
848
|
+
try:
|
849
|
+
return (datetime.datetime.fromtimestamp(date_time.timestamp(), tz=tz)).replace(tzinfo=None) if isinstance(date_time, datetime.datetime) is True else None
|
850
|
+
except Exception as e:
|
851
|
+
logger.exception(e) if debug is True else next
|
852
|
+
return None
|
853
|
+
|
854
|
+
|
855
|
+
def datetime_utc_to_timezone(
|
856
|
+
date_time: datetime.datetime = None,
|
857
|
+
tz: datetime.timezone = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo,
|
858
|
+
debug: bool = False
|
859
|
+
) -> datetime.datetime | None:
|
860
|
+
"""
|
861
|
+
UTC datetime to TimeZone datetime (默认转换为 Local datetime)
|
862
|
+
replace(tzinfo=None) 移除结尾的时区信息
|
863
|
+
"""
|
864
|
+
try:
|
865
|
+
return date_time.replace(tzinfo=datetime.timezone.utc).astimezone(tz).replace(tzinfo=None) if isinstance(date_time, datetime.datetime) is True else None
|
866
|
+
except Exception as e:
|
867
|
+
logger.exception(e) if debug is True else next
|
868
|
+
return None
|
869
|
+
|
870
|
+
|
871
|
+
def timestamp_to_datetime(
|
872
|
+
timestamp: int = None,
|
873
|
+
tz: datetime.timezone = datetime.timezone.utc,
|
874
|
+
debug: bool = False
|
875
|
+
) -> datetime.datetime | None:
|
876
|
+
"""Unix Timestamp 转换为 Datatime"""
|
877
|
+
try:
|
878
|
+
return (datetime.datetime.fromtimestamp(timestamp, tz=tz)).replace(tzinfo=None) if v_true(timestamp, int, debug=debug) else None
|
879
|
+
except Exception as e:
|
880
|
+
logger.exception(e) if debug is True else next
|
881
|
+
return None
|
882
|
+
|
883
|
+
|
884
|
+
def datetime_string_to_datetime(
|
885
|
+
datetime_string: str = None,
|
886
|
+
datetime_format: str = '%Y-%m-%d %H:%M:%S',
|
887
|
+
debug: bool = False
|
888
|
+
) -> datetime.datetime | None:
|
889
|
+
try:
|
890
|
+
return datetime.datetime.strptime(datetime_string, datetime_format) if v_true(datetime_string, str, debug=debug) else None
|
891
|
+
except Exception as e:
|
892
|
+
logger.exception(e) if debug is True else next
|
893
|
+
return None
|
894
|
+
|
895
|
+
|
896
|
+
def datetime_string_to_timestamp(
|
897
|
+
datetime_string: str = None,
|
898
|
+
datetime_format: str = '%Y-%m-%d %H:%M:%S',
|
899
|
+
debug: bool = False
|
900
|
+
) -> int | None:
|
901
|
+
try:
|
902
|
+
return int(time.mktime(time.strptime(datetime_string, datetime_format))) if v_true(datetime_string, str, debug=debug) else None
|
903
|
+
except Exception as e:
|
904
|
+
logger.exception(e) if debug is True else next
|
905
|
+
return None
|
906
|
+
|
907
|
+
|
908
|
+
def datetime_object(
|
909
|
+
date_time: datetime.datetime = None,
|
910
|
+
debug: bool = False
|
911
|
+
) -> dict | None:
|
912
|
+
try:
|
913
|
+
return {
|
914
|
+
'date': date_time.strftime("%Y-%m-%d"),
|
915
|
+
'time': date_time.strftime("%H:%M:%S"),
|
916
|
+
'datetime_now': date_time.strftime("%Y-%m-%d %H:%M:%S"),
|
917
|
+
'datetime_zero': date_time.strftime('%Y-%m-%d 00:00:00')
|
918
|
+
}
|
919
|
+
except Exception as e:
|
920
|
+
logger.exception(e) if debug is True else next
|
921
|
+
return None
|
922
|
+
|
923
|
+
|
924
|
+
# --------------------------------------------------------------------------------------------------
|
925
|
+
|
926
|
+
|
927
|
+
'''
|
928
|
+
run_cmd = bash('echo ok', universal_newlines=True, stdout=PIPE)
|
929
|
+
|
930
|
+
if run_cmd != None:
|
931
|
+
returncode = run_cmd.returncode
|
932
|
+
outputs = run_cmd.stdout.splitlines()
|
933
|
+
print(returncode, type(returncode))
|
934
|
+
print(outputs, type(outputs))
|
935
|
+
|
936
|
+
# echo 'echo ok' > /tmp/ok.sh
|
937
|
+
run_script = bash('/tmp/ok.sh', file=True, universal_newlines=True, stdout=PIPE)
|
938
|
+
|
939
|
+
if run_script != None:
|
940
|
+
returncode = run_script.returncode
|
941
|
+
outputs = run_script.stdout.splitlines()
|
942
|
+
print(returncode, type(returncode))
|
943
|
+
print(outputs, type(outputs))
|
944
|
+
'''
|
945
|
+
|
946
|
+
|
947
|
+
def shell(
|
948
|
+
command: str = None,
|
949
|
+
isfile: bool = False,
|
950
|
+
sh_shell: str = '/bin/bash',
|
951
|
+
sh_option: str = None,
|
952
|
+
debug: bool = False,
|
953
|
+
**kwargs
|
954
|
+
) -> subprocess.CompletedProcess | None:
|
955
|
+
"""run shell command or script"""
|
956
|
+
try:
|
957
|
+
match True:
|
958
|
+
case True if not check_file_type(sh_shell, 'file', debug=debug):
|
959
|
+
return None
|
960
|
+
case True if v_true(sh_shell, str, debug=debug) and v_true(command, str, debug=debug):
|
961
|
+
if isfile is True:
|
962
|
+
if sh_option == None:
|
963
|
+
return subprocess.run([sh_shell, command], **kwargs)
|
964
|
+
else:
|
965
|
+
return subprocess.run([sh_shell, sh_option, command], **kwargs)
|
966
|
+
else:
|
967
|
+
if sh_option == None:
|
968
|
+
sh_option = '-c'
|
969
|
+
return subprocess.run([sh_shell, sh_option, command], **kwargs)
|
970
|
+
case _:
|
971
|
+
return None
|
972
|
+
except Exception as e:
|
973
|
+
logger.exception(e) if debug is True else next
|
974
|
+
return None
|
975
|
+
|
976
|
+
|
977
|
+
# --------------------------------------------------------------------------------------------------
|
978
|
+
|
979
|
+
|
980
|
+
def json_file_parser(
|
981
|
+
file: str = None,
|
982
|
+
debug: bool = False
|
983
|
+
) -> dict | None:
|
984
|
+
try:
|
985
|
+
if check_file_type(file, 'file', debug=debug):
|
986
|
+
with open(file) as json_raw:
|
987
|
+
json_dict = json.load(json_raw)
|
988
|
+
return json_dict
|
989
|
+
else:
|
990
|
+
logger.error(f"No such file: {file}")
|
991
|
+
return None
|
992
|
+
except Exception as e:
|
993
|
+
logger.exception(e) if debug is True else next
|
994
|
+
return None
|
995
|
+
|
996
|
+
|
997
|
+
"""
|
998
|
+
json_raw = '''
|
999
|
+
{
|
1000
|
+
"markdown.preview.fontSize": 14,
|
1001
|
+
"editor.minimap.enabled": false,
|
1002
|
+
"workbench.iconTheme": "vscode-icons",
|
1003
|
+
"http.proxy": "http://127.0.0.1:1087"
|
1004
|
+
|
1005
|
+
}
|
1006
|
+
'''
|
1007
|
+
|
1008
|
+
print(json_sort(json_raw))
|
1009
|
+
|
1010
|
+
{
|
1011
|
+
"editor.minimap.enabled": false,
|
1012
|
+
"http.proxy": "http://127.0.0.1:1087",
|
1013
|
+
"markdown.preview.fontSize": 14,
|
1014
|
+
"workbench.iconTheme": "vscode-icons"
|
1015
|
+
}
|
1016
|
+
"""
|
1017
|
+
|
1018
|
+
|
1019
|
+
def json_sort(
|
1020
|
+
string: str = None,
|
1021
|
+
debug: bool = False,
|
1022
|
+
**kwargs
|
1023
|
+
) -> dict | None:
|
1024
|
+
try:
|
1025
|
+
return json.dumps(json.loads(string), indent=4, sort_keys=True, **kwargs) if v_true(string, str, debug=debug) else None
|
1026
|
+
except Exception as e:
|
1027
|
+
logger.exception(e) if debug is True else next
|
1028
|
+
return None
|
1029
|
+
|
1030
|
+
|
1031
|
+
# --------------------------------------------------------------------------------------------------
|
1032
|
+
|
1033
|
+
|
1034
|
+
def delete_files(
|
1035
|
+
files: str | list = None,
|
1036
|
+
debug: bool = False
|
1037
|
+
) -> bool:
|
1038
|
+
"""删除文件"""
|
1039
|
+
try:
|
1040
|
+
|
1041
|
+
if v_true(files, str, debug=debug) and check_file_type(files, 'file', debug=debug):
|
1042
|
+
|
1043
|
+
os.remove(files)
|
1044
|
+
logger.success('deleted file: {}'.format(files))
|
1045
|
+
return True
|
1046
|
+
|
1047
|
+
elif v_true(files, list, debug=debug):
|
1048
|
+
|
1049
|
+
for _file in files:
|
1050
|
+
|
1051
|
+
if v_true(_file, str, debug=debug) and check_file_type(_file, 'file', debug=debug):
|
1052
|
+
try:
|
1053
|
+
os.remove(_file)
|
1054
|
+
logger.success('deleted file: {}'.format(_file))
|
1055
|
+
except Exception as e:
|
1056
|
+
logger.error('error file: {} {}'.format(_file, e))
|
1057
|
+
else:
|
1058
|
+
logger.error('error file: {}'.format(_file))
|
1059
|
+
|
1060
|
+
return True
|
1061
|
+
|
1062
|
+
else:
|
1063
|
+
|
1064
|
+
logger.error('error file: {}'.format(files))
|
1065
|
+
return False
|
1066
|
+
|
1067
|
+
except Exception as e:
|
1068
|
+
logger.exception(e) if debug is True else next
|
1069
|
+
return False
|
1070
|
+
|
1071
|
+
|
1072
|
+
def delete_directory(
|
1073
|
+
directory: str | list = None,
|
1074
|
+
debug: bool = False
|
1075
|
+
) -> bool:
|
1076
|
+
"""
|
1077
|
+
delete directory
|
1078
|
+
|
1079
|
+
https://docs.python.org/3/library/os.html#os.rmdir
|
1080
|
+
|
1081
|
+
os.rmdir(path, *, dir_fd=None)
|
1082
|
+
|
1083
|
+
Remove (delete) the directory path.
|
1084
|
+
|
1085
|
+
If the directory does not exist or is not empty, an FileNotFoundError or an OSError is raised respectively.
|
1086
|
+
|
1087
|
+
In order to remove whole directory trees, shutil.rmtree() can be used.
|
1088
|
+
|
1089
|
+
https://docs.python.org/3/library/shutil.html#shutil.rmtree
|
1090
|
+
|
1091
|
+
shutil.rmtree(path, ignore_errors=False, onerror=None)
|
1092
|
+
|
1093
|
+
Delete an entire directory tree; path must point to a directory (but not a symbolic link to a directory).
|
1094
|
+
|
1095
|
+
If ignore_errors is true, errors resulting from failed removals will be ignored;
|
1096
|
+
|
1097
|
+
if false or omitted, such errors are handled by calling a handler specified by onerror or, if that is omitted, they raise an exception.
|
1098
|
+
"""
|
1099
|
+
try:
|
1100
|
+
|
1101
|
+
if v_true(directory, str, debug=debug) and check_file_type(directory, 'dir', debug=debug):
|
1102
|
+
|
1103
|
+
rmtree(directory)
|
1104
|
+
logger.success('deleted directory: {}'.format(directory)) if debug is True else next
|
1105
|
+
return True
|
1106
|
+
|
1107
|
+
elif v_true(directory, list, debug=debug):
|
1108
|
+
|
1109
|
+
for _dir in directory:
|
1110
|
+
|
1111
|
+
if v_true(_dir, str, debug=debug) and check_file_type(_dir, 'dir', debug=debug):
|
1112
|
+
try:
|
1113
|
+
rmtree(_dir)
|
1114
|
+
logger.success('deleted directory: {}'.format(_dir)) if debug is True else next
|
1115
|
+
except Exception as e:
|
1116
|
+
logger.error('error directory: {} {}'.format(_dir, e)) if debug is True else next
|
1117
|
+
else:
|
1118
|
+
logger.error('error directory: {}'.format(_dir)) if debug is True else next
|
1119
|
+
|
1120
|
+
return True
|
1121
|
+
|
1122
|
+
else:
|
1123
|
+
|
1124
|
+
logger.error('error directory: {}'.format(directory)) if debug is True else next
|
1125
|
+
return False
|
1126
|
+
|
1127
|
+
except Exception as e:
|
1128
|
+
logger.exception(e) if debug is True else next
|
1129
|
+
return False
|
1130
|
+
|
1131
|
+
|
1132
|
+
# --------------------------------------------------------------------------------------------------
|
1133
|
+
|
1134
|
+
|
1135
|
+
def process_pool(
|
1136
|
+
process_func: Callable = None,
|
1137
|
+
process_data: any = None,
|
1138
|
+
process_num: int = 2,
|
1139
|
+
thread: bool = True,
|
1140
|
+
debug: bool = False,
|
1141
|
+
**kwargs
|
1142
|
+
) -> list | bool:
|
1143
|
+
"""
|
1144
|
+
多线程(MultiThread) | 多进程(MultiProcess)
|
1145
|
+
"""
|
1146
|
+
"""
|
1147
|
+
ThreadPool 线程池
|
1148
|
+
ThreadPool 共享内存, Pool 不共享内存
|
1149
|
+
ThreadPool 可以解决 Pool 在某些情况下产生的 Can't pickle local object 的错误
|
1150
|
+
https://stackoverflow.com/a/58897266
|
1151
|
+
"""
|
1152
|
+
try:
|
1153
|
+
|
1154
|
+
# 处理数据
|
1155
|
+
logger.info(f"data split ......") if debug is True else next
|
1156
|
+
if len(process_data) <= process_num:
|
1157
|
+
process_num = len(process_data)
|
1158
|
+
_data = process_data
|
1159
|
+
else:
|
1160
|
+
_data = list_split(process_data, process_num, equally=True, debug=debug)
|
1161
|
+
logger.info(f"data: {_data}") if debug is True else next
|
1162
|
+
|
1163
|
+
# 执行函数
|
1164
|
+
if thread is True:
|
1165
|
+
# 多线程
|
1166
|
+
logger.info(f"execute multi thread ......") if debug is True else next
|
1167
|
+
with ThreadPool(process_num, **kwargs) as p:
|
1168
|
+
return p.map(process_func, _data)
|
1169
|
+
else:
|
1170
|
+
# 多进程
|
1171
|
+
logger.info(f"execute multi process ......") if debug is True else next
|
1172
|
+
with Pool(process_num, **kwargs) as p:
|
1173
|
+
return p.map(process_func, _data)
|
1174
|
+
|
1175
|
+
except Exception as e:
|
1176
|
+
logger.exception(e) if debug is True else next
|
1177
|
+
return False
|
1178
|
+
|
1179
|
+
|
1180
|
+
# --------------------------------------------------------------------------------------------------
|
1181
|
+
|
1182
|
+
|
1183
|
+
def create_empty_file(
|
1184
|
+
file: str = None,
|
1185
|
+
debug: bool = False
|
1186
|
+
) -> str | None:
|
1187
|
+
try:
|
1188
|
+
if file is None:
|
1189
|
+
# 当前时间戳(纳秒)
|
1190
|
+
timestamp = time.time_ns()
|
1191
|
+
logger.info(f"timestamp: {timestamp}") if debug is True else next
|
1192
|
+
# 空文件路径
|
1193
|
+
file = f'/tmp/empty_file_{timestamp}.txt'
|
1194
|
+
# 创建一个空文件
|
1195
|
+
logger.info(f"file: {file}") if debug is True else next
|
1196
|
+
open(file, 'w').close()
|
1197
|
+
# 返回文件路径
|
1198
|
+
return file
|
1199
|
+
except Exception as e:
|
1200
|
+
logger.exception(e) if debug is True else next
|
1201
|
+
return None
|
1202
|
+
|
1203
|
+
|
1204
|
+
# --------------------------------------------------------------------------------------------------
|
1205
|
+
|
1206
|
+
|
1207
|
+
def uuid4_hex() -> str:
|
1208
|
+
return uuid4().hex
|
1209
|
+
|
1210
|
+
|
1211
|
+
def increment_version(
|
1212
|
+
version: str = None,
|
1213
|
+
debug: bool = False
|
1214
|
+
) -> str | None:
|
1215
|
+
"""版本号递增"""
|
1216
|
+
try:
|
1217
|
+
version_numbers = version.split('.')
|
1218
|
+
version_numbers[-1] = str(int(version_numbers[-1]) + 1)
|
1219
|
+
return '.'.join(version_numbers)
|
1220
|
+
except Exception as e:
|
1221
|
+
logger.exception(e) if debug is True else next
|
1222
|
+
return None
|
1223
|
+
|
1224
|
+
|
1225
|
+
# --------------------------------------------------------------------------------------------------
|
1226
|
+
|
1227
|
+
|
1228
|
+
def make_directory(
|
1229
|
+
directory: str = None,
|
1230
|
+
debug: bool = False
|
1231
|
+
) -> bool:
|
1232
|
+
"""创建目录"""
|
1233
|
+
try:
|
1234
|
+
os.makedirs(directory)
|
1235
|
+
return True
|
1236
|
+
except Exception as e:
|
1237
|
+
logger.exception(e) if debug is True else next
|
1238
|
+
return False
|
1239
|
+
|
1240
|
+
def change_directory(
|
1241
|
+
directory: str = None,
|
1242
|
+
debug: bool = False
|
1243
|
+
) -> bool:
|
1244
|
+
"""改变目录"""
|
1245
|
+
try:
|
1246
|
+
directory = str(directory) if v_true(directory, str, debug=debug) else next
|
1247
|
+
logger.info(f"directory: {directory}") if debug is True else next
|
1248
|
+
if check_file_type(directory, 'dir', debug=debug):
|
1249
|
+
logger.info(f"change directory to {directory}") if debug is True else next
|
1250
|
+
os.chdir(directory)
|
1251
|
+
return True
|
1252
|
+
else:
|
1253
|
+
logger.error(f"no such directory: {directory}") if debug is True else next
|
1254
|
+
return False
|
1255
|
+
except Exception as e:
|
1256
|
+
logger.exception(e) if debug is True else next
|
1257
|
+
return False
|