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,133 @@
1
+ """IO工具模块"""
2
+
3
+ from typing import IO, List, Optional, Union
4
+
5
+
6
+ class IoUtil:
7
+ """IO工具类"""
8
+
9
+ @staticmethod
10
+ def read(stream: IO, charset: str = "utf-8") -> str:
11
+ """从流中读取字符串
12
+
13
+ :param stream: 输入流(需支持 read 方法)
14
+ :param charset: 字符编码,默认 utf-8
15
+ :return: 读取到的字符串内容
16
+ """
17
+ raw = stream.read()
18
+ if isinstance(raw, bytes):
19
+ return raw.decode(charset)
20
+ return raw
21
+
22
+ @staticmethod
23
+ def read_bytes(stream: IO) -> bytes:
24
+ """从流中读取字节数组
25
+
26
+ :param stream: 输入流(需支持 read 方法)
27
+ :return: 读取到的字节数组
28
+ """
29
+ data = stream.read()
30
+ if isinstance(data, str):
31
+ return data.encode("utf-8")
32
+ return data
33
+
34
+ @staticmethod
35
+ def read_lines(stream: IO, charset: str = "utf-8") -> List[str]:
36
+ """从流中读取行列表
37
+
38
+ :param stream: 输入流(需支持 read 或 readlines 方法)
39
+ :param charset: 字符编码,默认 utf-8
40
+ :return: 行列表
41
+ """
42
+ raw = stream.read()
43
+ if isinstance(raw, bytes):
44
+ raw = raw.decode(charset)
45
+ return raw.splitlines(keepends=False)
46
+
47
+ @staticmethod
48
+ def write(stream: IO, data: Union[str, bytes], charset: str = "utf-8") -> int:
49
+ """写入数据到流
50
+
51
+ :param stream: 输出流(需支持 write 方法)
52
+ :param data: 要写入的数据(str 或 bytes)
53
+ :param charset: 当 data 为 str 时使用的编码,默认 utf-8
54
+ :return: 写入的字节数
55
+ """
56
+ if isinstance(data, str):
57
+ encoded = data.encode(charset)
58
+ elif isinstance(data, (bytes, bytearray)):
59
+ encoded = bytes(data)
60
+ else:
61
+ encoded = str(data).encode(charset)
62
+ return stream.write(encoded)
63
+
64
+ @staticmethod
65
+ def write_bytes(stream: IO, data: bytes) -> int:
66
+ """写入字节数组到流
67
+
68
+ :param stream: 输出流(需支持 write 方法)
69
+ :param data: 要写入的字节数组
70
+ :return: 写入的字节数
71
+ """
72
+ if isinstance(data, bytearray):
73
+ data = bytes(data)
74
+ return stream.write(data)
75
+
76
+ @staticmethod
77
+ def copy(
78
+ input_stream: IO,
79
+ output_stream: IO,
80
+ buffer_size: int = 8192,
81
+ ) -> int:
82
+ """拷贝流数据
83
+
84
+ :param input_stream: 输入流
85
+ :param output_stream: 输出流
86
+ :param buffer_size: 缓冲区大小,默认 8192 字节
87
+ :return: 拷贝的总字节数
88
+ """
89
+ total = 0
90
+ while True:
91
+ chunk = input_stream.read(buffer_size)
92
+ if not chunk:
93
+ break
94
+ if isinstance(chunk, str):
95
+ chunk = chunk.encode("utf-8")
96
+ output_stream.write(chunk)
97
+ total += len(chunk)
98
+ return total
99
+
100
+ @staticmethod
101
+ def close(closable: Optional[IO]) -> None:
102
+ """安全关闭可关闭对象
103
+
104
+ 如果对象为 None 或已关闭,则不做任何操作。
105
+
106
+ :param closable: 可关闭对象(如文件流),可以为 None
107
+ """
108
+ if closable is None:
109
+ return
110
+ try:
111
+ closable.close()
112
+ except Exception:
113
+ pass
114
+
115
+ @staticmethod
116
+ def to_bytes(obj: Union[str, bytes, bytearray, memoryview]) -> bytes:
117
+ """将对象转为 bytes
118
+
119
+ 支持 str / bytes / bytearray / memoryview 类型。
120
+
121
+ :param obj: 待转换的对象
122
+ :return: 字节数组
123
+ :raises TypeError: 不支持的对象类型
124
+ """
125
+ if isinstance(obj, bytes):
126
+ return obj
127
+ if isinstance(obj, str):
128
+ return obj.encode("utf-8")
129
+ if isinstance(obj, bytearray):
130
+ return bytes(obj)
131
+ if isinstance(obj, memoryview):
132
+ return obj.tobytes()
133
+ raise TypeError(f"不支持的对象类型: {type(obj).__name__}")
@@ -0,0 +1,247 @@
1
+ """路径工具模块"""
2
+
3
+ import os
4
+ import shutil
5
+ from pathlib import Path
6
+ from typing import Callable, List, Optional, Union
7
+
8
+
9
+ class PathUtil:
10
+ """路径工具类"""
11
+
12
+ @staticmethod
13
+ def get_path_separator() -> str:
14
+ """获取当前操作系统的路径分隔符
15
+
16
+ :return: 路径分隔符字符串(Windows 为 '\\',Linux/macOS 为 '/')
17
+ """
18
+ return os.sep
19
+
20
+ @staticmethod
21
+ def normalize(path: Union[str, Path]) -> str:
22
+ """标准化路径
23
+
24
+ 解析符号链接、去除冗余分隔符和上级引用(..),返回绝对路径。
25
+
26
+ :param path: 原始路径
27
+ :return: 标准化后的路径字符串
28
+ """
29
+ return str(Path(path).resolve())
30
+
31
+ @staticmethod
32
+ def get_parent(path: Union[str, Path]) -> str:
33
+ """获取父目录路径
34
+
35
+ :param path: 文件或目录路径
36
+ :return: 父目录的路径字符串
37
+ """
38
+ return str(Path(path).parent)
39
+
40
+ @staticmethod
41
+ def get_name(path: Union[str, Path]) -> str:
42
+ """获取路径中的文件名或最后一级目录名
43
+
44
+ :param path: 文件或目录路径
45
+ :return: 文件名或目录名
46
+ """
47
+ return Path(path).name
48
+
49
+ @staticmethod
50
+ def sub_path(path: Union[str, Path], from_index: int, to_index: int) -> str:
51
+ """获取路径的子段
52
+
53
+ 按路径层级截取子路径。索引从 0 开始。
54
+
55
+ :param path: 原始路径
56
+ :param from_index: 起始索引(含)
57
+ :param to_index: 结束索引(不含)
58
+ :return: 子路径字符串
59
+ :raises ValueError: 索引越界时抛出
60
+ """
61
+ p = Path(path)
62
+ parts = p.parts
63
+ if from_index < 0 or to_index > len(parts) or from_index > to_index:
64
+ raise ValueError(f"无效的索引范围: [{from_index}, {to_index}),路径共 {len(parts)} 层")
65
+ sub_parts = parts[from_index:to_index]
66
+ return str(Path(*sub_parts))
67
+
68
+ @staticmethod
69
+ def is_absolute(path: Union[str, Path]) -> bool:
70
+ """判断路径是否为绝对路径
71
+
72
+ :param path: 文件路径
73
+ :return: 是绝对路径返回 True
74
+ """
75
+ return Path(path).is_absolute()
76
+
77
+ @staticmethod
78
+ def exists(path: Union[str, Path]) -> bool:
79
+ """判断路径是否存在
80
+
81
+ :param path: 文件或目录路径
82
+ :return: 存在返回 True
83
+ """
84
+ return Path(path).exists()
85
+
86
+ @staticmethod
87
+ def mkdir(path: Union[str, Path]) -> Path:
88
+ """创建目录(包括所有必要的父目录)
89
+
90
+ 如果目录已存在则不做任何操作。
91
+
92
+ :param path: 目录路径
93
+ :return: 创建的目录 Path 对象
94
+ """
95
+ p = Path(path)
96
+ p.mkdir(parents=True, exist_ok=True)
97
+ return p
98
+
99
+ @staticmethod
100
+ def mk_parent_dirs(path: Union[str, Path]) -> Path:
101
+ """创建文件的父目录
102
+
103
+ :param path: 文件路径
104
+ :return: 父目录的 Path 对象
105
+ """
106
+ p = Path(path)
107
+ p.parent.mkdir(parents=True, exist_ok=True)
108
+ return p.parent
109
+
110
+ @staticmethod
111
+ def loop_files(
112
+ path: Union[str, Path],
113
+ file_filter: Optional[Callable[[Path], bool]] = None,
114
+ ) -> List[Path]:
115
+ """递归遍历目录下的所有文件
116
+
117
+ :param path: 目录路径
118
+ :param file_filter: 可选的过滤函数,接受 Path 参数,返回 True 表示保留
119
+ :return: 文件 Path 列表
120
+ :raises FileNotFoundError: 路径不存在时抛出
121
+ """
122
+ p = Path(path)
123
+ if not p.exists():
124
+ raise FileNotFoundError(f"路径不存在: {path}")
125
+ if p.is_file():
126
+ files = [p]
127
+ else:
128
+ files = sorted(p.rglob("*"))
129
+ files = [f for f in files if f.is_file()]
130
+ if file_filter is not None:
131
+ files = [f for f in files if file_filter(f)]
132
+ return files
133
+
134
+ @staticmethod
135
+ def copy(
136
+ src: Union[str, Path],
137
+ dest: Union[str, Path],
138
+ is_override: bool = True,
139
+ ) -> Path:
140
+ """复制文件或目录
141
+
142
+ :param src: 源路径
143
+ :param dest: 目标路径
144
+ :param is_override: 是否覆盖已存在的目标,默认 True
145
+ :return: 目标路径的 Path 对象
146
+ :raises FileExistsError: 目标已存在且 is_override 为 False 时抛出
147
+ :raises FileNotFoundError: 源路径不存在时抛出
148
+ """
149
+ src_p = Path(src)
150
+ dest_p = Path(dest)
151
+ if not src_p.exists():
152
+ raise FileNotFoundError(f"源路径不存在: {src}")
153
+ if dest_p.exists() and not is_override:
154
+ raise FileExistsError(f"目标路径已存在: {dest}")
155
+ if src_p.is_dir():
156
+ if dest_p.exists() and is_override:
157
+ shutil.rmtree(dest_p)
158
+ shutil.copytree(src_p, dest_p)
159
+ else:
160
+ dest_p.parent.mkdir(parents=True, exist_ok=True)
161
+ shutil.copy2(src_p, dest_p)
162
+ return dest_p
163
+
164
+ @staticmethod
165
+ def move(
166
+ src: Union[str, Path],
167
+ dest: Union[str, Path],
168
+ is_override: bool = True,
169
+ ) -> Path:
170
+ """移动文件或目录
171
+
172
+ :param src: 源路径
173
+ :param dest: 目标路径
174
+ :param is_override: 是否覆盖已存在的目标,默认 True
175
+ :return: 目标路径的 Path 对象
176
+ :raises FileExistsError: 目标已存在且 is_override 为 False 时抛出
177
+ :raises FileNotFoundError: 源路径不存在时抛出
178
+ """
179
+ src_p = Path(src)
180
+ dest_p = Path(dest)
181
+ if not src_p.exists():
182
+ raise FileNotFoundError(f"源路径不存在: {src}")
183
+ if dest_p.exists():
184
+ if not is_override:
185
+ raise FileExistsError(f"目标路径已存在: {dest}")
186
+ if dest_p.is_dir():
187
+ shutil.rmtree(dest_p)
188
+ else:
189
+ dest_p.unlink()
190
+ dest_p.parent.mkdir(parents=True, exist_ok=True)
191
+ shutil.move(str(src_p), str(dest_p))
192
+ return dest_p
193
+
194
+ @staticmethod
195
+ def del_path(path: Union[str, Path]) -> bool:
196
+ """删除文件或目录
197
+
198
+ 目录将递归删除。如果路径不存在则返回 False。
199
+
200
+ :param path: 文件或目录路径
201
+ :return: 删除成功返回 True,路径不存在返回 False
202
+ """
203
+ p = Path(path)
204
+ if not p.exists():
205
+ return False
206
+ if p.is_dir():
207
+ shutil.rmtree(p)
208
+ else:
209
+ p.unlink()
210
+ return True
211
+
212
+ @staticmethod
213
+ def equals(
214
+ path1: Union[str, Path],
215
+ path2: Union[str, Path],
216
+ ) -> bool:
217
+ """比较两个路径是否指向同一位置
218
+
219
+ 通过解析后的绝对路径进行比较。
220
+
221
+ :param path1: 第一个路径
222
+ :param path2: 第二个路径
223
+ :return: 相等返回 True
224
+ """
225
+ return Path(path1).resolve() == Path(path2).resolve()
226
+
227
+ @staticmethod
228
+ def starts_with(path: Union[str, Path], prefix: str) -> bool:
229
+ """判断路径是否以指定前缀开头
230
+
231
+ :param path: 文件路径
232
+ :param prefix: 路径前缀
233
+ :return: 匹配返回 True
234
+ """
235
+ normalized_path = PathUtil.normalize(path)
236
+ normalized_prefix = PathUtil.normalize(prefix)
237
+ return normalized_path.startswith(normalized_prefix)
238
+
239
+ @staticmethod
240
+ def ends_with(path: Union[str, Path], suffix: str) -> bool:
241
+ """判断路径是否以指定后缀结尾
242
+
243
+ :param path: 文件路径
244
+ :param suffix: 路径后缀
245
+ :return: 匹配返回 True
246
+ """
247
+ return str(path).replace("\\", "/").endswith(suffix.replace("\\", "/"))
@@ -0,0 +1,137 @@
1
+ """资源工具模块"""
2
+
3
+ import fnmatch
4
+ import os
5
+ from pathlib import Path
6
+ from typing import IO, List
7
+
8
+
9
+ class ResourceUtil:
10
+ """资源工具类"""
11
+
12
+ # 默认资源搜索路径列表,可通过 add_resource_path 扩展
13
+ _resource_paths: List[str] = []
14
+
15
+ @classmethod
16
+ def add_resource_path(cls, path: str) -> None:
17
+ """添加资源搜索路径
18
+
19
+ :param path: 资源目录的绝对或相对路径
20
+ """
21
+ abs_path = os.path.abspath(path)
22
+ if abs_path not in cls._resource_paths:
23
+ cls._resource_paths.append(abs_path)
24
+
25
+ @staticmethod
26
+ def get_resource(path: str) -> str:
27
+ """获取资源的绝对路径
28
+
29
+ 按以下顺序查找资源:
30
+ 1. 绝对路径直接返回
31
+ 2. 当前工作目录
32
+ 3. 已注册的资源搜索路径
33
+
34
+ :param path: 资源的相对或绝对路径
35
+ :return: 资源的绝对路径
36
+ :raises FileNotFoundError: 资源不存在时抛出
37
+ """
38
+ # 如果是绝对路径且存在,直接返回
39
+ if os.path.isabs(path) and os.path.exists(path):
40
+ return os.path.abspath(path)
41
+
42
+ # 在当前工作目录查找
43
+ cwd_path = os.path.join(os.getcwd(), path)
44
+ if os.path.exists(cwd_path):
45
+ return os.path.abspath(cwd_path)
46
+
47
+ # 在已注册的资源路径中查找
48
+ for res_dir in ResourceUtil._resource_paths:
49
+ candidate = os.path.join(res_dir, path)
50
+ if os.path.exists(candidate):
51
+ return os.path.abspath(candidate)
52
+
53
+ raise FileNotFoundError(f"资源不存在: {path}")
54
+
55
+ @staticmethod
56
+ def get_resource_bytes(path: str) -> bytes:
57
+ """读取资源为字节数组
58
+
59
+ :param path: 资源的相对或绝对路径
60
+ :return: 资源内容的字节数组
61
+ :raises FileNotFoundError: 资源不存在时抛出
62
+ """
63
+ abs_path = ResourceUtil.get_resource(path)
64
+ with open(abs_path, "rb") as f:
65
+ return f.read()
66
+
67
+ @staticmethod
68
+ def get_resource_str(path: str, charset: str = "utf-8") -> str:
69
+ """读取资源为字符串
70
+
71
+ :param path: 资源的相对或绝对路径
72
+ :param charset: 字符编码,默认 utf-8
73
+ :return: 资源内容的字符串
74
+ :raises FileNotFoundError: 资源不存在时抛出
75
+ """
76
+ abs_path = ResourceUtil.get_resource(path)
77
+ with open(abs_path, encoding=charset) as f:
78
+ return f.read()
79
+
80
+ @staticmethod
81
+ def get_resource_stream(path: str) -> IO:
82
+ """获取资源的文件流
83
+
84
+ 调用方负责关闭返回的文件流。
85
+
86
+ :param path: 资源的相对或绝对路径
87
+ :return: 可读的文件对象
88
+ :raises FileNotFoundError: 资源不存在时抛出
89
+ """
90
+ abs_path = ResourceUtil.get_resource(path)
91
+ return open(abs_path, "rb")
92
+
93
+ @staticmethod
94
+ def get_resources(pattern: str) -> List[str]:
95
+ """根据模式匹配获取资源列表
96
+
97
+ 支持通配符模式(如 "*.txt"、"data/*.csv")。
98
+ 在当前工作目录和已注册的资源搜索路径中搜索。
99
+
100
+ :param pattern: 文件名匹配模式(支持 * 和 ? 通配符)
101
+ :return: 匹配的资源绝对路径列表
102
+ """
103
+ results: List[str] = []
104
+
105
+ # 在当前工作目录中搜索
106
+ cwd = os.getcwd()
107
+ ResourceUtil._search_in_dir(cwd, pattern, results)
108
+
109
+ # 在已注册的资源路径中搜索
110
+ for res_dir in ResourceUtil._resource_paths:
111
+ ResourceUtil._search_in_dir(res_dir, pattern, results)
112
+
113
+ # 去重并排序
114
+ return sorted(set(results))
115
+
116
+ @staticmethod
117
+ def _search_in_dir(base_dir: str, pattern: str, results: List[str]) -> None:
118
+ """在指定目录中递归搜索匹配的文件
119
+
120
+ :param base_dir: 基础搜索目录
121
+ :pattern: 文件名匹配模式
122
+ :param results: 结果列表(原地修改)
123
+ """
124
+ base = Path(base_dir)
125
+ if not base.is_dir():
126
+ return
127
+
128
+ # 如果 pattern 包含路径分隔符,按路径层级匹配
129
+ if os.sep in pattern or "/" in pattern:
130
+ for match in base.glob(pattern):
131
+ if match.is_file():
132
+ results.append(str(match.resolve()))
133
+ else:
134
+ # 只按文件名匹配,递归搜索所有子目录
135
+ for file_path in base.rglob("*"):
136
+ if file_path.is_file() and fnmatch.fnmatch(file_path.name, pattern):
137
+ results.append(str(file_path.resolve()))