hutool-python 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. hutool/__init__.py +174 -0
  2. hutool/cache/__init__.py +7 -0
  3. hutool/cache/cache_util.py +47 -0
  4. hutool/cache/fifo_cache.py +87 -0
  5. hutool/cache/lfu_cache.py +129 -0
  6. hutool/cache/lru_cache.py +93 -0
  7. hutool/cache/timed_cache.py +115 -0
  8. hutool/captcha/__init__.py +3 -0
  9. hutool/captcha/captcha_util.py +215 -0
  10. hutool/core/__init__.py +23 -0
  11. hutool/core/_base.py +61 -0
  12. hutool/core/bean.py +214 -0
  13. hutool/core/codec.py +111 -0
  14. hutool/core/coll.py +635 -0
  15. hutool/core/date.py +1024 -0
  16. hutool/core/exceptions.py +66 -0
  17. hutool/core/io/__init__.py +0 -0
  18. hutool/core/io/data_size_util.py +79 -0
  19. hutool/core/io/file_name_util.py +111 -0
  20. hutool/core/io/file_util.py +650 -0
  21. hutool/core/io/io_util.py +133 -0
  22. hutool/core/io/path_util.py +247 -0
  23. hutool/core/io/resource_util.py +137 -0
  24. hutool/core/map.py +933 -0
  25. hutool/core/math_util.py +105 -0
  26. hutool/core/net.py +288 -0
  27. hutool/core/text/__init__.py +0 -0
  28. hutool/core/text/csv_util.py +54 -0
  29. hutool/core/text/str_builder.py +224 -0
  30. hutool/core/text/unicode_util.py +58 -0
  31. hutool/core/tree.py +242 -0
  32. hutool/core/util/__init__.py +63 -0
  33. hutool/core/util/array_util.py +503 -0
  34. hutool/core/util/boolean_util.py +124 -0
  35. hutool/core/util/charset_util.py +60 -0
  36. hutool/core/util/class_util.py +136 -0
  37. hutool/core/util/coordinate_util.py +186 -0
  38. hutool/core/util/credit_code_util.py +110 -0
  39. hutool/core/util/desensitized_util.py +194 -0
  40. hutool/core/util/enum_util.py +94 -0
  41. hutool/core/util/escape_util.py +97 -0
  42. hutool/core/util/hash_util.py +243 -0
  43. hutool/core/util/hex_util.py +140 -0
  44. hutool/core/util/id_util.py +147 -0
  45. hutool/core/util/idcard_util.py +300 -0
  46. hutool/core/util/number_util.py +720 -0
  47. hutool/core/util/object_util.py +294 -0
  48. hutool/core/util/page_util.py +61 -0
  49. hutool/core/util/phone_util.py +140 -0
  50. hutool/core/util/random_util.py +112 -0
  51. hutool/core/util/re_util.py +231 -0
  52. hutool/core/util/reflect_util.py +135 -0
  53. hutool/core/util/runtime_util.py +89 -0
  54. hutool/core/util/str_util.py +2320 -0
  55. hutool/core/util/system_util.py +62 -0
  56. hutool/core/util/url_util.py +232 -0
  57. hutool/core/util/version_util.py +41 -0
  58. hutool/core/util/xml_util.py +158 -0
  59. hutool/core/util/zip_util.py +126 -0
  60. hutool/cron/__init__.py +4 -0
  61. hutool/cron/cron_pattern.py +123 -0
  62. hutool/cron/cron_util.py +115 -0
  63. hutool/crypto/__init__.py +5 -0
  64. hutool/crypto/digest_util.py +167 -0
  65. hutool/crypto/secure_util.py +311 -0
  66. hutool/crypto/sign_util.py +74 -0
  67. hutool/dfa/__init__.py +3 -0
  68. hutool/dfa/sensitive_util.py +114 -0
  69. hutool/extra/__init__.py +6 -0
  70. hutool/extra/emoji_util.py +90 -0
  71. hutool/extra/pinyin_util.py +44 -0
  72. hutool/extra/qr_code_util.py +58 -0
  73. hutool/extra/template_util.py +41 -0
  74. hutool/http/__init__.py +6 -0
  75. hutool/http/html_util.py +88 -0
  76. hutool/http/http_request.py +188 -0
  77. hutool/http/http_response.py +139 -0
  78. hutool/http/http_util.py +237 -0
  79. hutool/json_util.py +251 -0
  80. hutool/jwt_util.py +57 -0
  81. hutool/setting/__init__.py +5 -0
  82. hutool/setting/props_util.py +79 -0
  83. hutool/setting/setting_util.py +80 -0
  84. hutool/setting/yaml_util.py +45 -0
  85. hutool_python-1.0.0.dist-info/LICENSE +127 -0
  86. hutool_python-1.0.0.dist-info/METADATA +438 -0
  87. hutool_python-1.0.0.dist-info/RECORD +89 -0
  88. hutool_python-1.0.0.dist-info/WHEEL +5 -0
  89. hutool_python-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,123 @@
1
+ """Cron表达式解析与匹配"""
2
+
3
+ from datetime import datetime, timedelta
4
+ from typing import List, Optional, Set
5
+
6
+
7
+ class CronPattern:
8
+ """Cron表达式解析与匹配
9
+
10
+ 支持标准5位cron: 分 时 日 月 周
11
+
12
+ 特殊字符说明:
13
+
14
+ - ``*`` : 匹配任意值
15
+ - ``,`` : 列举多个值 (如 1,3,5)
16
+ - ``-`` : 范围 (如 1-5)
17
+ - ``/`` : 步长 (如 */5 表示每5个单位)
18
+ """
19
+
20
+ def __init__(self, pattern: str) -> None:
21
+ """初始化CronPattern
22
+
23
+ :param pattern: cron表达式字符串,格式为 "分 时 日 月 周"
24
+ :raises ValueError: 表达式格式不正确
25
+ """
26
+ self._pattern: str = pattern
27
+ self._fields: List[str] = pattern.strip().split()
28
+ if len(self._fields) != 5:
29
+ raise ValueError(f"无效的cron表达式 '{pattern}',必须包含5个字段: 分 时 日 月 周")
30
+
31
+ def match(self, dt: datetime) -> bool:
32
+ """判断给定时间是否匹配
33
+
34
+ :param dt: 待匹配的时间
35
+ :return: 是否匹配cron表达式
36
+ """
37
+ minute_set = self._parse_field(self._fields[0], 0, 59)
38
+ hour_set = self._parse_field(self._fields[1], 0, 23)
39
+ day_set = self._parse_field(self._fields[2], 1, 31)
40
+ month_set = self._parse_field(self._fields[3], 1, 12)
41
+ weekday_set = self._parse_field(self._fields[4], 0, 6)
42
+
43
+ return (
44
+ dt.minute in minute_set
45
+ and dt.hour in hour_set
46
+ and dt.day in day_set
47
+ and dt.month in month_set
48
+ and dt.weekday() % 7 in weekday_set
49
+ )
50
+
51
+ def next_match_time(self, after: Optional[datetime] = None) -> datetime:
52
+ """获取下一次匹配时间
53
+
54
+ :param after: 基准时间,默认为当前时间
55
+ :return: 下一次匹配的时间
56
+ :raises RuntimeError: 超过搜索上限仍未找到匹配时间
57
+ """
58
+ if after is None:
59
+ after = datetime.now()
60
+ # 从下一分钟开始搜索
61
+ current = after.replace(second=0, microsecond=0) + timedelta(minutes=1)
62
+ # 最多搜索2年内的匹配
63
+ max_search = 366 * 24 * 60
64
+ for _ in range(max_search):
65
+ if self.match(current):
66
+ return current
67
+ current += timedelta(minutes=1)
68
+ raise RuntimeError(f"在2年内未找到匹配 '{self._pattern}' 的时间")
69
+
70
+ @staticmethod
71
+ def _parse_field(field: str, min_val: int, max_val: int) -> Set[int]:
72
+ """解析单个cron字段
73
+
74
+ :param field: 字段字符串
75
+ :param min_val: 最小值
76
+ :param max_val: 最大值
77
+ :return: 匹配的值集合
78
+ :raises ValueError: 字段格式不正确
79
+ """
80
+ values: Set[int] = set()
81
+
82
+ parts = field.split(",")
83
+ for part in parts:
84
+ step: Optional[int] = None
85
+ if "/" in part:
86
+ range_part, step_str = part.split("/", 1)
87
+ step = int(step_str)
88
+ if step <= 0:
89
+ raise ValueError(f"步长必须大于0: {part}")
90
+ else:
91
+ range_part = part
92
+
93
+ if range_part == "*":
94
+ start = min_val
95
+ end = max_val
96
+ elif "-" in range_part:
97
+ start_str, end_str = range_part.split("-", 1)
98
+ start = int(start_str)
99
+ end = int(end_str)
100
+ else:
101
+ val = int(range_part)
102
+ if step is not None:
103
+ start = val
104
+ end = max_val
105
+ else:
106
+ if val < min_val or val > max_val:
107
+ raise ValueError(f"值 {val} 超出范围 [{min_val}, {max_val}]")
108
+ values.add(val)
109
+ continue
110
+
111
+ if step is None:
112
+ step = 1
113
+
114
+ for v in range(start, end + 1, step):
115
+ if v < min_val or v > max_val:
116
+ raise ValueError(f"值 {v} 超出范围 [{min_val}, {max_val}]")
117
+ values.add(v)
118
+
119
+ return values
120
+
121
+ def __str__(self) -> str:
122
+ """返回cron表达式字符串"""
123
+ return self._pattern
@@ -0,0 +1,115 @@
1
+ """定时任务工具类"""
2
+
3
+ import threading
4
+ import time
5
+ from datetime import datetime
6
+ from typing import Callable, List, Optional
7
+
8
+ from .cron_pattern import CronPattern
9
+
10
+
11
+ class _CronTask:
12
+ """内部Cron任务"""
13
+
14
+ def __init__(self, pattern: CronPattern, func: Callable) -> None:
15
+ self.pattern = pattern
16
+ self.func = func
17
+
18
+
19
+ class _FixedRateTask:
20
+ """内部固定频率任务"""
21
+
22
+ def __init__(self, func: Callable, period_seconds: int) -> None:
23
+ self.func = func
24
+ self.period_seconds = period_seconds
25
+ self.last_run: Optional[float] = None
26
+
27
+
28
+ class CronUtil:
29
+ """定时任务工具类"""
30
+
31
+ _tasks: List[_CronTask] = []
32
+ _fixed_tasks: List[_FixedRateTask] = []
33
+ _running: bool = False
34
+ _thread: Optional[threading.Thread] = None
35
+ _lock = threading.Lock()
36
+
37
+ @classmethod
38
+ def schedule(cls, cron_pattern: str, func: Callable) -> None:
39
+ """添加定时任务
40
+
41
+ :param cron_pattern: cron表达式字符串,格式为 "分 时 日 月 周"
42
+ :param func: 定时执行的函数
43
+ """
44
+ pattern = CronPattern(cron_pattern)
45
+ with cls._lock:
46
+ cls._tasks.append(_CronTask(pattern, func))
47
+
48
+ @classmethod
49
+ def schedule_at_fixed_rate(cls, func: Callable, period_seconds: int) -> None:
50
+ """添加固定频率任务
51
+
52
+ :param func: 定时执行的函数
53
+ :param period_seconds: 执行间隔(秒)
54
+ """
55
+ with cls._lock:
56
+ task = _FixedRateTask(func, period_seconds)
57
+ cls._fixed_tasks.append(task)
58
+
59
+ @classmethod
60
+ def start(cls) -> None:
61
+ """启动调度器
62
+
63
+ 调度器在后台线程中运行,每秒检查一次是否有需要执行的任务。
64
+ """
65
+ if cls._running:
66
+ return
67
+ cls._running = True
68
+ cls._thread = threading.Thread(target=cls._run_loop, daemon=True)
69
+ cls._thread.start()
70
+
71
+ @classmethod
72
+ def stop(cls) -> None:
73
+ """停止调度器"""
74
+ cls._running = False
75
+ if cls._thread is not None:
76
+ cls._thread.join(timeout=5)
77
+ cls._thread = None
78
+
79
+ @classmethod
80
+ def restart(cls) -> None:
81
+ """重启调度器"""
82
+ cls.stop()
83
+ cls.start()
84
+
85
+ @classmethod
86
+ def _run_loop(cls) -> None:
87
+ """调度器主循环"""
88
+ while cls._running:
89
+ now = datetime.now()
90
+ # 检查cron任务
91
+ with cls._lock:
92
+ tasks_snapshot = list(cls._tasks)
93
+ fixed_tasks_snapshot = list(cls._fixed_tasks)
94
+
95
+ for task in tasks_snapshot:
96
+ try:
97
+ if task.pattern.match(now):
98
+ task.func()
99
+ except Exception as e:
100
+ print(f"[CronUtil] 执行cron任务时发生异常: {e}")
101
+
102
+ # 检查固定频率任务
103
+ for task in fixed_tasks_snapshot:
104
+ try:
105
+ current_time = time.time()
106
+ if task.last_run is None or (current_time - task.last_run >= task.period_seconds):
107
+ task.func()
108
+ task.last_run = current_time
109
+ except Exception as e:
110
+ print(f"[CronUtil] 执行固定频率任务时发生异常: {e}")
111
+
112
+ # 休眠到下一个分钟的开始
113
+ now = datetime.now()
114
+ seconds_until_next_minute = 60 - now.second
115
+ time.sleep(max(1, seconds_until_next_minute))
@@ -0,0 +1,5 @@
1
+ from .digest_util import DigestUtil
2
+ from .secure_util import SecureUtil
3
+ from .sign_util import SignUtil
4
+
5
+ __all__ = ["DigestUtil", "SecureUtil", "SignUtil"]
@@ -0,0 +1,167 @@
1
+ import hashlib
2
+ import hmac
3
+ from typing import Union
4
+
5
+
6
+ class DigestUtil:
7
+ """摘要工具类
8
+
9
+ 提供常用的摘要(哈希)算法封装,包括:
10
+ - MD5 / SHA-1 / SHA-256 / SHA-384 / SHA-512
11
+ - HMAC-MD5 / HMAC-SHA1 / HMAC-SHA256
12
+ 所有方法同时支持 str 和 bytes 输入。
13
+ """
14
+
15
+ # ------------------------------------------------------------------ #
16
+ # 内部辅助
17
+ # ------------------------------------------------------------------ #
18
+
19
+ @staticmethod
20
+ def _to_bytes(data: Union[str, bytes]) -> bytes:
21
+ """将输入统一转为 bytes"""
22
+ if isinstance(data, str):
23
+ return data.encode("utf-8")
24
+ return data
25
+
26
+ @staticmethod
27
+ def _digest(data: Union[str, bytes], algorithm: str) -> bytes:
28
+ """通用摘要计算,返回原始 bytes"""
29
+ return hashlib.new(algorithm, DigestUtil._to_bytes(data)).digest()
30
+
31
+ @staticmethod
32
+ def _hex_digest(data: Union[str, bytes], algorithm: str) -> str:
33
+ """通用摘要计算,返回十六进制字符串"""
34
+ return hashlib.new(algorithm, DigestUtil._to_bytes(data)).hexdigest()
35
+
36
+ # ------------------------------------------------------------------ #
37
+ # MD5
38
+ # ------------------------------------------------------------------ #
39
+
40
+ @staticmethod
41
+ def md5(data: Union[str, bytes]) -> bytes:
42
+ """计算MD5摘要,返回原始bytes"""
43
+ return DigestUtil._digest(data, "md5")
44
+
45
+ @staticmethod
46
+ def md5_hex(data: Union[str, bytes]) -> str:
47
+ """计算MD5摘要,返回32位十六进制字符串"""
48
+ return DigestUtil._hex_digest(data, "md5")
49
+
50
+ @staticmethod
51
+ def md5_hex16(data: Union[str, bytes]) -> str:
52
+ """计算MD5摘要,返回16位十六进制字符串(取32位结果的中间16位)"""
53
+ return DigestUtil.md5_hex(data)[8:24]
54
+
55
+ # ------------------------------------------------------------------ #
56
+ # SHA-1
57
+ # ------------------------------------------------------------------ #
58
+
59
+ @staticmethod
60
+ def sha1(data: Union[str, bytes]) -> bytes:
61
+ """计算SHA-1摘要,返回原始bytes"""
62
+ return DigestUtil._digest(data, "sha1")
63
+
64
+ @staticmethod
65
+ def sha1_hex(data: Union[str, bytes]) -> str:
66
+ """计算SHA-1摘要,返回十六进制字符串"""
67
+ return DigestUtil._hex_digest(data, "sha1")
68
+
69
+ # ------------------------------------------------------------------ #
70
+ # SHA-256
71
+ # ------------------------------------------------------------------ #
72
+
73
+ @staticmethod
74
+ def sha256(data: Union[str, bytes]) -> bytes:
75
+ """计算SHA-256摘要,返回原始bytes"""
76
+ return DigestUtil._digest(data, "sha256")
77
+
78
+ @staticmethod
79
+ def sha256_hex(data: Union[str, bytes]) -> str:
80
+ """计算SHA-256摘要,返回十六进制字符串"""
81
+ return DigestUtil._hex_digest(data, "sha256")
82
+
83
+ # ------------------------------------------------------------------ #
84
+ # SHA-384
85
+ # ------------------------------------------------------------------ #
86
+
87
+ @staticmethod
88
+ def sha384(data: Union[str, bytes]) -> bytes:
89
+ """计算SHA-384摘要,返回原始bytes"""
90
+ return DigestUtil._digest(data, "sha384")
91
+
92
+ @staticmethod
93
+ def sha384_hex(data: Union[str, bytes]) -> str:
94
+ """计算SHA-384摘要,返回十六进制字符串"""
95
+ return DigestUtil._hex_digest(data, "sha384")
96
+
97
+ # ------------------------------------------------------------------ #
98
+ # SHA-512
99
+ # ------------------------------------------------------------------ #
100
+
101
+ @staticmethod
102
+ def sha512(data: Union[str, bytes]) -> bytes:
103
+ """计算SHA-512摘要,返回原始bytes"""
104
+ return DigestUtil._digest(data, "sha512")
105
+
106
+ @staticmethod
107
+ def sha512_hex(data: Union[str, bytes]) -> str:
108
+ """计算SHA-512摘要,返回十六进制字符串"""
109
+ return DigestUtil._hex_digest(data, "sha512")
110
+
111
+ # ------------------------------------------------------------------ #
112
+ # HMAC
113
+ # ------------------------------------------------------------------ #
114
+
115
+ @staticmethod
116
+ def _hmac_digest(data: Union[str, bytes], key: Union[str, bytes], algorithm: str) -> bytes:
117
+ """通用HMAC计算,返回原始bytes"""
118
+ return hmac.new(
119
+ DigestUtil._to_bytes(key),
120
+ DigestUtil._to_bytes(data),
121
+ algorithm,
122
+ ).digest()
123
+
124
+ @staticmethod
125
+ def _hmac_hex_digest(data: Union[str, bytes], key: Union[str, bytes], algorithm: str) -> str:
126
+ """通用HMAC计算,返回十六进制字符串"""
127
+ return hmac.new(
128
+ DigestUtil._to_bytes(key),
129
+ DigestUtil._to_bytes(data),
130
+ algorithm,
131
+ ).hexdigest()
132
+
133
+ # HMAC-MD5
134
+
135
+ @staticmethod
136
+ def hmac_md5(data: Union[str, bytes], key: Union[str, bytes]) -> bytes:
137
+ """HMAC-MD5计算,返回原始bytes"""
138
+ return DigestUtil._hmac_digest(data, key, "md5")
139
+
140
+ @staticmethod
141
+ def hmac_md5_hex(data: Union[str, bytes], key: Union[str, bytes]) -> str:
142
+ """HMAC-MD5计算,返回十六进制字符串"""
143
+ return DigestUtil._hmac_hex_digest(data, key, "md5")
144
+
145
+ # HMAC-SHA1
146
+
147
+ @staticmethod
148
+ def hmac_sha1(data: Union[str, bytes], key: Union[str, bytes]) -> bytes:
149
+ """HMAC-SHA1计算,返回原始bytes"""
150
+ return DigestUtil._hmac_digest(data, key, "sha1")
151
+
152
+ @staticmethod
153
+ def hmac_sha1_hex(data: Union[str, bytes], key: Union[str, bytes]) -> str:
154
+ """HMAC-SHA1计算,返回十六进制字符串"""
155
+ return DigestUtil._hmac_hex_digest(data, key, "sha1")
156
+
157
+ # HMAC-SHA256
158
+
159
+ @staticmethod
160
+ def hmac_sha256(data: Union[str, bytes], key: Union[str, bytes]) -> bytes:
161
+ """HMAC-SHA256计算,返回原始bytes"""
162
+ return DigestUtil._hmac_digest(data, key, "sha256")
163
+
164
+ @staticmethod
165
+ def hmac_sha256_hex(data: Union[str, bytes], key: Union[str, bytes]) -> str:
166
+ """HMAC-SHA256计算,返回十六进制字符串"""
167
+ return DigestUtil._hmac_hex_digest(data, key, "sha256")