zjs-kit 0.1.0__tar.gz

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.
zjs_kit-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: zjs-kit
3
+ Version: 0.1.0
4
+ Summary: Jenson Zhang Kit
5
+ Author: Jenson Zhang
6
+ Requires-Python: >=3.8
@@ -0,0 +1,14 @@
1
+ [project]
2
+ name = "zjs-kit"
3
+ version = "0.1.0"
4
+ description = "Jenson Zhang Kit"
5
+ readme = "README.md"
6
+ authors = [{ name = "Jenson Zhang", email = "zhang.jinshuo@163.com" }]
7
+ requires-python = ">=3.8"
8
+ license = { text = "MIT" }
9
+ keywords = ["logging", "utils", "tools"]
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "License :: OSI Approved :: MIT License",
13
+ "Operating System :: OS Independent",
14
+ ]
@@ -0,0 +1,13 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "zjs-kit"
7
+ version = "0.1.0"
8
+ description = "Jenson Zhang Kit"
9
+ authors = [{ name = "Jenson Zhang" }]
10
+ requires-python = ">=3.8"
11
+
12
+ [tool.setuptools.packages.find]
13
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,453 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # ========================================
4
+ # @Created : 2026/8/21 16:35
5
+ # @File : Logger.py
6
+ # @Project : None
7
+ # @Author : Jenson Zhang
8
+ # @Contact : zhang.jinshuo@163.com
9
+ # @Software : PyCharm
10
+ # @Purpose :
11
+ # ----------------------------------------
12
+ # Updated : 2026/8/21 16:35 (v1.0.0)
13
+ # Upd. Cont.: 初始版本
14
+ # Introduce :
15
+ # ========================================
16
+
17
+ import os
18
+ import sys
19
+ import time
20
+ import shutil
21
+ import socket
22
+ import getpass
23
+ import platform
24
+ import threading
25
+ import inspect
26
+ from datetime import datetime, timedelta
27
+ from pathlib import Path
28
+ from typing import Optional
29
+
30
+ try:
31
+ import colorama
32
+ colorama.init()
33
+ HAS_COLORAMA = True
34
+ except ImportError:
35
+ HAS_COLORAMA = False
36
+
37
+ # 日志级别定义
38
+ class ELogLevel:
39
+ """ 日志级别枚举 """
40
+ LOG_DEBUG = 0
41
+ LOG_INFO = 1
42
+ LOG_WARN = 2
43
+ LOG_ERROR = 3
44
+
45
+ # 为方便使用,定义模块级常量
46
+ LOG_DEBUG = ELogLevel.LOG_DEBUG
47
+ LOG_INFO = ELogLevel.LOG_INFO
48
+ LOG_WARN = ELogLevel.LOG_WARN
49
+ LOG_ERROR = ELogLevel.LOG_ERROR
50
+
51
+ # 全局状态
52
+ _logger_lock = threading.RLock() # 保护全局状态的锁
53
+ _logger_initialized = False # 是否已初始化
54
+ _log_file = None # 日志文件对象
55
+ _logger_path = "" # 日志根目录
56
+ _logger_name = "" # 日志名称(子目录名)
57
+ _logger_keep_days = 3 # 日志保留天数
58
+ _en_con = True # 是否启用控制台输出
59
+ _file_log_level = LOG_DEBUG # 文件输出最低级别
60
+ _con_log_level = LOG_DEBUG # 控制台输出最低级别
61
+ _current_log_date = "" # 当前日志文件对应的日期,用于跨天切换
62
+
63
+ # 内部工具函数
64
+ def _get_date_str() -> str:
65
+ """
66
+ 返回当前日期字符串
67
+ """
68
+ return datetime.now().strftime("%Y%m%d")
69
+
70
+ def _get_time_str() -> str:
71
+ """
72
+ 返回当前时间字符串
73
+ """
74
+ return datetime.now().strftime("%H%M_%S")
75
+
76
+ def _get_timestamp_str() -> str:
77
+ """
78
+ 返回完整时间戳字符串
79
+ """
80
+ now = datetime.now()
81
+ return f"[{now.year:04d}-{now.month:02d}-{now.day:02d}] ({now.hour:02d}:{now.minute:02d}:{now.second:02d})"
82
+
83
+ def _get_level_text(level: int) -> str:
84
+ """
85
+ 返回日志级别文本
86
+ """
87
+ if level == LOG_DEBUG:
88
+ return "[DEBUG]"
89
+ elif level == LOG_INFO:
90
+ return "[INFO ]"
91
+ elif level == LOG_WARN:
92
+ return "[WARN ]"
93
+ elif level == LOG_ERROR:
94
+ return "[ERROR]"
95
+ else:
96
+ return "[UNK ]"
97
+
98
+ def _get_level_color(level: int) -> str:
99
+ """
100
+ 返回控制台颜色代码
101
+ """
102
+ if not HAS_COLORAMA:
103
+ return ""
104
+ # 使用 colorama 颜色常量
105
+ if level == LOG_DEBUG:
106
+ return colorama.Style.DIM # 灰色
107
+ elif level == LOG_INFO:
108
+ return colorama.Fore.GREEN # 绿色
109
+ elif level == LOG_WARN:
110
+ return colorama.Fore.YELLOW # 黄色
111
+ elif level == LOG_ERROR:
112
+ return colorama.Fore.RED # 红色
113
+ else:
114
+ return colorama.Fore.RESET
115
+
116
+ def _reset_color() -> str:
117
+ """ 恢复颜色 """
118
+ if HAS_COLORAMA:
119
+ return colorama.Style.RESET_ALL
120
+ return ""
121
+
122
+ def _get_mac_address() -> str:
123
+ """
124
+ 获取 MAC 地址
125
+ """
126
+ try:
127
+ import uuid
128
+ mac = uuid.getnode()
129
+ if mac == 0:
130
+ return "None"
131
+ # 转换为 6 字节
132
+ mac_hex = f"{mac:012x}"
133
+ return "-".join(mac_hex[i:i+2].upper() for i in range(0, 12, 2))
134
+ except Exception:
135
+ return "None"
136
+
137
+ def _get_computer_name() -> str:
138
+ """
139
+ 获取计算机名
140
+ """
141
+ return socket.gethostname()
142
+
143
+ def _get_user_name() -> str:
144
+ """
145
+ 获取当前用户名
146
+ """
147
+ try:
148
+ return getpass.getuser()
149
+ except Exception:
150
+ return "Unknown"
151
+
152
+ def _get_program_name() -> str:
153
+ """
154
+ 获取当前程序名称
155
+ """
156
+ # 尝试获取主脚本路径
157
+ try:
158
+ main_script = sys.argv[0]
159
+ if main_script:
160
+ return os.path.basename(main_script)
161
+ except Exception:
162
+ pass
163
+ return "Python"
164
+
165
+ def _create_multi_dir(path: str) -> None:
166
+ """
167
+ 递归创建多级目录
168
+ """
169
+ if path:
170
+ os.makedirs(path, exist_ok=True)
171
+
172
+ def _delete_folder(folder_path: str) -> None:
173
+ """
174
+ 删除整个文件夹
175
+ """
176
+ if os.path.exists(folder_path):
177
+ shutil.rmtree(folder_path, ignore_errors=True)
178
+
179
+ def _clean_old_logs() -> None:
180
+ """
181
+ 清理过期日志文件夹
182
+ """
183
+ if not _logger_path or not _logger_name:
184
+ return
185
+
186
+ logger_base_dir = os.path.join(_logger_path, _logger_name)
187
+ if not os.path.isdir(logger_base_dir):
188
+ return
189
+
190
+ today = datetime.now().date()
191
+ for item in os.listdir(logger_base_dir):
192
+ item_path = os.path.join(logger_base_dir, item)
193
+ if os.path.isdir(item_path):
194
+ try:
195
+ # 尝试解析目录名为日期 YYYYMMDD
196
+ folder_date = datetime.strptime(item, "%Y%m%d").date()
197
+ diff_days = (today - folder_date).days
198
+ if diff_days > _logger_keep_days:
199
+ _delete_folder(item_path)
200
+ except ValueError:
201
+ # 目录名不是日期格式,忽略
202
+ continue
203
+
204
+ def _write_header_info() -> None:
205
+ """
206
+ 写入日志头信息到文件和控制台
207
+ """
208
+ global _log_file, _en_con
209
+
210
+ if _log_file is None:
211
+ return
212
+
213
+ # 如果文件已有内容,则跳过头部
214
+ _log_file.seek(0, os.SEEK_END)
215
+ if _log_file.tell() > 0:
216
+ _log_file.seek(0, os.SEEK_END)
217
+ return
218
+
219
+ now = datetime.now()
220
+ mac = _get_mac_address()
221
+ comp_name = _get_computer_name()
222
+ user_name = _get_user_name()
223
+ prog_name = _get_program_name()
224
+
225
+ header1 = f"=== MAC: {mac} || PC Name: {comp_name} || User Name: {user_name} ==="
226
+ header2 = f"=== Init Time: [{now.year:04d}-{now.month:02d}-{now.day:02d}]({now.hour:02d}:{now.minute:02d}:{now.second:02d}) || EXE Name: {prog_name} ==="
227
+
228
+ # 写入文件
229
+ _log_file.write(header1 + "\n")
230
+ _log_file.write(header2 + "\n")
231
+ _log_file.flush()
232
+
233
+ # 输出到控制台
234
+ if _en_con:
235
+ if HAS_COLORAMA:
236
+ print(colorama.Fore.BLUE + header1 + _reset_color())
237
+ print(colorama.Fore.BLUE + header2 + _reset_color())
238
+ else:
239
+ print(header1)
240
+ print(header2)
241
+
242
+ def _open_new_log_file() -> None:
243
+ """
244
+ 根据当前日期和时间打开新的日志文件
245
+ """
246
+ global _log_file, _current_log_date
247
+
248
+ date_str = _get_date_str()
249
+ time_str = _get_time_str()
250
+
251
+ # 构建日志目录
252
+ log_dir = os.path.join(_logger_path, _logger_name, date_str)
253
+ _create_multi_dir(log_dir)
254
+
255
+ # 日志文件完整路径
256
+ log_file_path = os.path.join(log_dir, f"{time_str}.log")
257
+
258
+ # 打开文件
259
+ try:
260
+ _log_file = open(log_file_path, "a+", encoding="utf-8")
261
+ _current_log_date = date_str
262
+ except Exception as e:
263
+ _log_file = None
264
+ error_msg = f"[Logger] Failed to open log file: {log_file_path}, error: {e}"
265
+ if _en_con:
266
+ if HAS_COLORAMA:
267
+ print(colorama.Fore.RED + error_msg + _reset_color())
268
+ else:
269
+ print(error_msg)
270
+ else:
271
+ # 输出到调试器(这里使用 stderr 代替)
272
+ sys.stderr.write(error_msg + "\n")
273
+ return
274
+
275
+ # 写入日志头
276
+ _write_header_info()
277
+
278
+ def _maybe_rotate_log_file() -> None:
279
+ """
280
+ 如果日期变化,则切换新日志文件
281
+ """
282
+ global _log_file, _current_log_date
283
+ today = _get_date_str()
284
+ if today != _current_log_date:
285
+ # 关闭旧文件,打开新文件
286
+ if _log_file:
287
+ _log_file.close()
288
+ _log_file = None
289
+ _open_new_log_file()
290
+ # 顺便清理旧日志
291
+ _clean_old_logs()
292
+
293
+ # 公共接口
294
+ def Logger_Init(
295
+ logger_path: str = ".",
296
+ logger_name: str = "zjs_kit",
297
+ logger_keep_days: int = 3,
298
+ en_con: bool = True,
299
+ file_log_level: int = LOG_DEBUG,
300
+ con_log_level: int = LOG_DEBUG
301
+ ) -> None:
302
+ """
303
+ 初始化日志系统
304
+ """
305
+ global _logger_initialized, _logger_path, _logger_name, _logger_keep_days
306
+ global _en_con, _file_log_level, _con_log_level, _log_file, _current_log_date
307
+
308
+ with _logger_lock:
309
+ if _logger_initialized:
310
+ return # 防止重复初始化
311
+
312
+ # 处理日志根目录
313
+ if not logger_path or logger_path == ".":
314
+ # 使用当前脚本所在目录
315
+ try:
316
+ script_path = os.path.dirname(os.path.abspath(sys.argv[0]))
317
+ logger_path = script_path
318
+ except Exception:
319
+ logger_path = os.getcwd()
320
+
321
+ _logger_path = logger_path
322
+ _logger_name = logger_name
323
+ _logger_keep_days = logger_keep_days
324
+ _en_con = en_con
325
+ _file_log_level = file_log_level
326
+ _con_log_level = con_log_level
327
+
328
+ # 打开日志文件
329
+ _open_new_log_file()
330
+
331
+ # 清理旧日志
332
+ _clean_old_logs()
333
+
334
+ _logger_initialized = True
335
+
336
+ def Logger_Uninit() -> None:
337
+ """
338
+ 关闭日志系统
339
+ """
340
+ global _logger_initialized, _log_file
341
+
342
+ with _logger_lock:
343
+ if not _logger_initialized:
344
+ return
345
+
346
+ if _log_file:
347
+ _log_file.close()
348
+ _log_file = None
349
+
350
+ _logger_initialized = False
351
+
352
+ def Logger_Write(
353
+ log_level: int,
354
+ class_name: Optional[str],
355
+ func_name: Optional[str],
356
+ fmt: str,
357
+ *args
358
+ ) -> None:
359
+ """
360
+ 写日志
361
+ """
362
+ global _logger_initialized, _file_log_level, _con_log_level, _log_file, _en_con
363
+
364
+ if not _logger_initialized:
365
+ return
366
+
367
+ # 如果文件和控制台都不需要输出,直接返回
368
+ if log_level < _file_log_level and log_level < _con_log_level:
369
+ return
370
+
371
+ with _logger_lock:
372
+ # 跨天自动切换日志文件
373
+ _maybe_rotate_log_file()
374
+
375
+ timestamp = _get_timestamp_str()
376
+ level_text = _get_level_text(log_level)
377
+
378
+ # 构建上下文:类名.函数名
379
+ context = ""
380
+ if class_name and func_name:
381
+ context = f"{class_name}.{func_name}"
382
+ elif class_name:
383
+ context = class_name
384
+ elif func_name:
385
+ context = func_name
386
+
387
+ # 格式化用户消息
388
+ try:
389
+ if args:
390
+ user_msg = fmt % args
391
+ else:
392
+ user_msg = fmt
393
+ except Exception:
394
+ user_msg = fmt # 格式化失败就保留原始字符串
395
+
396
+ # 组装完整日志行
397
+ if context:
398
+ full_line = f"{timestamp} {level_text} - {context} - {user_msg}"
399
+ else:
400
+ full_line = f"{timestamp} {level_text} - {user_msg}"
401
+
402
+ # 输出到文件
403
+ if _log_file and log_level >= _file_log_level:
404
+ _log_file.write(full_line + "\n")
405
+ _log_file.flush()
406
+
407
+ # 输出到控制台(带颜色)
408
+ if _en_con and log_level >= _con_log_level:
409
+ if HAS_COLORAMA:
410
+ color = _get_level_color(log_level)
411
+ reset = _reset_color()
412
+ print(color + full_line + reset)
413
+ else:
414
+ print(full_line)
415
+
416
+ def Get_Exe_Path() -> str:
417
+ """
418
+ 获取当前程序
419
+ """
420
+ try:
421
+ return os.path.abspath(sys.argv[0])
422
+ except Exception:
423
+ return ""
424
+
425
+ # 便捷日志函数
426
+ def log_debug(class_name: Optional[str], func_name: Optional[str], fmt: str, *args):
427
+ """Debug 级别日志"""
428
+ Logger_Write(LOG_DEBUG, class_name, func_name, fmt, *args)
429
+
430
+ def log_info(class_name: Optional[str], func_name: Optional[str], fmt: str, *args):
431
+ """Info 级别日志"""
432
+ Logger_Write(LOG_INFO, class_name, func_name, fmt, *args)
433
+
434
+ def log_warn(class_name: Optional[str], func_name: Optional[str], fmt: str, *args):
435
+ """Warn 级别日志"""
436
+ Logger_Write(LOG_WARN, class_name, func_name, fmt, *args)
437
+
438
+ def log_error(class_name: Optional[str], func_name: Optional[str], fmt: str, *args):
439
+ """Error 级别日志"""
440
+ Logger_Write(LOG_ERROR, class_name, func_name, fmt, *args)
441
+
442
+ # 自动获取函数名的装饰器/辅助函数
443
+ def auto_log(level: int, fmt: str, *args):
444
+ """
445
+ 自动获取调用者的函数名
446
+ """
447
+ frame = inspect.currentframe().f_back
448
+ func_name = frame.f_code.co_name
449
+ class_name = None
450
+ # 尝试从 self 参数获取类名(仅当在方法中调用时有效)
451
+ if 'self' in frame.f_locals:
452
+ class_name = frame.f_locals['self'].__class__.__name__
453
+ Logger_Write(level, class_name, func_name, fmt, *args)
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # ========================================
4
+ # @Created : 2026/8/21 16:24
5
+ # @File : __init__.py
6
+ # @Project : None
7
+ # @Author : Jenson Zhang
8
+ # @Contact : zhang.jinshuo@163.com
9
+ # @Software : PyCharm
10
+ # @Purpose :
11
+ # ----------------------------------------
12
+ # Updated : 2026/8/21 16:24 (v1.0.0)
13
+ # Upd. Cont.: 初始版本
14
+ # Introduce :
15
+ # ========================================
16
+ from .Logger import (
17
+ Logger_Init,
18
+ Logger_Uninit,
19
+ Logger_Write,
20
+ Get_Exe_Path,
21
+ log_debug,
22
+ log_info,
23
+ log_warn,
24
+ log_error,
25
+ auto_log,
26
+ LOG_DEBUG,
27
+ LOG_INFO,
28
+ LOG_WARN,
29
+ LOG_ERROR,
30
+ )
31
+ from .utils import read_json, write_json, timestamp, timestamp_ms
32
+
33
+ __all__ = [
34
+ "Logger_Init", "Logger_Uninit", "Logger_Write", "Get_Exe_Path",
35
+ "log_debug", "log_info", "log_warn", "log_error", "auto_log",
36
+ "LOG_DEBUG", "LOG_INFO", "LOG_WARN", "LOG_ERROR",
37
+ "read_json", "write_json", "timestamp", "timestamp_ms",
38
+ ]
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # ========================================
4
+ # @Created : 2026/8/21 16:37
5
+ # @File : utils.py
6
+ # @Project : None
7
+ # @Author : Jenson Zhang
8
+ # @Contact : zhang.jinshuo@163.com
9
+ # @Software : PyCharm
10
+ # @Purpose :
11
+ # ----------------------------------------
12
+ # Updated : 2026/8/21 16:37 (v1.0.0)
13
+ # Upd. Cont.: 初始版本
14
+ # Introduce :
15
+ # ========================================
16
+ import json
17
+ import time
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+ def read_json(file_path: str) -> Any:
22
+ """
23
+ 读取 JSON 文件并返回解析结果
24
+ """
25
+ with open(file_path, "r", encoding="utf-8") as f:
26
+ return json.load(f)
27
+
28
+ def write_json(file_path: str, data: Any, indent: int = 2, ensure_ascii: bool = False) -> None:
29
+ """
30
+ 将数据写入 JSON 文件
31
+ """
32
+ Path(file_path).parent.mkdir(parents=True, exist_ok=True)
33
+ with open(file_path, "w", encoding="utf-8") as f:
34
+ json.dump(data, f, ensure_ascii=ensure_ascii, indent=indent)
35
+
36
+ def timestamp() -> int:
37
+ """
38
+ 返回当前 Unix 时间戳 [s]
39
+ """
40
+ return int(time.time())
41
+
42
+ def timestamp_ms() -> int:
43
+ """
44
+ 返回当前 Unix 时间戳 [ms]
45
+ """
46
+ return int(time.time() * 1000)
47
+ return int(time.time() * 1000)
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: zjs-kit
3
+ Version: 0.1.0
4
+ Summary: Jenson Zhang Kit
5
+ Author: Jenson Zhang
6
+ Requires-Python: >=3.8
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/zjs_kit/Logger.py
4
+ src/zjs_kit/__init__.py
5
+ src/zjs_kit/utils.py
6
+ src/zjs_kit.egg-info/PKG-INFO
7
+ src/zjs_kit.egg-info/SOURCES.txt
8
+ src/zjs_kit.egg-info/dependency_links.txt
9
+ src/zjs_kit.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ zjs_kit