gomyck-tools 1.3.2__py3-none-any.whl → 1.3.3__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.
- ctools/cdebug.py +143 -0
- ctools/cron_lite.py +8 -15
- {gomyck_tools-1.3.2.dist-info → gomyck_tools-1.3.3.dist-info}/METADATA +4 -2
- {gomyck_tools-1.3.2.dist-info → gomyck_tools-1.3.3.dist-info}/RECORD +7 -5
- {gomyck_tools-1.3.2.dist-info → gomyck_tools-1.3.3.dist-info}/WHEEL +1 -1
- gomyck_tools-1.3.3.dist-info/licenses/LICENSE +13 -0
- {gomyck_tools-1.3.2.dist-info → gomyck_tools-1.3.3.dist-info}/top_level.txt +0 -0
ctools/cdebug.py
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
import subprocess
|
2
|
+
import sys
|
3
|
+
import threading
|
4
|
+
from datetime import datetime
|
5
|
+
from queue import Queue, Empty
|
6
|
+
|
7
|
+
|
8
|
+
class ProgramInterceptor:
|
9
|
+
def __init__(self, command):
|
10
|
+
self.command = command
|
11
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
12
|
+
self.log_filename = f"program_io_log_{timestamp}.txt"
|
13
|
+
self.process = None
|
14
|
+
self.log_queue = Queue()
|
15
|
+
|
16
|
+
def start(self):
|
17
|
+
# 启动子进程
|
18
|
+
self.process = subprocess.Popen(
|
19
|
+
self.command,
|
20
|
+
stdin=subprocess.PIPE,
|
21
|
+
stdout=subprocess.PIPE,
|
22
|
+
stderr=subprocess.PIPE,
|
23
|
+
bufsize=0,
|
24
|
+
universal_newlines=True
|
25
|
+
)
|
26
|
+
|
27
|
+
# 启动日志写入线程
|
28
|
+
log_thread = threading.Thread(target=self._write_log_thread, daemon=True)
|
29
|
+
log_thread.start()
|
30
|
+
|
31
|
+
# 记录初始信息
|
32
|
+
self._enqueue_log("header", f"Command: {' '.join(self.command)}")
|
33
|
+
self._enqueue_log("header", f"Start time: {datetime.now()}")
|
34
|
+
self._enqueue_log("header", "-" * 50)
|
35
|
+
|
36
|
+
# 启动输出转发线程
|
37
|
+
stdout_thread = threading.Thread(
|
38
|
+
target=self._forward_stream,
|
39
|
+
args=(self.process.stdout, sys.stdout, "stdout"),
|
40
|
+
daemon=True
|
41
|
+
)
|
42
|
+
stderr_thread = threading.Thread(
|
43
|
+
target=self._forward_stream,
|
44
|
+
args=(self.process.stderr, sys.stderr, "stderr"),
|
45
|
+
daemon=True
|
46
|
+
)
|
47
|
+
|
48
|
+
stdout_thread.start()
|
49
|
+
stderr_thread.start()
|
50
|
+
|
51
|
+
# 主线程处理输入转发
|
52
|
+
try:
|
53
|
+
while True:
|
54
|
+
if self.process.poll() is not None:
|
55
|
+
break
|
56
|
+
|
57
|
+
# 读取用户输入
|
58
|
+
try:
|
59
|
+
user_input = sys.stdin.readline()
|
60
|
+
if not user_input: # EOF
|
61
|
+
break
|
62
|
+
|
63
|
+
# 记录输入
|
64
|
+
self._enqueue_log("stdin", user_input)
|
65
|
+
|
66
|
+
# 转发到子进程
|
67
|
+
try:
|
68
|
+
self.process.stdin.write(user_input)
|
69
|
+
self.process.stdin.flush()
|
70
|
+
except (BrokenPipeError, OSError):
|
71
|
+
break
|
72
|
+
|
73
|
+
except KeyboardInterrupt:
|
74
|
+
break
|
75
|
+
|
76
|
+
finally:
|
77
|
+
# 清理工作
|
78
|
+
self.process.terminate()
|
79
|
+
|
80
|
+
# 等待所有输出处理完成
|
81
|
+
stdout_thread.join(timeout=1)
|
82
|
+
stderr_thread.join(timeout=1)
|
83
|
+
|
84
|
+
# 记录结束信息
|
85
|
+
self._enqueue_log("header", "-" * 50)
|
86
|
+
self._enqueue_log("header", f"End time: {datetime.now()}")
|
87
|
+
self._enqueue_log("header", f"Exit code: {self.process.returncode}")
|
88
|
+
|
89
|
+
# 等待日志写入完成
|
90
|
+
self.log_queue.put(None) # 结束信号
|
91
|
+
log_thread.join(timeout=2)
|
92
|
+
|
93
|
+
def _forward_stream(self, source, target, stream_name):
|
94
|
+
"""转发数据流并记录"""
|
95
|
+
for line in iter(source.readline, ''):
|
96
|
+
# 转发到目标
|
97
|
+
target.write(line)
|
98
|
+
target.flush()
|
99
|
+
|
100
|
+
# 记录输出
|
101
|
+
self._enqueue_log(stream_name, line)
|
102
|
+
|
103
|
+
def _enqueue_log(self, stream_type, content):
|
104
|
+
"""将日志内容放入队列"""
|
105
|
+
self.log_queue.put((stream_type, content))
|
106
|
+
|
107
|
+
def _write_log_thread(self):
|
108
|
+
"""日志写入线程"""
|
109
|
+
with open(self.log_filename, 'w', encoding='utf-8') as log_file:
|
110
|
+
while True:
|
111
|
+
try:
|
112
|
+
item = self.log_queue.get(timeout=0.5)
|
113
|
+
if item is None: # 结束信号
|
114
|
+
break
|
115
|
+
|
116
|
+
stream_type, content = item
|
117
|
+
|
118
|
+
if stream_type == 'stderr': continue
|
119
|
+
|
120
|
+
if stream_type == "header":
|
121
|
+
log_file.write(content + "\n")
|
122
|
+
else:
|
123
|
+
direction = ">>>" if stream_type == "stdin" else "<<<"
|
124
|
+
log_file.write(f"{direction} {stream_type}: {content}")
|
125
|
+
|
126
|
+
log_file.flush()
|
127
|
+
|
128
|
+
except Empty:
|
129
|
+
if self.process.poll() is not None:
|
130
|
+
continue
|
131
|
+
|
132
|
+
|
133
|
+
def main():
|
134
|
+
if len(sys.argv) < 2:
|
135
|
+
print("Usage: cdebug.py <program> [args...]")
|
136
|
+
print("Example: cdebug.py python script.py arg1 arg2")
|
137
|
+
sys.exit(1)
|
138
|
+
interceptor = ProgramInterceptor(sys.argv[1:])
|
139
|
+
interceptor.start()
|
140
|
+
|
141
|
+
|
142
|
+
if __name__ == "__main__":
|
143
|
+
main()
|
ctools/cron_lite.py
CHANGED
@@ -23,7 +23,7 @@ def demo1():
|
|
23
23
|
def demo2(xx, fff):
|
24
24
|
print('hello world222', xx, fff)
|
25
25
|
|
26
|
-
cron_lite.
|
26
|
+
cron_lite.reg_cron_task('0/1 * * * * ? *', demo2, (123123123, 34534534))
|
27
27
|
print(123123)
|
28
28
|
|
29
29
|
cron_lite.start_all()
|
@@ -35,13 +35,14 @@ class SchedulerMeta:
|
|
35
35
|
status: bool = False
|
36
36
|
event: sched.Event = None
|
37
37
|
scheduler: sched.scheduler = None
|
38
|
+
cron_str: str = None
|
38
39
|
|
39
40
|
|
40
41
|
scheduler_map: Dict[str, SchedulerMeta] = {} # {timer_task_name: SchedulerMeta}
|
41
42
|
_switch = False
|
42
43
|
_info_handler = print
|
43
44
|
_error_handler = print
|
44
|
-
_time_zone: Optional[pytz.BaseTzInfo] =
|
45
|
+
_time_zone: Optional[pytz.BaseTzInfo] = pytz.timezone("Asia/Shanghai")
|
45
46
|
|
46
47
|
|
47
48
|
def set_time_zone(time_zone_name: str):
|
@@ -79,7 +80,7 @@ def cron_task(cron_expr: str, task_name: str = None, till_time_stamp: int = None
|
|
79
80
|
return deco
|
80
81
|
|
81
82
|
|
82
|
-
def
|
83
|
+
def reg_cron_task(cron_expr, func, params, timer_task_name=None, till_time_stamp=None):
|
83
84
|
"""
|
84
85
|
cron_task decorator to register a function as crontab task
|
85
86
|
:param func: task callback function
|
@@ -105,11 +106,6 @@ def apply_cron_task(cron_expr, func, params, timer_task_name=None, till_time_sta
|
|
105
106
|
_register_next(task_name, wrapper, cron_expr, till_time_stamp)
|
106
107
|
|
107
108
|
_register_next(task_name, wrapper, cron_expr, till_time_stamp, init=True)
|
108
|
-
# 不使用 submit, 因为提交的任务, 不是 daemon 线程
|
109
|
-
t = threading.Thread(target=_start, args=(timer_task_name, ))
|
110
|
-
t.setDaemon(True)
|
111
|
-
t.start()
|
112
|
-
return t
|
113
109
|
|
114
110
|
def start_all(spawn: bool = True, daemon: bool = True, info_handler=None, error_handler=None) -> Optional[threading.Thread]:
|
115
111
|
"""
|
@@ -130,8 +126,7 @@ def start_all(spawn: bool = True, daemon: bool = True, info_handler=None, error_
|
|
130
126
|
if error_handler:
|
131
127
|
_error_handler = error_handler
|
132
128
|
if spawn:
|
133
|
-
t = threading.Thread(target=_start)
|
134
|
-
t.setDaemon(daemon)
|
129
|
+
t = threading.Thread(target=_start, daemon=daemon)
|
135
130
|
t.start()
|
136
131
|
return t
|
137
132
|
else:
|
@@ -192,6 +187,7 @@ def _register_next(timer_task_name, base_func, cron_expr, till_time_stamp, init:
|
|
192
187
|
scheduler_meta.timer_task_name = timer_task_name
|
193
188
|
scheduler_meta.switch = True
|
194
189
|
scheduler_meta.scheduler = sched.scheduler(time.time, time.sleep)
|
190
|
+
scheduler_meta.cron_str = cron_expr
|
195
191
|
scheduler_map[timer_task_name] = scheduler_meta
|
196
192
|
elif init:
|
197
193
|
raise ValueError(f"task name: {timer_task_name} already exists!!!!!")
|
@@ -219,7 +215,6 @@ def _run_sched(scheduler_meta: SchedulerMeta):
|
|
219
215
|
return
|
220
216
|
time.sleep(0.5)
|
221
217
|
|
222
|
-
|
223
218
|
def _start(taskName: str = None):
|
224
219
|
global _switch
|
225
220
|
_switch = True
|
@@ -227,9 +222,8 @@ def _start(taskName: str = None):
|
|
227
222
|
taskList = []
|
228
223
|
for timer_task_name, scheduler_meta in scheduler_map.items():
|
229
224
|
if taskName is not None and timer_task_name != taskName: continue
|
230
|
-
print("register job: ", timer_task_name)
|
231
|
-
thread = threading.Thread(target=_run_sched, args=(scheduler_meta, ))
|
232
|
-
thread.setDaemon(True)
|
225
|
+
print("register job: ", timer_task_name, ", cron: ", scheduler_meta.cron_str)
|
226
|
+
thread = threading.Thread(target=_run_sched, args=(scheduler_meta, ), daemon=True)
|
233
227
|
thread.start()
|
234
228
|
taskList.append(thread)
|
235
229
|
for task in taskList: task.join()
|
@@ -237,7 +231,6 @@ def _start(taskName: str = None):
|
|
237
231
|
_switch = False
|
238
232
|
scheduler_map.clear()
|
239
233
|
|
240
|
-
|
241
234
|
def _convert_cron(cron_expr):
|
242
235
|
res_cron = ""
|
243
236
|
cron_list = cron_expr.split(" ")
|
@@ -1,11 +1,12 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: gomyck-tools
|
3
|
-
Version: 1.3.
|
3
|
+
Version: 1.3.3
|
4
4
|
Summary: A tools collection for python development by hao474798383
|
5
5
|
Author-email: gomyck <hao474798383@163.com>
|
6
|
-
License:
|
6
|
+
License-Expression: Apache-2.0
|
7
7
|
Requires-Python: >=3.10
|
8
8
|
Description-Content-Type: text/markdown
|
9
|
+
License-File: LICENSE
|
9
10
|
Requires-Dist: jsonpickle~=3.4.2
|
10
11
|
Requires-Dist: SQLAlchemy~=2.0.36
|
11
12
|
Requires-Dist: chardet~=5.2.0
|
@@ -41,6 +42,7 @@ Requires-Dist: asyncpg>=0.28; extra == "full"
|
|
41
42
|
Requires-Dist: pytest>=7.0; extra == "full"
|
42
43
|
Requires-Dist: black>=24.0; extra == "full"
|
43
44
|
Requires-Dist: mypy>=1.0; extra == "full"
|
45
|
+
Dynamic: license-file
|
44
46
|
|
45
47
|
# Gomyck-Tools
|
46
48
|
|
@@ -9,6 +9,7 @@ ctools/bottle_webserver.py,sha256=l7t_sN4ayywD1sR0kzuhGioOuaqGR9VhJh7e6Gbd6aE,46
|
|
9
9
|
ctools/bottle_websocket.py,sha256=zqCE1rGlMeC9oxFOULNd137IWIhdetq83Oq5OoH_zGI,1953
|
10
10
|
ctools/browser_element_tools.py,sha256=IFR_tWu5on0LxhuC_4yT6EOjwCsC-juIoU8KQRDqR7E,9952
|
11
11
|
ctools/call.py,sha256=BCr8wzt5qd70okv8IZn-9-EpjywleZgvA3u1vfZ_Kt8,1581
|
12
|
+
ctools/cdebug.py,sha256=4PWZxrUcfTOljK7xXSiQ9iXddYwsKKeq96RGUFNqNkg,3807
|
12
13
|
ctools/cftp.py,sha256=SkHPDvKu58jnMnl68u5WxWEiFWsm2C0CGa5_GR_Obcw,2481
|
13
14
|
ctools/cjson.py,sha256=d0RZ53b-M18OudRFGRtPCvvGofJcaLHdbNlTCemyJag,1365
|
14
15
|
ctools/ckafka.py,sha256=EiiGCFkSbq8yRjQKVjc2_V114hKb8fJAVIOks_XbQ3w,5944
|
@@ -16,7 +17,7 @@ ctools/compile_tools.py,sha256=Nybh3vnkurIKnPnubdYzigjnzFu4GaTMKPvqFdibxmE,510
|
|
16
17
|
ctools/console.py,sha256=EZumuyynwteKUhUxB_XoulHswDxHd75OQB34RiZ-OBM,1807
|
17
18
|
ctools/coord_trans.py,sha256=pzIHxC4aLwvOF3eJG47Dda3vIq-Zp42xnu_FwILDflU,3951
|
18
19
|
ctools/credis.py,sha256=sW7yDQvxa7B4dWvGwUH7GROq-7ElRMDhFT6g2C8ryfE,4522
|
19
|
-
ctools/cron_lite.py,sha256=
|
20
|
+
ctools/cron_lite.py,sha256=CUqdtO02VnYUWcw6t6Jr7v2dKKhx3_G7CDAtlWnirBE,8232
|
20
21
|
ctools/ctoken.py,sha256=NZSBGF3lJajJFLRIZoeXmpp8h5cKM0dAH2weySgeORc,882
|
21
22
|
ctools/cword.py,sha256=ZRzAFn96yjo-hAbZuGIm4DoBAL2y8tFySWZ5xbYgY6Q,857
|
22
23
|
ctools/czip.py,sha256=8VQ420KgMF09U8McSXTkaAz0jd0Zzm6qazf3iJADQI4,4674
|
@@ -56,7 +57,8 @@ ctools/win_control.py,sha256=35f9x_ijSyc4ZDkcT32e9ZIhr_ffNxadynrQfFuIdYo,3489
|
|
56
57
|
ctools/word_fill.py,sha256=xeo-P4DOjQUqd-o9XL3g66wQrE2diUPGwFywm8TdVyw,18210
|
57
58
|
ctools/word_fill_entity.py,sha256=eX3G0Gy16hfGpavQSEkCIoKDdTnNgRRJrFvKliETZK8,985
|
58
59
|
ctools/work_path.py,sha256=OmfYu-Jjg2huRY6Su8zJ_2EGFFhtBZFbobYTwbjJtG4,1817
|
59
|
-
gomyck_tools-1.3.
|
60
|
-
gomyck_tools-1.3.
|
61
|
-
gomyck_tools-1.3.
|
62
|
-
gomyck_tools-1.3.
|
60
|
+
gomyck_tools-1.3.3.dist-info/licenses/LICENSE,sha256=X25ypfH9E6VTht2hcO8k7LCSdHUcoG_ALQt80jdYZfY,547
|
61
|
+
gomyck_tools-1.3.3.dist-info/METADATA,sha256=lGQt_tpcOP_1PQ1KTpb3_0ZBKxbXOkI8tuJfrnPk9qU,1744
|
62
|
+
gomyck_tools-1.3.3.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
63
|
+
gomyck_tools-1.3.3.dist-info/top_level.txt,sha256=-MiIH9FYRVKp1i5_SVRkaI-71WmF1sZSRrNWFU9ls3s,7
|
64
|
+
gomyck_tools-1.3.3.dist-info/RECORD,,
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2025 gomyck
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
File without changes
|