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.
Files changed (107) hide show
  1. ctools/__init__.py +21 -0
  2. ctools/ai/__init__.py +4 -0
  3. ctools/ai/llm_chat.py +184 -0
  4. ctools/ai/llm_client.py +163 -0
  5. ctools/ai/llm_exception.py +17 -0
  6. ctools/ai/mcp/__init__.py +4 -0
  7. ctools/ai/mcp/mcp_client.py +326 -0
  8. ctools/ai/tools/__init__.py +4 -0
  9. ctools/ai/tools/json_extract.py +202 -0
  10. ctools/ai/tools/quick_tools.py +149 -0
  11. ctools/ai/tools/think_process.py +11 -0
  12. ctools/ai/tools/tool_use_xml_parse.py +40 -0
  13. ctools/ai/tools/xml_extract.py +15 -0
  14. ctools/application.py +50 -47
  15. ctools/aspect.py +65 -0
  16. ctools/auto/__init__.py +4 -0
  17. ctools/{browser_element_tools.py → auto/browser_element.py} +18 -8
  18. ctools/{plan_area_tools.py → auto/plan_area.py} +5 -7
  19. ctools/{pty_tools.py → auto/pty_process.py} +6 -3
  20. ctools/{resource_bundle_tools.py → auto/resource_bundle.py} +4 -4
  21. ctools/{screenshot_tools.py → auto/screenshot.py} +7 -6
  22. ctools/{win_canvas.py → auto/win_canvas.py} +10 -4
  23. ctools/{win_control.py → auto/win_control.py} +14 -5
  24. ctools/call.py +34 -49
  25. ctools/cdate.py +84 -0
  26. ctools/cdebug.py +146 -0
  27. ctools/cid.py +20 -0
  28. ctools/cipher/__init__.py +4 -0
  29. ctools/{aes_tools.py → cipher/aes_util.py} +10 -0
  30. ctools/{b64.py → cipher/b64.py} +2 -0
  31. ctools/cipher/czip.py +133 -0
  32. ctools/cipher/rsa.py +75 -0
  33. ctools/{sign.py → cipher/sign.py} +2 -1
  34. ctools/{sm_tools.py → cipher/sm_util.py} +20 -4
  35. ctools/cjson.py +10 -10
  36. ctools/cron_lite.py +109 -97
  37. ctools/database/__init__.py +4 -0
  38. ctools/{database.py → database/database.py} +93 -26
  39. ctools/dict_wrapper.py +21 -0
  40. ctools/ex.py +4 -0
  41. ctools/geo/__init__.py +4 -0
  42. ctools/geo/coord_trans.py +127 -0
  43. ctools/geo/douglas_rarefy.py +139 -0
  44. ctools/metrics.py +56 -63
  45. ctools/office/__init__.py +4 -0
  46. ctools/office/cword.py +30 -0
  47. ctools/{word_fill.py → office/word_fill.py} +3 -6
  48. ctools/patch.py +88 -0
  49. ctools/{work_path.py → path_info.py} +34 -2
  50. ctools/pkg/__init__.py +4 -0
  51. ctools/pkg/dynamic_imp.py +38 -0
  52. ctools/pools/__init__.py +4 -0
  53. ctools/pools/process_pool.py +41 -0
  54. ctools/{thread_pool.py → pools/thread_pool.py} +13 -4
  55. ctools/similar.py +25 -0
  56. ctools/stream/__init__.py +4 -0
  57. ctools/stream/ckafka.py +164 -0
  58. ctools/stream/credis.py +127 -0
  59. ctools/{mqtt_utils.py → stream/mqtt_utils.py} +20 -12
  60. ctools/sys_info.py +36 -13
  61. ctools/sys_log.py +46 -27
  62. ctools/util/__init__.py +4 -0
  63. ctools/util/cftp.py +76 -0
  64. ctools/util/cklock.py +118 -0
  65. ctools/util/config_util.py +52 -0
  66. ctools/util/env_config.py +63 -0
  67. ctools/{html_soup.py → util/html_soup.py} +0 -7
  68. ctools/{http_utils.py → util/http_util.py} +4 -2
  69. ctools/{images_tools.py → util/image_process.py} +10 -1
  70. ctools/util/jb_cut.py +54 -0
  71. ctools/{id_worker_tools.py → util/snow_id.py} +8 -23
  72. ctools/web/__init__.py +4 -0
  73. ctools/web/aio_web_server.py +186 -0
  74. ctools/web/api_result.py +56 -0
  75. ctools/web/bottle_web_base.py +239 -0
  76. ctools/web/bottle_webserver.py +191 -0
  77. ctools/web/bottle_websocket.py +79 -0
  78. ctools/web/ctoken.py +103 -0
  79. ctools/{download_tools.py → web/download_util.py} +14 -12
  80. ctools/web/params_util.py +46 -0
  81. ctools/{upload_tools.py → web/upload_util.py} +3 -2
  82. gomyck_tools-1.4.7.dist-info/METADATA +70 -0
  83. gomyck_tools-1.4.7.dist-info/RECORD +88 -0
  84. {gomyck_tools-1.0.0.dist-info → gomyck_tools-1.4.7.dist-info}/WHEEL +1 -1
  85. gomyck_tools-1.4.7.dist-info/licenses/LICENSE +13 -0
  86. ctools/bashPath.py +0 -13
  87. ctools/bottle_server.py +0 -49
  88. ctools/console.py +0 -55
  89. ctools/date_utils.py +0 -44
  90. ctools/enums.py +0 -4
  91. ctools/excelOpt.py +0 -36
  92. ctools/imgDialog.py +0 -44
  93. ctools/license.py +0 -37
  94. ctools/log.py +0 -28
  95. ctools/mvc.py +0 -56
  96. ctools/obj.py +0 -20
  97. ctools/pacth.py +0 -73
  98. ctools/ssh.py +0 -9
  99. ctools/strDiff.py +0 -20
  100. ctools/string_tools.py +0 -101
  101. ctools/token_tools.py +0 -13
  102. ctools/wordFill.py +0 -24
  103. gomyck_tools-1.0.0.dist-info/METADATA +0 -20
  104. gomyck_tools-1.0.0.dist-info/RECORD +0 -54
  105. /ctools/{word_fill_entity.py → office/word_fill_entity.py} +0 -0
  106. /ctools/{compile_tools.py → util/compile_util.py} +0 -0
  107. {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, func
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
- return sm2_crypt.sign(sign_data.encode('UTF-8'), func.random_hex(sm2_crypt.para_len))
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
- return sm2_crypt.verify(sign_val, sign_data.encode('UTF-8'))
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', encoding='utf-8')
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
- return f'{jsonpickle.encode(obj, unpicklable=False)}'
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
- _time_zone: Optional[pytz.BaseTzInfo] = None
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
- def _register_next(timer_task_name, base_func, cron_expr, till_time_stamp):
36
- cron_obj = croniter(cron_expr)
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 = convert_cron(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 apply_cron_task(timer_task_name, func, params, cron_expr, till_time_stamp=None):
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 = convert_cron(cron_expr)
144
- assert len(cron_expr.split(" ")) in (5, 6), \
145
- "Only supported <minute hour day month weekday> and <minute hour day month weekday second>"
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
- func(timer_task_name, params, *args, **kwargs)
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(timer_task_name, wrapper, cron_expr, till_time_stamp)
109
+ _register_next(task_name, wrapper, cron_expr, till_time_stamp)
155
110
 
156
- _register_next(timer_task_name, wrapper, cron_expr, till_time_stamp)
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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2025/6/11 09:22'
@@ -1,57 +1,106 @@
1
1
  import contextlib
2
2
  import datetime
3
3
  import math
4
- from sqlalchemy import create_engine, Integer, Column, event
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
- from ctools import call, string_tools
9
- from ctools.thread_pool import thread_local
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
- def getEngine(db_key: str='default'):
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
- def set_search_path(dbapi_connection, default_schema):
25
- with dbapi_connection.cursor() as cursor:
26
- cursor.execute(f'SET search_path TO {default_schema}')
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
- 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):
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
- if default_schema: event.listen(engine, 'connect', lambda dbapi_connection, connection_record: set_search_path(dbapi_connection, default_schema))
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
- def _create_connection(db_url: str, pool_size: int=5, max_overflow: int=25, connect_args={}, echo: bool=False):
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
- echo=echo,
41
- future=True,
42
- pool_size=pool_size,
43
- max_overflow=max_overflow,
44
- pool_pre_ping=True,
45
- pool_recycle=3600,
46
- connect_args=connect_args)
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(string_tools.get_snowflake_id())
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
- def query_by_page(query, pageInfo):
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
- def query_by_page4_crd_sql(session, sql: str, params: dict, pageInfo) -> []:
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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2025/6/11 09:20'