gomyck-tools 1.0.0__py3-none-any.whl → 1.4.7__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/__init__.py +21 -0
- ctools/ai/__init__.py +4 -0
- ctools/ai/llm_chat.py +184 -0
- ctools/ai/llm_client.py +163 -0
- ctools/ai/llm_exception.py +17 -0
- ctools/ai/mcp/__init__.py +4 -0
- ctools/ai/mcp/mcp_client.py +326 -0
- ctools/ai/tools/__init__.py +4 -0
- ctools/ai/tools/json_extract.py +202 -0
- ctools/ai/tools/quick_tools.py +149 -0
- ctools/ai/tools/think_process.py +11 -0
- ctools/ai/tools/tool_use_xml_parse.py +40 -0
- ctools/ai/tools/xml_extract.py +15 -0
- ctools/application.py +50 -47
- ctools/aspect.py +65 -0
- ctools/auto/__init__.py +4 -0
- ctools/{browser_element_tools.py → auto/browser_element.py} +18 -8
- ctools/{plan_area_tools.py → auto/plan_area.py} +5 -7
- ctools/{pty_tools.py → auto/pty_process.py} +6 -3
- ctools/{resource_bundle_tools.py → auto/resource_bundle.py} +4 -4
- ctools/{screenshot_tools.py → auto/screenshot.py} +7 -6
- ctools/{win_canvas.py → auto/win_canvas.py} +10 -4
- ctools/{win_control.py → auto/win_control.py} +14 -5
- ctools/call.py +34 -49
- ctools/cdate.py +84 -0
- ctools/cdebug.py +146 -0
- ctools/cid.py +20 -0
- ctools/cipher/__init__.py +4 -0
- ctools/{aes_tools.py → cipher/aes_util.py} +10 -0
- ctools/{b64.py → cipher/b64.py} +2 -0
- ctools/cipher/czip.py +133 -0
- ctools/cipher/rsa.py +75 -0
- ctools/{sign.py → cipher/sign.py} +2 -1
- ctools/{sm_tools.py → cipher/sm_util.py} +20 -4
- ctools/cjson.py +10 -10
- ctools/cron_lite.py +109 -97
- ctools/database/__init__.py +4 -0
- ctools/{database.py → database/database.py} +93 -26
- ctools/dict_wrapper.py +21 -0
- ctools/ex.py +4 -0
- ctools/geo/__init__.py +4 -0
- ctools/geo/coord_trans.py +127 -0
- ctools/geo/douglas_rarefy.py +139 -0
- ctools/metrics.py +56 -63
- ctools/office/__init__.py +4 -0
- ctools/office/cword.py +30 -0
- ctools/{word_fill.py → office/word_fill.py} +3 -6
- ctools/patch.py +88 -0
- ctools/{work_path.py → path_info.py} +34 -2
- ctools/pkg/__init__.py +4 -0
- ctools/pkg/dynamic_imp.py +38 -0
- ctools/pools/__init__.py +4 -0
- ctools/pools/process_pool.py +41 -0
- ctools/{thread_pool.py → pools/thread_pool.py} +13 -4
- ctools/similar.py +25 -0
- ctools/stream/__init__.py +4 -0
- ctools/stream/ckafka.py +164 -0
- ctools/stream/credis.py +127 -0
- ctools/{mqtt_utils.py → stream/mqtt_utils.py} +20 -12
- ctools/sys_info.py +36 -13
- ctools/sys_log.py +46 -27
- ctools/util/__init__.py +4 -0
- ctools/util/cftp.py +76 -0
- ctools/util/cklock.py +118 -0
- ctools/util/config_util.py +52 -0
- ctools/util/env_config.py +63 -0
- ctools/{html_soup.py → util/html_soup.py} +0 -7
- ctools/{http_utils.py → util/http_util.py} +4 -2
- ctools/{images_tools.py → util/image_process.py} +10 -1
- ctools/util/jb_cut.py +54 -0
- ctools/{id_worker_tools.py → util/snow_id.py} +8 -23
- ctools/web/__init__.py +4 -0
- ctools/web/aio_web_server.py +186 -0
- ctools/web/api_result.py +56 -0
- ctools/web/bottle_web_base.py +239 -0
- ctools/web/bottle_webserver.py +191 -0
- ctools/web/bottle_websocket.py +79 -0
- ctools/web/ctoken.py +103 -0
- ctools/{download_tools.py → web/download_util.py} +14 -12
- ctools/web/params_util.py +46 -0
- ctools/{upload_tools.py → web/upload_util.py} +3 -2
- gomyck_tools-1.4.7.dist-info/METADATA +70 -0
- gomyck_tools-1.4.7.dist-info/RECORD +88 -0
- {gomyck_tools-1.0.0.dist-info → gomyck_tools-1.4.7.dist-info}/WHEEL +1 -1
- gomyck_tools-1.4.7.dist-info/licenses/LICENSE +13 -0
- ctools/bashPath.py +0 -13
- ctools/bottle_server.py +0 -49
- ctools/console.py +0 -55
- ctools/date_utils.py +0 -44
- ctools/enums.py +0 -4
- ctools/excelOpt.py +0 -36
- ctools/imgDialog.py +0 -44
- ctools/license.py +0 -37
- ctools/log.py +0 -28
- ctools/mvc.py +0 -56
- ctools/obj.py +0 -20
- ctools/pacth.py +0 -73
- ctools/ssh.py +0 -9
- ctools/strDiff.py +0 -20
- ctools/string_tools.py +0 -101
- ctools/token_tools.py +0 -13
- ctools/wordFill.py +0 -24
- gomyck_tools-1.0.0.dist-info/METADATA +0 -20
- gomyck_tools-1.0.0.dist-info/RECORD +0 -54
- /ctools/{word_fill_entity.py → office/word_fill_entity.py} +0 -0
- /ctools/{compile_tools.py → util/compile_util.py} +0 -0
- {gomyck_tools-1.0.0.dist-info → gomyck_tools-1.4.7.dist-info}/top_level.txt +0 -0
|
@@ -1,24 +1,40 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
|
|
3
|
-
from gmssl import sm2
|
|
3
|
+
from gmssl import sm2
|
|
4
4
|
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT
|
|
5
5
|
|
|
6
|
-
sm2_crypt: sm2.CryptSM2
|
|
6
|
+
sm2_crypt: sm2.CryptSM2 = None
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def init(private_key: str, public_key: str):
|
|
10
|
+
global sm2_crypt
|
|
11
|
+
if sm2_crypt is not None:
|
|
12
|
+
print('sm2 is already init!!!')
|
|
13
|
+
return
|
|
14
|
+
sm2_crypt = sm2.CryptSM2(private_key=private_key, public_key=public_key, asn1=True, mode=1)
|
|
7
15
|
|
|
8
16
|
|
|
9
17
|
def sign_with_sm2(sign_data: str) -> str:
|
|
10
|
-
|
|
18
|
+
if sm2_crypt is None: raise Exception('sm2 is not init!!!')
|
|
19
|
+
return sm2_crypt.sign_with_sm3(sign_data.encode('UTF-8'))
|
|
11
20
|
|
|
12
21
|
|
|
13
22
|
def verify_with_sm2(sign_val: str, sign_data: str) -> bool:
|
|
14
|
-
|
|
23
|
+
if sm2_crypt is None: raise Exception('sm2 is not init!!!')
|
|
24
|
+
try:
|
|
25
|
+
return sm2_crypt.verify_with_sm3(sign_val, sign_data.encode('UTF-8'))
|
|
26
|
+
except Exception as e:
|
|
27
|
+
print('签名验证失败: {}'.format(e))
|
|
28
|
+
return False
|
|
15
29
|
|
|
16
30
|
|
|
17
31
|
def encrypt_with_sm2(encrypt_data: str) -> str:
|
|
32
|
+
if sm2_crypt is None: raise Exception('sm2 is not init!!!')
|
|
18
33
|
return base64.b64encode(sm2_crypt.encrypt(encrypt_data.encode('UTF-8'))).decode('UTF-8')
|
|
19
34
|
|
|
20
35
|
|
|
21
36
|
def decrypt_with_sm2(encrypt_data: str) -> str:
|
|
37
|
+
if sm2_crypt is None: raise Exception('sm2 is not init!!!')
|
|
22
38
|
return sm2_crypt.decrypt(base64.b64decode(encrypt_data.encode('UTF-8'))).decode('UTF-8')
|
|
23
39
|
|
|
24
40
|
|
ctools/cjson.py
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
import jsonpickle
|
|
2
2
|
|
|
3
|
+
# 需要转换成str的属性
|
|
4
|
+
str_value_keys = []
|
|
3
5
|
jsonpickle.set_preferred_backend('json')
|
|
4
6
|
jsonpickle.set_encoder_options('json', ensure_ascii=False)
|
|
5
|
-
jsonpickle.set_decoder_options('json'
|
|
7
|
+
jsonpickle.set_decoder_options('json')
|
|
6
8
|
|
|
7
9
|
|
|
8
|
-
def dumps(obj) -> str:
|
|
10
|
+
def dumps(obj, **kwargs) -> str:
|
|
9
11
|
"""
|
|
10
12
|
将对象转换为json字符串
|
|
11
13
|
:param obj: 对象
|
|
12
14
|
:return: json 字符串
|
|
13
15
|
"""
|
|
16
|
+
# indent = 2 可以美化输出
|
|
14
17
|
if obj is None: return None
|
|
15
|
-
|
|
18
|
+
if type(obj) == str: return obj
|
|
19
|
+
return f'{jsonpickle.encode(obj, unpicklable=False, make_refs=False, **kwargs)}'
|
|
16
20
|
|
|
17
21
|
|
|
18
|
-
def loads(json_str: str) -> dict:
|
|
22
|
+
def loads(json_str: str, **kwargs) -> dict:
|
|
19
23
|
"""
|
|
20
24
|
将json字符串转换为对象
|
|
21
25
|
:param json_str: json 字符串
|
|
22
26
|
:return: 对象
|
|
23
27
|
"""
|
|
24
|
-
return jsonpickle.decode(json_str)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# 需要转换成str的属性
|
|
28
|
-
str_value_keys = []
|
|
28
|
+
return jsonpickle.decode(json_str, **kwargs)
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def unify_to_str(json_str: str) -> str:
|
|
32
|
-
if not str_value_keys: return json_str
|
|
32
|
+
if not str_value_keys and len(str_value_keys) == 0: return json_str
|
|
33
33
|
obj = loads(json_str)
|
|
34
34
|
if isinstance(obj, list):
|
|
35
35
|
_handle_list(obj)
|
ctools/cron_lite.py
CHANGED
|
@@ -11,6 +11,24 @@ from typing import Optional, Dict
|
|
|
11
11
|
import pytz
|
|
12
12
|
from croniter import croniter
|
|
13
13
|
|
|
14
|
+
"""
|
|
15
|
+
@cron_lite.cron_task('0/1 * * * * ? *')
|
|
16
|
+
def demo():
|
|
17
|
+
print('hello world')
|
|
18
|
+
|
|
19
|
+
@cron_lite.cron_task('0/1 * * * * ? *')
|
|
20
|
+
def demo1():
|
|
21
|
+
print('hello world111')
|
|
22
|
+
|
|
23
|
+
def demo2(xx, fff):
|
|
24
|
+
print('hello world222', xx, fff)
|
|
25
|
+
|
|
26
|
+
cron_lite.reg_cron_task('0/1 * * * * ? *', demo2, (123123123, 34534534))
|
|
27
|
+
print(123123)
|
|
28
|
+
|
|
29
|
+
cron_lite.start_all()
|
|
30
|
+
"""
|
|
31
|
+
|
|
14
32
|
|
|
15
33
|
class SchedulerMeta:
|
|
16
34
|
timer_task_name: str = None
|
|
@@ -18,13 +36,14 @@ class SchedulerMeta:
|
|
|
18
36
|
status: bool = False
|
|
19
37
|
event: sched.Event = None
|
|
20
38
|
scheduler: sched.scheduler = None
|
|
39
|
+
cron_str: str = None
|
|
21
40
|
|
|
22
41
|
|
|
23
42
|
scheduler_map: Dict[str, SchedulerMeta] = {} # {timer_task_name: SchedulerMeta}
|
|
24
43
|
_switch = False
|
|
25
|
-
_error_handler = print
|
|
26
44
|
_info_handler = print
|
|
27
|
-
|
|
45
|
+
_error_handler = print
|
|
46
|
+
_time_zone: Optional[pytz.BaseTzInfo] = pytz.timezone("Asia/Shanghai")
|
|
28
47
|
|
|
29
48
|
|
|
30
49
|
def set_time_zone(time_zone_name: str):
|
|
@@ -32,74 +51,8 @@ def set_time_zone(time_zone_name: str):
|
|
|
32
51
|
_time_zone = pytz.timezone(time_zone_name)
|
|
33
52
|
|
|
34
53
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if _time_zone:
|
|
38
|
-
cron_obj.set_current(datetime.now(tz=_time_zone))
|
|
39
|
-
next_time = int(cron_obj.get_next())
|
|
40
|
-
if scheduler_map.get(timer_task_name) is None:
|
|
41
|
-
scheduler_meta = SchedulerMeta()
|
|
42
|
-
scheduler_meta.timer_task_name = timer_task_name
|
|
43
|
-
scheduler_meta.switch = True
|
|
44
|
-
scheduler_meta.scheduler = sched.scheduler(time.time, time.sleep)
|
|
45
|
-
scheduler_map[timer_task_name] = scheduler_meta
|
|
46
|
-
if till_time_stamp is None or next_time <= till_time_stamp:
|
|
47
|
-
scheduler_map[timer_task_name].event = scheduler_map[timer_task_name].scheduler.enterabs(next_time, 0, base_func)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _run_sched(scheduler_meta: SchedulerMeta):
|
|
51
|
-
active(scheduler_meta.timer_task_name)
|
|
52
|
-
while True:
|
|
53
|
-
scheduler = scheduler_meta.scheduler
|
|
54
|
-
if not _switch or not scheduler_meta.switch:
|
|
55
|
-
scheduler.empty()
|
|
56
|
-
inactive(scheduler_meta.timer_task_name)
|
|
57
|
-
return
|
|
58
|
-
t = scheduler.run(False)
|
|
59
|
-
if t is None:
|
|
60
|
-
inactive(scheduler_meta.timer_task_name)
|
|
61
|
-
return
|
|
62
|
-
st = time.time()
|
|
63
|
-
while time.time() - st < t:
|
|
64
|
-
if not _switch or not scheduler_meta.switch:
|
|
65
|
-
scheduler.empty()
|
|
66
|
-
inactive(scheduler_meta.timer_task_name)
|
|
67
|
-
return
|
|
68
|
-
time.sleep(0.5)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def _start():
|
|
72
|
-
global _switch
|
|
73
|
-
_info_handler("cron started")
|
|
74
|
-
tl = []
|
|
75
|
-
for timer_task_name, scheduler_meta in scheduler_map.items():
|
|
76
|
-
print("Registering Job:", timer_task_name)
|
|
77
|
-
t = threading.Thread(target=_run_sched, args=(scheduler_meta,), daemon=True)
|
|
78
|
-
# 有些task非常耗时,会影响退出。目前设计改为退出时不保证task完成
|
|
79
|
-
t.start()
|
|
80
|
-
tl.append(t)
|
|
81
|
-
|
|
82
|
-
for t in tl:
|
|
83
|
-
t.join()
|
|
84
|
-
_info_handler("cron finished")
|
|
85
|
-
_switch = False # ensure close when there are no more tasks with switch open
|
|
86
|
-
scheduler_map.clear()
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def convert_cron(cron_expr):
|
|
90
|
-
res_cron = ""
|
|
91
|
-
cron_list = cron_expr.split(" ")
|
|
92
|
-
if len(cron_list) > 6:
|
|
93
|
-
for cron in cron_list[1:]:
|
|
94
|
-
if cron != "?":
|
|
95
|
-
res_cron += "%s " % cron
|
|
96
|
-
res_cron += "%s" % cron_list[0]
|
|
97
|
-
else:
|
|
98
|
-
res_cron = cron_expr
|
|
99
|
-
return res_cron
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def cron_task(cron_expr: str, till_time_stamp: int = None):
|
|
54
|
+
# @annotation
|
|
55
|
+
def cron_task(cron_expr: str, task_name: str = None, till_time_stamp: int = None):
|
|
103
56
|
"""
|
|
104
57
|
cron_task decorator to register a function as crontab task
|
|
105
58
|
:param cron_expr: the croniter accepted cron_expression. NOTICE: the default timezone is UTC and can be changed by
|
|
@@ -107,7 +60,7 @@ def cron_task(cron_expr: str, till_time_stamp: int = None):
|
|
|
107
60
|
:param till_time_stamp: run this jog till when. None means forever
|
|
108
61
|
:return: the real decorator
|
|
109
62
|
"""
|
|
110
|
-
cron_expr =
|
|
63
|
+
cron_expr = _convert_cron(cron_expr)
|
|
111
64
|
assert len(cron_expr.split(" ")) in (5, 6), \
|
|
112
65
|
"only supported <min hour day month weekday> and <min hour day month weekday sec>"
|
|
113
66
|
|
|
@@ -121,15 +74,15 @@ def cron_task(cron_expr: str, till_time_stamp: int = None):
|
|
|
121
74
|
_error_handler(f"run {func.__name__} failed\n" + traceback.format_exc())
|
|
122
75
|
except Exception:
|
|
123
76
|
_error_handler(f"run {func.__name__} failed\n")
|
|
124
|
-
_register_next(inner, inner, cron_expr, till_time_stamp)
|
|
77
|
+
_register_next(inner.__name__ if task_name is None else task_name, inner, cron_expr, till_time_stamp)
|
|
125
78
|
|
|
126
|
-
_register_next(inner, inner, cron_expr, till_time_stamp)
|
|
79
|
+
_register_next(inner.__name__ if task_name is None else task_name, inner, cron_expr, till_time_stamp, init=True)
|
|
127
80
|
return inner
|
|
128
81
|
|
|
129
82
|
return deco
|
|
130
83
|
|
|
131
84
|
|
|
132
|
-
def
|
|
85
|
+
def reg_cron_task(cron_expr, func, params, timer_task_name=None, till_time_stamp=None):
|
|
133
86
|
"""
|
|
134
87
|
cron_task decorator to register a function as crontab task
|
|
135
88
|
:param func: task callback function
|
|
@@ -140,37 +93,29 @@ def apply_cron_task(timer_task_name, func, params, cron_expr, till_time_stamp=No
|
|
|
140
93
|
:param till_time_stamp: run this jog till when. None means forever
|
|
141
94
|
:return: the real decorator
|
|
142
95
|
"""
|
|
143
|
-
cron_expr =
|
|
144
|
-
assert len(cron_expr.split(" ")) in (5, 6),
|
|
145
|
-
|
|
96
|
+
cron_expr = _convert_cron(cron_expr)
|
|
97
|
+
assert len(cron_expr.split(" ")) in (5, 6), "Only supported <minute hour day month weekday> and <minute hour day month weekday second>"
|
|
98
|
+
task_name = func.__name__ if timer_task_name is None else timer_task_name
|
|
146
99
|
|
|
147
100
|
@wraps(func)
|
|
148
101
|
def wrapper(*args, **kwargs):
|
|
149
102
|
try:
|
|
150
|
-
|
|
103
|
+
nonlocal params
|
|
104
|
+
func.__taskName__ = task_name
|
|
105
|
+
func(*params, *args, **kwargs)
|
|
151
106
|
except Exception as exc:
|
|
152
107
|
_error_handler(f"Run {func.__name__} failed with error: {str(exc)}")
|
|
153
108
|
finally:
|
|
154
|
-
_register_next(
|
|
109
|
+
_register_next(task_name, wrapper, cron_expr, till_time_stamp)
|
|
155
110
|
|
|
156
|
-
_register_next(
|
|
111
|
+
_register_next(task_name, wrapper, cron_expr, till_time_stamp, init=True)
|
|
157
112
|
|
|
158
|
-
global _switch
|
|
159
|
-
_switch = True
|
|
160
|
-
|
|
161
|
-
scheduler = scheduler_map.get(timer_task_name)
|
|
162
|
-
if scheduler:
|
|
163
|
-
scheduler.switch = True
|
|
164
|
-
t = threading.Thread(target=_run_sched, name=timer_task_name, args=(scheduler,), daemon=True)
|
|
165
|
-
# 有些task非常耗时,会影响退出。目前设计改为退出时不保证task完成
|
|
166
|
-
t.start()
|
|
167
|
-
return wrapper
|
|
168
113
|
|
|
169
|
-
|
|
170
|
-
def start_all(spawn: bool = True, info_handler=None, error_handler=None) -> Optional[threading.Thread]:
|
|
114
|
+
def start_all(spawn: bool = True, daemon: bool = True, info_handler=None, error_handler=None) -> Optional[threading.Thread]:
|
|
171
115
|
"""
|
|
172
116
|
start_all starts all cron tasks registered before.
|
|
173
117
|
:param spawn: whether to start a new thread for scheduler. If not, the action will block the current thread
|
|
118
|
+
:param daemon: the new thread is daemon if True
|
|
174
119
|
:param info_handler: handle info output (scheduler start / stop), default = print, can use logging.info
|
|
175
120
|
:param error_handler: handle error output (task execute exception), default = print, can use logging.error
|
|
176
121
|
:raise RuntimeError: if the tasks are already started and still running we cannot start again. The feature is not
|
|
@@ -179,16 +124,13 @@ def start_all(spawn: bool = True, info_handler=None, error_handler=None) -> Opti
|
|
|
179
124
|
"""
|
|
180
125
|
global _switch, _info_handler, _error_handler
|
|
181
126
|
if _switch:
|
|
182
|
-
raise RuntimeError("the crontab was already started")
|
|
127
|
+
raise RuntimeError("the crontab was already started...")
|
|
183
128
|
if info_handler:
|
|
184
129
|
_info_handler = info_handler
|
|
185
130
|
if error_handler:
|
|
186
131
|
_error_handler = error_handler
|
|
187
|
-
|
|
188
|
-
_switch = True
|
|
189
132
|
if spawn:
|
|
190
|
-
t = threading.Thread(target=_start)
|
|
191
|
-
t.setDaemon(True)
|
|
133
|
+
t = threading.Thread(target=_start, daemon=daemon)
|
|
192
134
|
t.start()
|
|
193
135
|
return t
|
|
194
136
|
else:
|
|
@@ -206,6 +148,7 @@ def active(timer_task_name):
|
|
|
206
148
|
if timer_task_name in scheduler_map:
|
|
207
149
|
scheduler_map.get(timer_task_name).status = True
|
|
208
150
|
|
|
151
|
+
|
|
209
152
|
def get_switch(timer_task_name):
|
|
210
153
|
switch = True
|
|
211
154
|
if timer_task_name in scheduler_map:
|
|
@@ -237,3 +180,72 @@ def stop_all(wait_thread: Optional[threading.Thread] = None):
|
|
|
237
180
|
scheduler_map.get(timer_task_name).switch = False
|
|
238
181
|
if wait_thread:
|
|
239
182
|
wait_thread.join()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _register_next(timer_task_name, base_func, cron_expr, till_time_stamp, init: bool = False):
|
|
186
|
+
cron_obj = croniter(cron_expr)
|
|
187
|
+
if _time_zone:
|
|
188
|
+
cron_obj.set_current(datetime.now(tz=_time_zone))
|
|
189
|
+
next_time = int(cron_obj.get_next())
|
|
190
|
+
if scheduler_map.get(timer_task_name) is None:
|
|
191
|
+
scheduler_meta = SchedulerMeta()
|
|
192
|
+
scheduler_meta.timer_task_name = timer_task_name
|
|
193
|
+
scheduler_meta.switch = True
|
|
194
|
+
scheduler_meta.scheduler = sched.scheduler(time.time, time.sleep)
|
|
195
|
+
scheduler_meta.cron_str = cron_expr
|
|
196
|
+
scheduler_map[timer_task_name] = scheduler_meta
|
|
197
|
+
elif init:
|
|
198
|
+
raise ValueError(f"task name: {timer_task_name} already exists!!!!!")
|
|
199
|
+
if till_time_stamp is None or next_time <= till_time_stamp:
|
|
200
|
+
scheduler_map[timer_task_name].event = scheduler_map[timer_task_name].scheduler.enterabs(next_time, 0, base_func)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _run_sched(scheduler_meta: SchedulerMeta):
|
|
204
|
+
active(scheduler_meta.timer_task_name)
|
|
205
|
+
while True:
|
|
206
|
+
scheduler = scheduler_meta.scheduler
|
|
207
|
+
if not _switch or not scheduler_meta.switch:
|
|
208
|
+
scheduler.empty()
|
|
209
|
+
inactive(scheduler_meta.timer_task_name)
|
|
210
|
+
return
|
|
211
|
+
t = scheduler.run(False)
|
|
212
|
+
if t is None:
|
|
213
|
+
inactive(scheduler_meta.timer_task_name)
|
|
214
|
+
return
|
|
215
|
+
st = time.time()
|
|
216
|
+
while time.time() - st < t:
|
|
217
|
+
if not _switch or not scheduler_meta.switch:
|
|
218
|
+
scheduler.empty()
|
|
219
|
+
inactive(scheduler_meta.timer_task_name)
|
|
220
|
+
return
|
|
221
|
+
time.sleep(0.5)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _start(taskName: str = None):
|
|
225
|
+
global _switch
|
|
226
|
+
_switch = True
|
|
227
|
+
_info_handler("cron job begin start...")
|
|
228
|
+
taskList = []
|
|
229
|
+
for timer_task_name, scheduler_meta in scheduler_map.items():
|
|
230
|
+
if taskName is not None and timer_task_name != taskName: continue
|
|
231
|
+
print("register job: ", timer_task_name, ", cron: ", scheduler_meta.cron_str)
|
|
232
|
+
thread = threading.Thread(target=_run_sched, args=(scheduler_meta,), daemon=True)
|
|
233
|
+
thread.start()
|
|
234
|
+
taskList.append(thread)
|
|
235
|
+
for task in taskList: task.join()
|
|
236
|
+
_info_handler("cron job execute finished...")
|
|
237
|
+
_switch = False
|
|
238
|
+
scheduler_map.clear()
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _convert_cron(cron_expr):
|
|
242
|
+
res_cron = ""
|
|
243
|
+
cron_list = cron_expr.split(" ")
|
|
244
|
+
if len(cron_list) > 6:
|
|
245
|
+
for cron in cron_list[1:]:
|
|
246
|
+
if cron != "?":
|
|
247
|
+
res_cron += "%s " % cron
|
|
248
|
+
res_cron += "%s" % cron_list[0]
|
|
249
|
+
else:
|
|
250
|
+
res_cron = cron_expr
|
|
251
|
+
return res_cron
|
|
@@ -1,57 +1,106 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
import datetime
|
|
3
3
|
import math
|
|
4
|
-
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import create_engine, BigInteger, Column, event
|
|
5
7
|
from sqlalchemy.ext.declarative import declarative_base
|
|
6
8
|
from sqlalchemy.orm import sessionmaker, Session
|
|
7
9
|
from sqlalchemy.sql import text
|
|
8
|
-
|
|
9
|
-
from ctools
|
|
10
|
+
|
|
11
|
+
from ctools import call
|
|
12
|
+
from ctools import cid
|
|
13
|
+
from ctools.pools.thread_pool import thread_local
|
|
14
|
+
from ctools.web.bottle_web_base import PageInfo
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
class XXXX(BaseMixin):
|
|
18
|
+
__tablename__ = 't_xxx_info'
|
|
19
|
+
__table_args__ = {'comment': 'xxx信息表'}
|
|
20
|
+
server_content: Column = Column(String(50), nullable=True, default='', comment='123123')
|
|
21
|
+
server_ip: Column = Column(String(30), index=True)
|
|
22
|
+
user_id: Column = Column(BigInteger)
|
|
23
|
+
|
|
24
|
+
database.init_db('postgresql://postgres:123456@192.168.3.107:32566/abc', default_schema='public', db_key='source', pool_size=100)
|
|
25
|
+
with database.get_session('source') as s:
|
|
26
|
+
s.execute(text('insert into xxx (name) values (:name)'), {'name': string_tools.get_random_str(5)})
|
|
27
|
+
s.commit()
|
|
28
|
+
"""
|
|
10
29
|
|
|
11
30
|
Base = None
|
|
12
31
|
inited_db = {}
|
|
13
32
|
engines = {}
|
|
14
33
|
sessionMakers = {}
|
|
15
34
|
|
|
16
|
-
|
|
35
|
+
|
|
36
|
+
def getEngine(db_key: str = 'default'):
|
|
17
37
|
return engines[db_key]
|
|
18
38
|
|
|
39
|
+
|
|
19
40
|
@call.init
|
|
20
41
|
def _init():
|
|
21
42
|
global Base
|
|
22
43
|
Base = declarative_base()
|
|
23
44
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
The string form of the URL is
|
|
48
|
+
dialect[+driver]://user:password@host/dbname[?key=value..]
|
|
49
|
+
where ``dialect`` is a database name such as ``mysql``, ``oracle``, ``postgresql``, etc.
|
|
50
|
+
and ``driver`` the name of a DBAPI such as ``psycopg2``, ``pyodbc``, ``cx_oracle``, etc. Alternatively
|
|
51
|
+
"""
|
|
52
|
+
|
|
27
53
|
|
|
28
54
|
# 密码里的@ 要替换成 %40
|
|
29
|
-
|
|
55
|
+
|
|
56
|
+
# sqlite connect_args={"check_same_thread": False} db_url=sqlite:///{}.format(db_url)
|
|
57
|
+
# sqlite 数据库, 初始化之后, 优化一下配置
|
|
58
|
+
# $ sqlite3 app.db
|
|
59
|
+
# > PRAGMA journal_mode=WAL; 设置事务的模式, wal 允许读写并发, 但是会额外创建俩文件
|
|
60
|
+
# > PRAGMA synchronous=NORMAL; 设置写盘策略, 默认是 FULL, 日志,数据都落, 设置成 NORMAL, 日志写完就算事务完成
|
|
61
|
+
|
|
62
|
+
def init_db(db_url: str, db_key: str = 'default', connect_args: dict = {}, default_schema: str = None, pool_size: int = 5, max_overflow: int = 25, echo: bool = False, auto_gen_table: bool = False):
|
|
63
|
+
if db_url.startswith('mysql'):
|
|
64
|
+
import pymysql
|
|
65
|
+
pymysql.install_as_MySQLdb()
|
|
30
66
|
if inited_db.get(db_key): raise Exception('db {} already init!!!'.format(db_key))
|
|
31
67
|
global engines, sessionMakers
|
|
68
|
+
if default_schema: connect_args.update({'options': '-csearch_path={}'.format(default_schema)})
|
|
32
69
|
engine, sessionMaker = _create_connection(db_url=db_url, connect_args=connect_args, pool_size=pool_size, max_overflow=max_overflow, echo=echo)
|
|
33
70
|
engines[db_key] = engine
|
|
34
71
|
sessionMakers[db_key] = sessionMaker
|
|
35
72
|
inited_db[db_key] = True
|
|
36
|
-
|
|
73
|
+
# 这个有并发问题, 高并发会导致卡顿, 可以考虑去做一些别的事儿
|
|
74
|
+
#if default_schema: event.listen(engine, 'connect', lambda dbapi_connection, connection_record: _set_search_path(dbapi_connection, default_schema))
|
|
75
|
+
if auto_gen_table: Base.metadata.create_all(engine)
|
|
37
76
|
|
|
38
|
-
|
|
77
|
+
|
|
78
|
+
def _set_search_path(dbapi_connection, default_schema):
|
|
79
|
+
with dbapi_connection.cursor() as cursor:
|
|
80
|
+
cursor.execute(f'SET search_path TO {default_schema}')
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _create_connection(db_url: str, pool_size: int = 5, max_overflow: int = 25, connect_args={}, echo: bool = False):
|
|
39
84
|
engine = create_engine('{}'.format(db_url),
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
sm = sessionmaker(bind=engine)
|
|
85
|
+
echo=echo,
|
|
86
|
+
future=True,
|
|
87
|
+
pool_size=pool_size,
|
|
88
|
+
max_overflow=max_overflow,
|
|
89
|
+
pool_pre_ping=True,
|
|
90
|
+
pool_recycle=3600,
|
|
91
|
+
connect_args=connect_args)
|
|
92
|
+
sm = sessionmaker(bind=engine, expire_on_commit=False)
|
|
48
93
|
return engine, sm
|
|
49
94
|
|
|
95
|
+
|
|
50
96
|
def generate_custom_id():
|
|
51
|
-
return str(
|
|
97
|
+
return str(cid.get_snowflake_id())
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class BaseMixin(Base):
|
|
101
|
+
__abstract__ = True
|
|
102
|
+
obj_id = Column(BigInteger, primary_key=True, default=generate_custom_id)
|
|
52
103
|
|
|
53
|
-
class BaseMixin:
|
|
54
|
-
obj_id = Column(Integer, primary_key=True, default=generate_custom_id)
|
|
55
104
|
# ext1 = Column(String)
|
|
56
105
|
# ext2 = Column(String)
|
|
57
106
|
# ext3 = Column(String)
|
|
@@ -75,10 +124,16 @@ class BaseMixin:
|
|
|
75
124
|
ret_state[key] = state[key]
|
|
76
125
|
return ret_state
|
|
77
126
|
|
|
127
|
+
@classmethod
|
|
128
|
+
def init(cls, data: dict):
|
|
129
|
+
valid_keys = cls.__table__.columns.keys()
|
|
130
|
+
filtered = {k: v for k, v in data.items() if k in valid_keys}
|
|
131
|
+
return cls(**filtered)
|
|
132
|
+
|
|
78
133
|
@contextlib.contextmanager
|
|
79
|
-
def get_session(db_key: str='default') -> Session:
|
|
134
|
+
def get_session(db_key: str = 'default') -> Session:
|
|
80
135
|
thread_local.db_key = db_key
|
|
81
|
-
if sm:=sessionMakers.get(db_key):
|
|
136
|
+
if sm := sessionMakers.get(db_key):
|
|
82
137
|
s = sm()
|
|
83
138
|
else:
|
|
84
139
|
raise ValueError("Invalid db_key: {}".format(db_key))
|
|
@@ -90,22 +145,32 @@ def get_session(db_key: str='default') -> Session:
|
|
|
90
145
|
finally:
|
|
91
146
|
s.close()
|
|
92
147
|
|
|
148
|
+
|
|
93
149
|
class PageInfoBuilder:
|
|
94
150
|
|
|
95
|
-
def __init__(self, pageInfo, total_count, records):
|
|
151
|
+
def __init__(self, pageInfo: PageInfo, total_count, records):
|
|
96
152
|
self.page_size = pageInfo.page_size
|
|
97
153
|
self.page_index = pageInfo.page_index
|
|
98
154
|
self.total_count = total_count
|
|
99
155
|
self.total_page = math.ceil(total_count / int(pageInfo.page_size))
|
|
100
156
|
self.records = records
|
|
101
157
|
|
|
102
|
-
|
|
158
|
+
|
|
159
|
+
def query_by_page(query, pageInfo) -> PageInfoBuilder:
|
|
160
|
+
"""
|
|
161
|
+
使用方法:
|
|
162
|
+
with database.get_session() as s:
|
|
163
|
+
query = s.query(AppInfoEntity).filter(AppInfoEntity.app_name.contains(params.app_name))
|
|
164
|
+
result = database.query_by_page(query, params.page_info)
|
|
165
|
+
return R.ok(result)
|
|
166
|
+
"""
|
|
103
167
|
records = query.offset((pageInfo.page_index - 1) * pageInfo.page_size).limit(pageInfo.page_size).all()
|
|
104
168
|
rs = []
|
|
105
169
|
for r in records:
|
|
106
170
|
rs.append(r)
|
|
107
171
|
return PageInfoBuilder(pageInfo, query.count(), rs)
|
|
108
172
|
|
|
173
|
+
|
|
109
174
|
def query4_crd_sql(session, sql: str, params: dict) -> []:
|
|
110
175
|
records = session.execute(text(sql), params).fetchall()
|
|
111
176
|
rs = []
|
|
@@ -116,6 +181,7 @@ def query4_crd_sql(session, sql: str, params: dict) -> []:
|
|
|
116
181
|
rs.append(data)
|
|
117
182
|
return rs
|
|
118
183
|
|
|
184
|
+
|
|
119
185
|
sqlite_and_pg_page_sql = """
|
|
120
186
|
limit :limit offset :offset
|
|
121
187
|
"""
|
|
@@ -123,7 +189,8 @@ mysql_page_sql = """
|
|
|
123
189
|
limit :offset, :limit
|
|
124
190
|
"""
|
|
125
191
|
|
|
126
|
-
|
|
192
|
+
|
|
193
|
+
def query_by_page4_crd_sql(session, sql: str, params: dict, pageInfo: PageInfo) -> []:
|
|
127
194
|
db_name = engines[thread_local.db_key].name
|
|
128
195
|
if db_name == 'postgresql' or db_name == 'sqlite':
|
|
129
196
|
page_sql = sqlite_and_pg_page_sql
|
ctools/dict_wrapper.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2024/10/25 09:42'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DictWrapper(dict):
|
|
8
|
+
|
|
9
|
+
def __getattr__(self, key):
|
|
10
|
+
res = self.get(key)
|
|
11
|
+
if res is None:
|
|
12
|
+
raise AttributeError(f" ==>> {key} <<== Not Found In This Entity!!!")
|
|
13
|
+
if isinstance(res, dict):
|
|
14
|
+
return DictWrapper(res)
|
|
15
|
+
return res
|
|
16
|
+
|
|
17
|
+
def __setattr__(self, key, value):
|
|
18
|
+
self[key] = value
|
|
19
|
+
|
|
20
|
+
def __delattr__(self, key):
|
|
21
|
+
del self[key]
|
ctools/ex.py
CHANGED
|
@@ -2,7 +2,11 @@ import time
|
|
|
2
2
|
import traceback
|
|
3
3
|
from functools import wraps
|
|
4
4
|
|
|
5
|
+
"""
|
|
6
|
+
@exception_handler(fail_return=['解析错误'], print_exc=True)
|
|
7
|
+
"""
|
|
5
8
|
|
|
9
|
+
# annotation
|
|
6
10
|
def exception_handler(fail_return, retry_num=0, delay=3, catch_e=Exception, print_exc=False):
|
|
7
11
|
def decorator(func):
|
|
8
12
|
@wraps(func)
|
ctools/geo/__init__.py
ADDED