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/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