gomyck-tools 1.0.0__py3-none-any.whl → 1.0.2__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/api_result.py ADDED
@@ -0,0 +1,51 @@
1
+ from ctools import cjson
2
+
3
+ cjson.str_value_keys = [
4
+ "obj_id",
5
+ "script_id",
6
+ "template_id"
7
+ ]
8
+
9
+
10
+ class _ResEnum(object):
11
+ def __init__(self, code: int, message: str):
12
+ self.code = code
13
+ self.message = message
14
+
15
+ def __eq__(self, o: object) -> bool:
16
+ return self.code == o
17
+
18
+ class R(object):
19
+ class Code:
20
+
21
+ @staticmethod
22
+ def cus_code(code, msg):
23
+ return _ResEnum(code, msg)
24
+
25
+ SUCCESS = _ResEnum(200, "成功")
26
+ FAIL = _ResEnum(400, "失败")
27
+ ERROR = _ResEnum(500, "异常")
28
+
29
+ def __init__(self, code: int, message: str, data=""):
30
+ self.code = code
31
+ self.message = message
32
+ self.data = data
33
+
34
+ def _to_json(self):
35
+ return cjson.unify_to_str(cjson.dumps(self))
36
+
37
+ @staticmethod
38
+ def parser(r_json: str):
39
+ return R(**cjson.loads(r_json))
40
+
41
+ @staticmethod
42
+ def ok(data=None, resp=Code.SUCCESS, msg=None):
43
+ return R(resp.code, msg if msg is not None else resp.message, data)._to_json()
44
+
45
+ @staticmethod
46
+ def fail(msg=None, resp=Code.FAIL, data=None):
47
+ return R(resp.code, msg if msg is not None else resp.message, data)._to_json()
48
+
49
+ @staticmethod
50
+ def error(msg=None, resp=Code.ERROR, data=None):
51
+ return R(resp.code, msg if msg is not None else resp.message, data)._to_json()
ctools/bottle_server.py CHANGED
@@ -4,6 +4,39 @@ from wsgiref.simple_server import WSGIServer, WSGIRequestHandler, make_server
4
4
  from geventwebsocket.handler import WebSocketHandler
5
5
  from ctools import sys_log
6
6
 
7
+ """
8
+ app = init_app('/doc_download')
9
+
10
+ @app.get('/queryList')
11
+ @mvc.parameter_handler()
12
+ @rule('DOC:DOWNLOAD')
13
+ def query_list(params, pageInfo):
14
+ """
15
+
16
+ """
17
+ module_names = list(globals().keys())
18
+ def get_modules():
19
+ mods = []
20
+ for modname in module_names:
21
+ if modname == 'base' or modname == 'online' or modname.startswith('__') or modname == 'importlib': continue
22
+ module = globals()[modname]
23
+ mods.append(module)
24
+ return mods
25
+
26
+ def get_ws_modules():
27
+ from . import websocket
28
+ return [websocket]
29
+ """
30
+
31
+ """
32
+ http_app = Bottle()
33
+ for module in controller.get_modules():
34
+ log.debug('正在挂载http路由: {}'.format(module.app.context_path))
35
+ http_app.mount(module.app.context_path, module.app)
36
+ http_server = bottle_server.WSGIRefServer(port=application.Server.port)
37
+ http_app.run(server=http_server, quiet=True)
38
+ """
39
+
7
40
  class ThreadedWSGIServer(ThreadingMixIn, WSGIServer): pass
8
41
 
9
42
  class CustomWSGIHandler(WSGIRequestHandler):
@@ -36,7 +69,7 @@ class WSGIRefServer(ServerAdapter):
36
69
 
37
70
  class WebSocketServer(ServerAdapter):
38
71
 
39
- def __init__(self, host='0.0.0.0', port=8012):
72
+ def __init__(self, host='0.0.0.0', port=8011):
40
73
  super().__init__(host, port)
41
74
  self.server = None
42
75
 
ctools/call.py CHANGED
@@ -3,7 +3,7 @@ import threading
3
3
  import time
4
4
  from functools import wraps
5
5
 
6
-
6
+ # annotation
7
7
  def once(func):
8
8
  """
9
9
  decorator to initialize a function once
@@ -24,7 +24,7 @@ def once(func):
24
24
 
25
25
  return wrapper
26
26
 
27
-
27
+ # annotation
28
28
  def init(func):
29
29
  """
30
30
  decorator to initialize a function automic
@@ -38,7 +38,7 @@ def init(func):
38
38
 
39
39
  return wrapper
40
40
 
41
-
41
+ # annotation
42
42
  def schd(interval_seconds, start_by_call: bool = False):
43
43
  start_flag = False
44
44
  run_flag = False
ctools/cjson.py CHANGED
@@ -1,10 +1,11 @@
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
7
  jsonpickle.set_decoder_options('json', encoding='utf-8')
6
8
 
7
-
8
9
  def dumps(obj) -> str:
9
10
  """
10
11
  将对象转换为json字符串
@@ -12,9 +13,9 @@ def dumps(obj) -> str:
12
13
  :return: json 字符串
13
14
  """
14
15
  if obj is None: return None
16
+ if type(obj) == str: return obj
15
17
  return f'{jsonpickle.encode(obj, unpicklable=False)}'
16
18
 
17
-
18
19
  def loads(json_str: str) -> dict:
19
20
  """
20
21
  将json字符串转换为对象
@@ -23,11 +24,6 @@ def loads(json_str: str) -> dict:
23
24
  """
24
25
  return jsonpickle.decode(json_str)
25
26
 
26
-
27
- # 需要转换成str的属性
28
- str_value_keys = []
29
-
30
-
31
27
  def unify_to_str(json_str: str) -> str:
32
28
  if not str_value_keys: return json_str
33
29
  obj = loads(json_str)
@@ -37,7 +33,6 @@ def unify_to_str(json_str: str) -> str:
37
33
  _handle_dict(obj)
38
34
  return dumps(obj)
39
35
 
40
-
41
36
  def _handle_list(data):
42
37
  for o in data:
43
38
  if isinstance(o, list):
@@ -45,7 +40,6 @@ def _handle_list(data):
45
40
  elif isinstance(o, dict):
46
41
  _handle_dict(o)
47
42
 
48
-
49
43
  def _handle_dict(data):
50
44
  for k, v in data.items():
51
45
  if isinstance(v, list):
ctools/ckafka.py ADDED
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2024/9/5 10:39'
5
+
6
+ import signal
7
+ import sys
8
+ import time
9
+ from threading import Thread, Lock
10
+
11
+ from kafka import KafkaProducer, errors, KafkaConsumer
12
+ from kafka.producer.future import FutureRecordMetadata
13
+
14
+ from ctools import thread_pool
15
+ from ctools.cjson import dumps
16
+
17
+ """
18
+ import time
19
+
20
+ from ctools import thread_pool
21
+ from ctools.ckafka import CKafka
22
+
23
+ c = CKafka(kafka_url='192.168.3.160:9094', secure=True)
24
+
25
+ def send_msg():
26
+ while True:
27
+ time.sleep(3)
28
+ c.send_msg('test', 'test')
29
+
30
+ thread_pool.submit(send_msg)
31
+ c.get_msg('test')
32
+ """
33
+
34
+ class CKafka:
35
+
36
+ def __init__(self, kafka_url: str = '127.0.0.1:9092', secure: bool = False, username: str = 'client', password: str = 'hylink_user_password', consumer_group: str = 'ck-py-kafka-consumer'):
37
+ self.consumer: KafkaConsumer = None
38
+ self.producer: KafkaProducer = None
39
+ self.start_consumer = False
40
+ self.consumer_callback = {"topic_key": []}
41
+ self.consumer_group = consumer_group
42
+ self.kafka_url = kafka_url
43
+ self.init_producer = False
44
+ self.init_consumer = False
45
+ self.secure = secure
46
+ self.username = username
47
+ self.password = password
48
+ self.locker = Lock()
49
+ self.quited = False
50
+
51
+ def _create_producer(self) -> KafkaProducer:
52
+ print("[ Producer ] Connecting to Kafka brokers")
53
+ for i in range(0, 6):
54
+ try:
55
+ if self.secure:
56
+ self.producer = KafkaProducer(
57
+ bootstrap_servers=self.kafka_url,
58
+ sasl_plain_username=self.username,
59
+ sasl_plain_password=self.password,
60
+ security_protocol='SASL_PLAINTEXT',
61
+ sasl_mechanism='PLAIN',
62
+ value_serializer=lambda x: dumps(x).encode('utf-8'))
63
+ else:
64
+ self.producer = KafkaProducer(
65
+ bootstrap_servers=self.kafka_url,
66
+ value_serializer=lambda x: dumps(x).encode('utf-8'))
67
+ print("[ Producer ] Connected to Kafka...")
68
+ self.init_producer = True
69
+ return self.producer
70
+ except errors.NoBrokersAvailable:
71
+ print("[ Producer ] Waiting for brokers to become available...")
72
+ time.sleep(3)
73
+ raise RuntimeError("[ Producer ] Failed to connect to brokers within 60 seconds")
74
+
75
+ def _create_consumer(self) -> KafkaProducer:
76
+ print("[ Consumer ] Connecting to Kafka brokers")
77
+ for i in range(0, 6):
78
+ try:
79
+ if self.secure:
80
+ self.consumer = KafkaConsumer(
81
+ group_id=self.consumer_group,
82
+ bootstrap_servers=self.kafka_url,
83
+ sasl_plain_username=self.username,
84
+ sasl_plain_password=self.password,
85
+ security_protocol='SASL_PLAINTEXT',
86
+ sasl_mechanism='PLAIN',
87
+ value_deserializer=lambda x: x.decode('utf-8'))
88
+ else:
89
+ self.consumer = KafkaProducer(
90
+ bootstrap_servers=self.kafka_url,
91
+ value_deserializer=lambda x: x.decode('utf-8'))
92
+ print("[ Consumer ] Connected to Kafka...")
93
+ self.init_consumer = True
94
+ return self.consumer
95
+ except errors.NoBrokersAvailable:
96
+ print("[ Consumer ] Waiting for brokers to become available...")
97
+ time.sleep(3)
98
+ raise RuntimeError("[ Consumer ] Failed to connect to brokers within 60 seconds")
99
+
100
+ # FutureRecordMetadata 可以添加回调, 来监听是否发送成功
101
+ # r.add_callback(lambda x: print(x))
102
+ # r.get() 可以同步获取结果
103
+ def send_msg(self, topic, msg, key: str=None, partition:int=None) -> FutureRecordMetadata:
104
+ if self.quited: return None
105
+ if not self.init_producer:
106
+ with self.locker:
107
+ if not self.init_producer:
108
+ self._create_producer()
109
+ return self.producer.send(topic=topic, value=msg, key=None if key is None else key.encode('utf-8'), partition=partition)
110
+
111
+ def get_msg(self, topics: str, callBack=print):
112
+ if not self.init_consumer:
113
+ with self.locker:
114
+ if not self.init_consumer:
115
+ self._create_consumer()
116
+ for topic in topics.split(','):
117
+ if topic not in self.consumer_callback.keys():
118
+ self.consumer_callback[topic] = []
119
+ self.consumer.subscribe(self.consumer_callback.keys())
120
+ self.consumer_callback[topic].append(callBack)
121
+ if not self.start_consumer:
122
+ t = Thread(target=self._start_consumer_poll)
123
+ t.start()
124
+
125
+ def _start_consumer_poll(self):
126
+ self.start_consumer = True
127
+ for msg in self.consumer:
128
+ if self.quited: break
129
+ taskList = []
130
+ funcList = []
131
+ begin_time = time.time()
132
+ for func in self.consumer_callback[msg.topic]:
133
+ if self.quited: break
134
+ f = thread_pool.submit(func, (msg, ))
135
+ taskList.append(f)
136
+ funcList.append(func.__name__)
137
+ for f in taskList:
138
+ if self.quited: break
139
+ f.result()
140
+ end_time = time.time()
141
+ if end_time - begin_time > 1: print(f"kafka consume too slow!!! {funcList} time cost: ", f'{round(end_time - begin_time, 2)}s')
142
+ taskList.clear()
143
+ funcList.clear()
144
+
145
+ def shutdown(self):
146
+ self.quited = True
147
+ try: self.consumer.close()
148
+ except Exception: pass
149
+ try: self.producer.close()
150
+ except Exception: pass
151
+ thread_pool.shutdown(wait=True)
152
+
ctools/cron_lite.py CHANGED
@@ -7,10 +7,26 @@ import traceback
7
7
  from datetime import datetime
8
8
  from functools import wraps
9
9
  from typing import Optional, Dict
10
-
11
10
  import pytz
12
11
  from croniter import croniter
13
12
 
13
+ """
14
+ @cron_lite.cron_task('0/1 * * * * ? *')
15
+ def demo():
16
+ print('hello world')
17
+
18
+ @cron_lite.cron_task('0/1 * * * * ? *')
19
+ def demo1():
20
+ print('hello world111')
21
+
22
+ def demo2(xx, fff):
23
+ print('hello world222', xx, fff)
24
+
25
+ cron_lite.apply_cron_task('0/1 * * * * ? *', demo2, (123123123, 34534534))
26
+ print(123123)
27
+
28
+ cron_lite.start_all()
29
+ """
14
30
 
15
31
  class SchedulerMeta:
16
32
  timer_task_name: str = None
@@ -22,8 +38,8 @@ class SchedulerMeta:
22
38
 
23
39
  scheduler_map: Dict[str, SchedulerMeta] = {} # {timer_task_name: SchedulerMeta}
24
40
  _switch = False
41
+ _info_handler = print
25
42
  _error_handler = print
26
- _info_handler = print
27
43
  _time_zone: Optional[pytz.BaseTzInfo] = None
28
44
 
29
45
 
@@ -31,75 +47,8 @@ def set_time_zone(time_zone_name: str):
31
47
  global _time_zone
32
48
  _time_zone = pytz.timezone(time_zone_name)
33
49
 
34
-
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):
50
+ # @annotation
51
+ def cron_task(cron_expr: str, task_name: str = None, till_time_stamp: int = None):
103
52
  """
104
53
  cron_task decorator to register a function as crontab task
105
54
  :param cron_expr: the croniter accepted cron_expression. NOTICE: the default timezone is UTC and can be changed by
@@ -107,7 +56,7 @@ def cron_task(cron_expr: str, till_time_stamp: int = None):
107
56
  :param till_time_stamp: run this jog till when. None means forever
108
57
  :return: the real decorator
109
58
  """
110
- cron_expr = convert_cron(cron_expr)
59
+ cron_expr = _convert_cron(cron_expr)
111
60
  assert len(cron_expr.split(" ")) in (5, 6), \
112
61
  "only supported <min hour day month weekday> and <min hour day month weekday sec>"
113
62
 
@@ -121,15 +70,15 @@ def cron_task(cron_expr: str, till_time_stamp: int = None):
121
70
  _error_handler(f"run {func.__name__} failed\n" + traceback.format_exc())
122
71
  except Exception:
123
72
  _error_handler(f"run {func.__name__} failed\n")
124
- _register_next(inner, inner, cron_expr, till_time_stamp)
73
+ _register_next(inner.__name__ if task_name is None else task_name, inner, cron_expr, till_time_stamp)
125
74
 
126
- _register_next(inner, inner, cron_expr, till_time_stamp)
75
+ _register_next(inner.__name__ if task_name is None else task_name, inner, cron_expr, till_time_stamp, init=True)
127
76
  return inner
128
77
 
129
78
  return deco
130
79
 
131
80
 
132
- def apply_cron_task(timer_task_name, func, params, cron_expr, till_time_stamp=None):
81
+ def apply_cron_task(cron_expr, func, params, timer_task_name=None, till_time_stamp=None):
133
82
  """
134
83
  cron_task decorator to register a function as crontab task
135
84
  :param func: task callback function
@@ -140,37 +89,32 @@ def apply_cron_task(timer_task_name, func, params, cron_expr, till_time_stamp=No
140
89
  :param till_time_stamp: run this jog till when. None means forever
141
90
  :return: the real decorator
142
91
  """
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>"
146
-
92
+ cron_expr = _convert_cron(cron_expr)
93
+ assert len(cron_expr.split(" ")) in (5, 6), "Only supported <minute hour day month weekday> and <minute hour day month weekday second>"
94
+ task_name = func.__name__ if timer_task_name is None else timer_task_name
147
95
  @wraps(func)
148
96
  def wrapper(*args, **kwargs):
149
97
  try:
150
- func(timer_task_name, params, *args, **kwargs)
98
+ nonlocal params
99
+ func.__taskName__ = task_name
100
+ func(*params, *args, **kwargs)
151
101
  except Exception as exc:
152
102
  _error_handler(f"Run {func.__name__} failed with error: {str(exc)}")
153
103
  finally:
154
- _register_next(timer_task_name, wrapper, cron_expr, till_time_stamp)
104
+ _register_next(task_name, wrapper, cron_expr, till_time_stamp)
155
105
 
156
- _register_next(timer_task_name, wrapper, cron_expr, till_time_stamp)
106
+ _register_next(task_name, wrapper, cron_expr, till_time_stamp, init=True)
107
+ # 不使用 submit, 因为提交的任务, 不是 daemon 线程
108
+ t = threading.Thread(target=_start, args=(timer_task_name, ))
109
+ t.setDaemon(True)
110
+ t.start()
111
+ return t
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
-
169
-
170
- def start_all(spawn: bool = True, info_handler=None, error_handler=None) -> Optional[threading.Thread]:
113
+ def start_all(spawn: bool = True, daemon: bool = True, info_handler=None, error_handler=None) -> Optional[threading.Thread]:
171
114
  """
172
115
  start_all starts all cron tasks registered before.
173
116
  :param spawn: whether to start a new thread for scheduler. If not, the action will block the current thread
117
+ :param daemon: the new thread is daemon if True
174
118
  :param info_handler: handle info output (scheduler start / stop), default = print, can use logging.info
175
119
  :param error_handler: handle error output (task execute exception), default = print, can use logging.error
176
120
  :raise RuntimeError: if the tasks are already started and still running we cannot start again. The feature is not
@@ -179,16 +123,14 @@ def start_all(spawn: bool = True, info_handler=None, error_handler=None) -> Opti
179
123
  """
180
124
  global _switch, _info_handler, _error_handler
181
125
  if _switch:
182
- raise RuntimeError("the crontab was already started")
126
+ raise RuntimeError("the crontab was already started...")
183
127
  if info_handler:
184
128
  _info_handler = info_handler
185
129
  if error_handler:
186
130
  _error_handler = error_handler
187
-
188
- _switch = True
189
131
  if spawn:
190
132
  t = threading.Thread(target=_start)
191
- t.setDaemon(True)
133
+ t.setDaemon(daemon)
192
134
  t.start()
193
135
  return t
194
136
  else:
@@ -237,3 +179,73 @@ def stop_all(wait_thread: Optional[threading.Thread] = None):
237
179
  scheduler_map.get(timer_task_name).switch = False
238
180
  if wait_thread:
239
181
  wait_thread.join()
182
+
183
+
184
+ def _register_next(timer_task_name, base_func, cron_expr, till_time_stamp, init: bool = False):
185
+ cron_obj = croniter(cron_expr)
186
+ if _time_zone:
187
+ cron_obj.set_current(datetime.now(tz=_time_zone))
188
+ next_time = int(cron_obj.get_next())
189
+ if scheduler_map.get(timer_task_name) is None:
190
+ scheduler_meta = SchedulerMeta()
191
+ scheduler_meta.timer_task_name = timer_task_name
192
+ scheduler_meta.switch = True
193
+ scheduler_meta.scheduler = sched.scheduler(time.time, time.sleep)
194
+ scheduler_map[timer_task_name] = scheduler_meta
195
+ elif init:
196
+ raise ValueError(f"task name: {timer_task_name} already exists!!!!!")
197
+ if till_time_stamp is None or next_time <= till_time_stamp:
198
+ scheduler_map[timer_task_name].event = scheduler_map[timer_task_name].scheduler.enterabs(next_time, 0, base_func)
199
+
200
+
201
+ def _run_sched(scheduler_meta: SchedulerMeta):
202
+ active(scheduler_meta.timer_task_name)
203
+ while True:
204
+ scheduler = scheduler_meta.scheduler
205
+ if not _switch or not scheduler_meta.switch:
206
+ scheduler.empty()
207
+ inactive(scheduler_meta.timer_task_name)
208
+ return
209
+ t = scheduler.run(False)
210
+ if t is None:
211
+ inactive(scheduler_meta.timer_task_name)
212
+ return
213
+ st = time.time()
214
+ while time.time() - st < t:
215
+ if not _switch or not scheduler_meta.switch:
216
+ scheduler.empty()
217
+ inactive(scheduler_meta.timer_task_name)
218
+ return
219
+ time.sleep(0.5)
220
+
221
+
222
+ def _start(taskName: str = None):
223
+ global _switch
224
+ _switch = True
225
+ _info_handler("cron job begin start...")
226
+ taskList = []
227
+ for timer_task_name, scheduler_meta in scheduler_map.items():
228
+ if taskName is not None and timer_task_name != taskName: continue
229
+ print("register job: ", timer_task_name)
230
+ thread = threading.Thread(target=_run_sched, args=(scheduler_meta, ))
231
+ thread.setDaemon(True)
232
+ thread.start()
233
+ taskList.append(thread)
234
+ for task in taskList: task.join()
235
+ _info_handler("cron job execute finished...")
236
+ _switch = False
237
+ scheduler_map.clear()
238
+
239
+
240
+ def _convert_cron(cron_expr):
241
+ res_cron = ""
242
+ cron_list = cron_expr.split(" ")
243
+ if len(cron_list) > 6:
244
+ for cron in cron_list[1:]:
245
+ if cron != "?":
246
+ res_cron += "%s " % cron
247
+ res_cron += "%s" % cron_list[0]
248
+ else:
249
+ res_cron = cron_expr
250
+ return res_cron
251
+
ctools/database.py CHANGED
@@ -8,6 +8,13 @@ from sqlalchemy.sql import text
8
8
  from ctools import call, string_tools
9
9
  from ctools.thread_pool import thread_local
10
10
 
11
+ """
12
+ database.init_db('postgresql://postgres:123456@192.168.3.107:32566/abc', default_schema='public', db_key='source', pool_size=100)
13
+ with database.get_session('source') as s:
14
+ s.execute(text('insert into xxx (name) values (:name)'), {'name': string_tools.get_random_str(5)})
15
+ s.commit()
16
+ """
17
+
11
18
  Base = None
12
19
  inited_db = {}
13
20
  engines = {}
@@ -21,11 +28,8 @@ def _init():
21
28
  global Base
22
29
  Base = declarative_base()
23
30
 
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}')
27
-
28
31
  # 密码里的@ 要替换成 %40
32
+ # sqlite connect_args={"check_same_thread": False} sqlite:///{}.format(db_url)
29
33
  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):
30
34
  if inited_db.get(db_key): raise Exception('db {} already init!!!'.format(db_key))
31
35
  global engines, sessionMakers
@@ -33,7 +37,11 @@ def init_db(db_url: str, db_key: str='default', connect_args: dict={}, default_s
33
37
  engines[db_key] = engine
34
38
  sessionMakers[db_key] = sessionMaker
35
39
  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))
40
+ if default_schema: event.listen(engine, 'connect', lambda dbapi_connection, connection_record: _set_search_path(dbapi_connection, default_schema))
41
+
42
+ def _set_search_path(dbapi_connection, default_schema):
43
+ with dbapi_connection.cursor() as cursor:
44
+ cursor.execute(f'SET search_path TO {default_schema}')
37
45
 
38
46
  def _create_connection(db_url: str, pool_size: int=5, max_overflow: int=25, connect_args={}, echo: bool=False):
39
47
  engine = create_engine('{}'.format(db_url),
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2024/9/19 14:02'
5
+
6
+ import math
7
+ from ctools import cjson
8
+ from ctools.sys_log import flog as log
9
+ from jsonpath_ng import parser
10
+
11
+ class THIN_LEVEL:
12
+ L1 = 0.00001
13
+ L2 = 0.00003
14
+ L3 = 0.00009
15
+ L4 = 0.0002
16
+ L5 = 0.0004
17
+ L6 = 0.0007
18
+ L7 = 0.0011
19
+ L8 = 0.0017
20
+ L9 = 0.0022
21
+
22
+ class Point:
23
+ def __init__(self, lng, lat, origin_data):
24
+ self.lng = lng
25
+ self.lat = lat
26
+ self.origin_data = origin_data
27
+
28
+ def _get_line_by_point(xy1, xy2):
29
+ """
30
+ 根据两个点求直线方程 ax + by + c = 0
31
+ :param xy1: 点1, 例如 {'lat': 1, 'lng': 1}
32
+ :param xy2: 点2, 例如 {'lat': 2, 'lng': 2}
33
+ :return: 直线方程的三个参数 [a, b, c]
34
+ """
35
+ x1 = xy1.lng
36
+ y1 = xy1.lat
37
+ x2 = xy2.lng
38
+ y2 = xy2.lat
39
+ a = y2 - y1
40
+ b = x1 - x2
41
+ c = (y1 - y2) * x1 - y1 * (x1 - x2)
42
+ return [a, b, c]
43
+
44
+
45
+ def _get_distance_from_point_to_line(a, b, c, xy):
46
+ """
47
+ 点到直线的距离,直线方程为 ax + by + c = 0
48
+ :param a: 直线参数a
49
+ :param b: 直线参数b
50
+ :param c: 直线参数c
51
+ :param xy: 点坐标,例如 {'lat': 2, 'lng': 2}
52
+ :return: 距离
53
+ """
54
+ x = xy.lng
55
+ y = xy.lat
56
+ return abs((a * x + b * y + c) / math.sqrt(a * a + b * b))
57
+
58
+
59
+ class DouglasRarefy:
60
+ """
61
+ DouglasRarefy Use Guide:
62
+ points must be arrays, element can be dict or arrays, when element is arrays, index 0 must be lng, index 1 must be lat, and element can be use max column num is 2 (lng, lat) in ret_tpl
63
+ level default is L2, this level can be hold most of the points detail
64
+ ret_tpl is the result tpl, can be arrays and json or some can be json loads, exp: [{lng}, {lat}] OR {{"lng": {lng}, "lat": {lat}}}
65
+ """
66
+ def __init__(self, points:[], level=THIN_LEVEL.L2, ret_tpl=None, get_lng=None, get_lat=None):
67
+ if not isinstance(points, list): raise Exception('points must be list obj !!')
68
+ if len(points) < 3: raise Exception('points length must be gt 2 !!')
69
+ self.points = points
70
+ self.threshold = THIN_LEVEL.L2 if level is None else (getattr(THIN_LEVEL, "L{}".format(int(level))) if int(level) >= 1 else level)
71
+ log.debug("threshold is: {}".format(self.threshold))
72
+ self.is_json = isinstance(points[0], dict)
73
+ self.get_lng = get_lng
74
+ self.get_lat = get_lat
75
+ if self.is_json:
76
+ if not self.get_lng: self.get_lng = '$.lng'
77
+ if not self.get_lat: self.get_lat = '$.lat'
78
+ else:
79
+ if not self.get_lng: self.get_lng = '$.[0]'
80
+ if not self.get_lat: self.get_lat = '$.[1]'
81
+ log.debug("get_lng is: {}, get_lat is: {}".format(self.get_lng, self.get_lat))
82
+ self.lng_parser = parser.parse(self.get_lng)
83
+ self.lat_parser = parser.parse(self.get_lat)
84
+ log.debug("is_json is: {}".format(self.is_json))
85
+ self.ret_tpl = ret_tpl
86
+ log.debug("ret_tpl is: {}".format(self.ret_tpl))
87
+ self.data = [Point(self.lng_parser.find(p)[0].value, self.lat_parser.find(p)[0].value, p) for p in self.points]
88
+
89
+ def _sparse_points(self, points):
90
+ """
91
+ 点位压缩
92
+ :return: 稀疏后的点集
93
+ """
94
+ if len(points) < 3:
95
+ if not self.ret_tpl:
96
+ return [points[0].origin_data, points[-1].origin_data]
97
+ else:
98
+ if self.is_json:
99
+ return [cjson.loads(self.ret_tpl.format(**points[0].origin_data)), cjson.loads(self.ret_tpl.format(**points[-1].origin_data))]
100
+ else:
101
+ return [cjson.loads(self.ret_tpl.format(lng=points[0].lng, lat=points[0].lat)), cjson.loads(self.ret_tpl.format(lng=points[-1].lng, lat=points[-1].lat))]
102
+
103
+ xy_first = points[0] # 第一个点
104
+ xy_end = points[-1] # 最后一个点
105
+ a, b, c = _get_line_by_point(xy_first, xy_end) # 获取直线方程的 a, b, c 值
106
+ d_max = 0 # 记录点到直线的最大距离
107
+ split = 0 # 分割位置
108
+ for i in range(1, len(points) - 1):
109
+ d = _get_distance_from_point_to_line(a, b, c, points[i])
110
+ if d > d_max:
111
+ split = i
112
+ d_max = d
113
+ if d_max > self.threshold:
114
+ # 如果存在点到首位点连成直线的距离大于 max_distance 的, 即需要再次划分
115
+ child_left = self._sparse_points(points[:split + 1]) # 递归处理左边部分
116
+ child_right = self._sparse_points(points[split:]) # 递归处理右边部分
117
+ # 合并结果,避免重复
118
+ return child_left + child_right[1:]
119
+ else:
120
+ if not self.ret_tpl:
121
+ return [points[0].origin_data, points[-1].origin_data]
122
+ else:
123
+ if self.is_json:
124
+ return [cjson.loads(self.ret_tpl.format(**points[0].origin_data)), cjson.loads(self.ret_tpl.format(**points[-1].origin_data))]
125
+ else:
126
+ return [cjson.loads(self.ret_tpl.format(lng=points[0].lng, lat=points[0].lat)), cjson.loads(self.ret_tpl.format(lng=points[-1].lng, lat=points[-1].lat))]
127
+
128
+ def sparse_points(self):
129
+ return self._sparse_points(self.data)
130
+
ctools/ex.py CHANGED
@@ -2,7 +2,7 @@ import time
2
2
  import traceback
3
3
  from functools import wraps
4
4
 
5
-
5
+ # annotation
6
6
  def exception_handler(fail_return, retry_num=0, delay=3, catch_e=Exception, print_exc=False):
7
7
  def decorator(func):
8
8
  @wraps(func)
ctools/http_utils.py CHANGED
@@ -4,7 +4,7 @@ import requests
4
4
  def get(url, params=None, headers=None):
5
5
  result = ""
6
6
  try:
7
- response = requests.get(url, params=params, headers=headers, timeout=5)
7
+ response = requests.get(url, params=params, headers=headers, timeout=60)
8
8
  response.raise_for_status()
9
9
  if response.status_code == 200:
10
10
  result = response.content
@@ -15,7 +15,7 @@ def get(url, params=None, headers=None):
15
15
 
16
16
  def post(url, data=None, json=None, headers=None, files=None):
17
17
  result = ""
18
- response = requests.post(url, data=data, json=json, files=files, headers=headers, timeout=5)
18
+ response = requests.post(url, data=data, json=json, files=files, headers=headers, timeout=60)
19
19
  response.raise_for_status()
20
20
  if response.status_code == 200:
21
21
  result = response.content
ctools/metrics.py CHANGED
@@ -4,7 +4,6 @@ from enum import Enum
4
4
 
5
5
  from prometheus_client import Counter, Gauge, Summary, Histogram
6
6
 
7
- from business.common.constant import MetricKey
8
7
  from ctools import call, cjson, sys_log
9
8
  from ctools.application import Server
10
9
 
@@ -123,23 +122,5 @@ def opt(metric_key: str, label_values: [], metric_value: int):
123
122
  log.error("添加指标信息异常: %s" % e)
124
123
  _lock.release()
125
124
 
126
-
127
125
  def _init_all_metrics():
128
126
  Metric(MetricType.COUNTER, 'demo123123', ['asdasd', 'sdfsdf'], persistent=True)
129
- Metric(MetricType.GAUGE, MetricKey.ASSETS_TPL_COUNT.value, [], persistent=True, desc="模版数量")
130
- Metric(MetricType.GAUGE, MetricKey.ASSETS_FLOW_COUNT.value, [], persistent=True, desc="流程数量")
131
- Metric(MetricType.GAUGE, MetricKey.ASSETS_SCHEDULE_COUNT.value, [], persistent=True, desc="调度任务数量")
132
-
133
- Metric(MetricType.COUNTER, MetricKey.CLIENT_STATUS_RUNNING_STATE.value, [], reset=True, desc="运行状态")
134
- Metric(MetricType.COUNTER, MetricKey.CLIENT_STATUS_AUTHORIZED_DAYS.value, [], reset=True, desc="已授权天数")
135
- Metric(MetricType.COUNTER, MetricKey.CLIENT_STATUS_PROGRESS.value, ['process_id'], reset=True, desc="流程运行进度[0-100]")
136
-
137
- Metric(MetricType.COUNTER, MetricKey.WORKLOAD_PROCESS_EXEC_COUNT.value, ['process_id'], persistent=True, desc="流程执行次数")
138
- Metric(MetricType.COUNTER, MetricKey.WORKLOAD_PROCESS_HOURS.value, ['process_id'], persistent=True, desc="全部流程工作时长")
139
- Metric(MetricType.COUNTER, MetricKey.WORKLOAD_PROCESS_HOURS_SINGLE.value, ['process_id'], reset=True, desc="单次流程工作时长")
140
- Metric(MetricType.COUNTER, MetricKey.WORKLOAD_PROCESS_COLLECT_DATA_TOTAL.value, ['process_id'], persistent=True, desc="全部流程采集数据总量")
141
- Metric(MetricType.COUNTER, MetricKey.WORKLOAD_PROCESS_COLLECT_DATA_TOTAL_SINGLE.value, ['process_id'], reset=True, desc="单次流程采集数据总量")
142
- Metric(MetricType.COUNTER, MetricKey.WORKLOAD_PROCESS_SEND_DATA_TOTAL.value, ['process_id'], persistent=True, desc="全部流程发送数据总量")
143
- Metric(MetricType.COUNTER, MetricKey.WORKLOAD_PROCESS_SEND_DATA_TOTAL_SINGLE.value, ['process_id'], reset=True, desc="单次流程发送数据总量")
144
-
145
- Metric(MetricType.COUNTER, MetricKey.ERROR_SCHEDULE_COUNT.value, ['process_id'], persistent=True, desc="出错任务数")
ctools/pacth.py CHANGED
@@ -3,7 +3,7 @@ import shutil
3
3
 
4
4
  from sqlalchemy.sql import text
5
5
 
6
- from business.persistent import database
6
+ from ctools import database
7
7
 
8
8
  class Patch:
9
9
 
ctools/process_pool.py ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2024/9/20 12:00'
5
+ import os
6
+ import time
7
+ from concurrent.futures import ProcessPoolExecutor
8
+ from ctools import call
9
+
10
+ _process_pool: ProcessPoolExecutor = None
11
+
12
+ @call.init
13
+ def init():
14
+ global _process_pool
15
+ max_workers = min(32, os.cpu_count()) # 最多 32 个
16
+ _process_pool = ProcessPoolExecutor(max_workers=max_workers)
17
+
18
+ def cb(f, callback):
19
+ exc = f.exception()
20
+ if exc:
21
+ if callback: callback(exc)
22
+ raise exc
23
+ else:
24
+ if callback: callback(f.result())
25
+
26
+ def submit(func, *args, callback=None, **kwargs):
27
+ if _process_pool is None: raise Exception('process pool is not init')
28
+ future = _process_pool.submit(func, *args, **kwargs)
29
+ future.add_done_callback(lambda f: cb(f, callback))
30
+ time.sleep(0.01)
31
+ return future
32
+
33
+ def shutdown(wait=True):
34
+ if _process_pool is None: raise Exception('process pool is not init')
35
+ _process_pool.shutdown(wait=wait)
ctools/sys_log.py CHANGED
@@ -2,21 +2,15 @@ import logging
2
2
  import os
3
3
  import sys
4
4
  import time
5
+ import traceback
5
6
 
6
- from ctools import application, call
7
+ from ctools import call, work_path
7
8
 
8
- application.sync_config()
9
-
10
- clog, flog = None, None
9
+ clog: logging.Logger = None
10
+ flog: logging.Logger = None
11
11
 
12
12
  neglect_keywords = [
13
13
  "OPTIONS",
14
- '/scheduleInfo/list',
15
- '/sys/get_sys_state',
16
- '/scheduleInfo/tmpList',
17
- '/sys/getSysLog',
18
- '/downloadManage/static_file',
19
- '/downloadManage/static_images'
20
14
  ]
21
15
 
22
16
 
@@ -27,7 +21,7 @@ def _file_log(sys_log_path: str = './', log_level: int = logging.INFO, mixin: bo
27
21
  os.mkdir(sys_log_path)
28
22
  except Exception:
29
23
  pass
30
- log_file = sys_log_path + os.path.sep + "log-" + time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(time.time())) + ".log"
24
+ log_file = sys_log_path + os.path.sep + "log-" + time.strftime("%Y-%m-%d-%H", time.localtime(time.time())) + ".log"
31
25
  if mixin:
32
26
  handlers = [logging.FileHandler(filename=log_file, encoding='utf-8'), logging.StreamHandler()]
33
27
  else:
@@ -56,14 +50,28 @@ class GlobalLogger(object):
56
50
  def __init__(self, logger):
57
51
  sys.stdout = self
58
52
  sys.stderr = self
53
+ sys.excepthook = self.handle_exception
59
54
  self.log = logger
60
55
 
61
56
  def write(self, message):
62
- if message == '\n': return
57
+ if message == '\n' or message == '\r\n': return
63
58
  global neglect_keywords
64
59
  for neglect_keyword in neglect_keywords:
65
60
  if neglect_keyword in message: return
66
- self.log.info(message)
61
+ try:
62
+ stack = traceback.extract_stack(limit=3)
63
+ caller = stack[-2]
64
+ location = f"{os.path.splitext(os.path.basename(caller.filename))[0]}({caller.name}:{caller.lineno})"
65
+ self.log.info(f"{location} {message.strip()}")
66
+ except Exception:
67
+ self.log.info(message.strip())
68
+
69
+ def handle_exception(self, exc_type, exc_value, exc_traceback):
70
+ if issubclass(exc_type, KeyboardInterrupt):
71
+ sys.__excepthook__(exc_type, exc_value, exc_traceback)
72
+ return
73
+ formatted_exception = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))
74
+ self.log.error(f"An error occurred:\n{formatted_exception.strip()}")
67
75
 
68
76
  def flush(self):
69
77
  pass
@@ -72,6 +80,10 @@ class GlobalLogger(object):
72
80
  @call.init
73
81
  def _init_log() -> None:
74
82
  global flog, clog
75
- flog = _file_log(sys_log_path=application.Server.sysLogPath, mixin=True, log_level=logging.INFO if application.Authorization.enabled else logging.DEBUG)
83
+ flog = _file_log(sys_log_path='{}/ck-py-log/'.format(work_path.get_user_work_path()), mixin=True, log_level=logging.DEBUG)
76
84
  clog = _console_log()
77
85
  GlobalLogger(flog)
86
+
87
+ def setLevel(log_level=logging.INFO):
88
+ flog.setLevel(log_level)
89
+ clog.setLevel(log_level)
ctools/thread_pool.py CHANGED
@@ -28,3 +28,7 @@ def submit(func, *args, callback=None, **kwargs):
28
28
  future.add_done_callback(lambda f: cb(f, callback))
29
29
  time.sleep(0.01)
30
30
  return future
31
+
32
+ def shutdown(wait=True):
33
+ if _threadPool is None: raise Exception('thread pool is not init')
34
+ _threadPool.shutdown(wait=wait)
ctools/web_base.py ADDED
@@ -0,0 +1,143 @@
1
+ import threading
2
+ from functools import wraps
3
+
4
+ import bottle
5
+ from bottle import response, Bottle, abort, request
6
+ from ctools.api_result import R
7
+ from ctools.sys_log import flog as log
8
+
9
+ bottle.BaseRequest.MEMFILE_MAX = 1024 * 1024 * 50
10
+
11
+ class GlobalState:
12
+ lock = threading.Lock()
13
+ withOutLoginURI = [
14
+ '/login',
15
+ '/get_sys_version'
16
+ ]
17
+ allowRemoteCallURI = [
18
+
19
+ ]
20
+ token = {}
21
+
22
+ def init_app(context_path):
23
+ app = Bottle()
24
+ app.context_path = context_path
25
+
26
+ @app.hook('before_request')
27
+ def before_request():
28
+ # abort(401, '系统时间被篡改, 请重新校时!')
29
+ pass
30
+
31
+ @app.error(401)
32
+ def unauthorized(error):
33
+ after_request()
34
+ response.status = 200
35
+ log.error("系统未授权: {} {} {}".format(error.body, request.method, request.fullpath))
36
+ return R.error(resp=R.Code.cus_code(9999, "系统未授权! {}".format(error.body)))
37
+
38
+ @app.error(403)
39
+ def unauthorized(error):
40
+ after_request()
41
+ response.status = 200
42
+ log.error("访问受限: {} {} {}".format(error.body, request.method, request.fullpath))
43
+ return R.error(resp=R.Code.cus_code(8888, "访问受限: {}".format(error.body)))
44
+
45
+ @app.error(405)
46
+ def cors_error(error):
47
+ if request.method == 'OPTIONS':
48
+ after_request()
49
+ response.status = 200
50
+ return
51
+ log.error("请求方法错误: {} {} {}".format(error.status_line, request.method, request.fullpath))
52
+ return R.error(msg='请求方法错误: {}'.format(error.status_line))
53
+
54
+ @app.error(500)
55
+ def cors_error(error):
56
+ after_request()
57
+ response.status = 200
58
+ log.error("系统发生错误: {} {} {}".format(error.body, request.method, request.fullpath))
59
+ return R.error(msg='系统发生错误: {}'.format(error.exception))
60
+
61
+ @app.hook('after_request')
62
+ def after_request():
63
+ enable_cors()
64
+ return app
65
+
66
+ # annotation
67
+ def rule(key):
68
+ def return_func(func):
69
+ @wraps(func)
70
+ def decorated(*args, **kwargs):
71
+ # if GlobalState.licenseInfo is not None and key not in GlobalState.licenseInfo.access_module:
72
+ # log.error("系统未授权! {} {}".format(request.fullpath, '当前请求的模块未授权!请联系管理员!'))
73
+ # return R.error(resp=R.Code.cus_code(9999, "系统未授权! {}".format('当前请求的模块未授权!请联系管理员!')))
74
+ return func(*args, **kwargs)
75
+ return decorated
76
+ return return_func
77
+
78
+
79
+ def enable_cors():
80
+ response.headers['Access-Control-Allow-Origin'] = '*'
81
+ response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
82
+ request_headers = request.headers.get('Access-Control-Request-Headers')
83
+ response.headers['Access-Control-Allow-Headers'] = request_headers if request_headers else ''
84
+ response.headers['Access-Control-Expose-Headers'] = '*'
85
+
86
+ # annotation
87
+ def params_resolve(func):
88
+ """
89
+ Use guide:
90
+ @params_resolve
91
+ def xxx(params):
92
+ print(params.a)
93
+ print(params.b)
94
+ """
95
+ @wraps(func)
96
+ def decorated(*args, **kwargs):
97
+ if request.method == 'GET':
98
+ queryStr = request.query.decode('utf-8')
99
+ pageInfo = PageInfo(
100
+ page_size=10 if request.headers.get('page_size') is None else int(request.headers.get('page_size')),
101
+ page_index=1 if request.headers.get('page_index') is None else int(request.headers.get('page_index'))
102
+ )
103
+ queryStr.pageInfo = pageInfo
104
+ return func(params=queryStr, *args, **kwargs)
105
+ elif request.method == 'POST':
106
+ content_type = request.get_header('content-type')
107
+ if content_type == 'application/json':
108
+ params = request.json
109
+ return func(params=DictWrapper(params), *args, **kwargs)
110
+ elif content_type and 'multipart/form-data' in content_type:
111
+ form_data = request.forms.decode()
112
+ form_files = request.files.decode()
113
+ params = FormDataParams(data=DictWrapper(form_data), files=form_files)
114
+ return func(params=params, *args, **kwargs)
115
+ else:
116
+ params = request.query.decode('utf-8')
117
+ return func(params=params, *args, **kwargs)
118
+ else:
119
+ return func(*args, **kwargs)
120
+ return decorated
121
+
122
+ class PageInfo:
123
+ def __init__(self, page_size, page_index):
124
+ self.page_size = page_size
125
+ self.page_index = page_index
126
+
127
+ class FormDataParams:
128
+ def __init__(self, data, files):
129
+ self.data = data
130
+ self.files = files
131
+
132
+ def __getattr__(self, key):
133
+ try:
134
+ return self.data[key]
135
+ except Exception:
136
+ return self.files[key]
137
+
138
+ class DictWrapper(dict):
139
+ def __getattr__(self, key):
140
+ return self.get(key)
141
+
142
+ def __setattr__(self, key, value):
143
+ self[key] = value
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: gomyck-tools
3
- Version: 1.0.0
4
- Summary: A ctools for python development
3
+ Version: 1.0.2
4
+ Summary: A ctools for python development by hao474798383
5
5
  Home-page: https://blog.gomyck.com
6
6
  Author: gomyck
7
7
  Author-email: hao474798383@163.com
@@ -10,11 +10,13 @@ Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.8
12
12
  Description-Content-Type: text/markdown
13
- Requires-Dist: jieba ==0.42.1
14
- Requires-Dist: jsonpickle ==3.2.2
15
- Requires-Dist: SQLAlchemy ==2.0.32
16
- Requires-Dist: paddlepaddle ==2.6.1
17
- Requires-Dist: chardet ==5.2.0
18
- Requires-Dist: psycopg2-binary ==2.9.9
13
+ Requires-Dist: jsonpickle >=3.2.2
14
+ Requires-Dist: SQLAlchemy >=2.0.32
15
+ Requires-Dist: chardet >=5.2.0
16
+ Requires-Dist: psycopg2-binary >=2.9.9
17
+ Requires-Dist: croniter >=3.0.3
18
+ Requires-Dist: pyautogui >=0.9.54
19
+ Requires-Dist: gmssl >=3.2.2
20
+ Requires-Dist: psutil >=6.0.0
19
21
 
20
22
  this package is for python development
@@ -1,34 +1,36 @@
1
1
  ctools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  ctools/aes_tools.py,sha256=ylUgyhlx7bNCTGrbWEZVwCObzYdJTQtwEc4ZMidL2Fo,563
3
+ ctools/api_result.py,sha256=NiM-R9K42G3m16B27sG_mqKrlexZzC5-LKoY8D5tO4s,1239
3
4
  ctools/application.py,sha256=WviU7p9GOqducbGW3XGkP7jCNKmraCh6JGSYBC33CQk,16008
4
5
  ctools/b64.py,sha256=_BdhX3p3-MaSSlU2wivN5qPxQfacR3VRBr1WC456tU0,194
5
6
  ctools/bashPath.py,sha256=BCN_EhYzqvwsxYso81omMNd3SbEociwSOyb9kLvu8V4,337
6
- ctools/bottle_server.py,sha256=1FngczUlovRpS8HGx7_q2-jrnGmS0g2UUxbILaMmsR8,1482
7
+ ctools/bottle_server.py,sha256=9gmw1og_8ZaOfmF8X9pwDlRxxeo648QmFvLl0shV0Dk,2303
7
8
  ctools/browser_element_tools.py,sha256=tWNxUJ9m-hNTYtS0NRvmH9r5I-Qw55uKekwDYgt49bc,9951
8
- ctools/call.py,sha256=v1QGKo8UeWDs1zASJSHUUuFkYZ5zb8Pzd0vEYzAi4d8,1545
9
- ctools/cjson.py,sha256=n7Q3k5-1F30uIKRsRoizXdDTsIC-wAyt338B_FzktdE,1236
9
+ ctools/call.py,sha256=BCr8wzt5qd70okv8IZn-9-EpjywleZgvA3u1vfZ_Kt8,1581
10
+ ctools/cjson.py,sha256=hFw-Ue3sUxHd_RAA5Yp1vIyf79KnlxiISOSHCKQQnF8,1263
11
+ ctools/ckafka.py,sha256=win6iV9-7FJDzcmqwQ5httN_NWjrBlWHc5b7N9E5tCQ,5187
10
12
  ctools/compile_tools.py,sha256=Nybh3vnkurIKnPnubdYzigjnzFu4GaTMKPvqFdibxmE,510
11
13
  ctools/console.py,sha256=1VAq_-fSGgHKYv6Qe53kHaJL0-3NpFRUZljahbJPcfk,1807
12
- ctools/cron_lite.py,sha256=ehayjzS9HHNc9ub9BY4aBJglXS01PJCGYvOrQyr3X8Y,7713
13
- ctools/database.py,sha256=33MWjA536g3JltpQ_Xi8QfLjwiVWpe_prfvqEa1ZEMc,4983
14
+ ctools/cron_lite.py,sha256=L3Le_ScIuKiLAfzdaCf8qoPUbFIMT9ASallQkmwJTfc,8307
15
+ ctools/database.py,sha256=xUiJbRRgHkGqwTzFUhAguFfDkW1dVfG2HVPFPzNpkmk,5360
14
16
  ctools/date_utils.py,sha256=-xI2anEzAonOvYwVmM1hCnkuLKodZ8pb33dS3dRxEIc,865
17
+ ctools/douglas_rarefy.py,sha256=2zjUa5nLkStsAsttVqe3PiJWpk108pw1m3CIRNm-Z94,4820
15
18
  ctools/download_tools.py,sha256=h12HGr2IaVvl8IDAvpDftHycSACI5V7HF0Yk9kV4bVY,1750
16
19
  ctools/enums.py,sha256=QbHa3j7j4-BDdwaga5Y0nYfA2uNSVJDHumYdIZdKVkM,118
17
- ctools/ex.py,sha256=vIkSWzHuNFslDFeBZCzCmbOefgLTEeScJp0vKeNm_P8,783
20
+ ctools/ex.py,sha256=_UtbmDLrC7uZsoBtTdecuCZAlf2DA7fvojUf5fGZDVo,795
18
21
  ctools/excelOpt.py,sha256=q3HLAb1JScTrMCvx_x-4WWnqKhyTEzQ-m5vtqFy8NZU,1138
19
22
  ctools/html_soup.py,sha256=LabCo4yWI58fbFBPhunk3THWBf0BbHEWLgwyvSpTGR4,1903
20
- ctools/http_utils.py,sha256=5tW_MV8JUfA4hMLh4cup0kyBsiiTK1l6sgdHVdc8p5g,614
23
+ ctools/http_utils.py,sha256=4dt7OTRZluB9wCK4HL0TBqVZrV3cj_AIs4-9mGDC1zA,616
21
24
  ctools/id_worker_tools.py,sha256=xtfxpL8q4hHLT02JFx2jVXEXpasHq44ZFsOhO580JmE,2357
22
25
  ctools/images_tools.py,sha256=hjYu-3tpjZ96yMqAM3XrFSUEOiUcyGk4DbAsz2_OeIs,669
23
26
  ctools/imgDialog.py,sha256=DSq5cFofyP_el80AFConht_ZYRqxZhIz4CWjqBlJ0cw,1385
24
27
  ctools/license.py,sha256=0Kh7lXL7LD1PQqyijHajFL0GbmZGNB88PA2WskbQn_s,1066
25
- ctools/log.py,sha256=kLlT67by9kTV6UlFXuYPg5AkNnqFRZknoUqLGGIKj14,1097
26
- ctools/metrics.py,sha256=wHRw1qBFqjDdRXWQQgIJdXUKQgocRlIoTsjNDA9t0SU,6288
28
+ ctools/metrics.py,sha256=vq9Fnq2fhmhS4yoHS4gO7ArKS033Eou8vpA779Uue0I,4414
27
29
  ctools/mqtt_utils.py,sha256=9q_5CGvEhhvy8fZxaioguPSz8mvkdpCxnZ86GxjwTE0,10676
28
- ctools/mvc.py,sha256=MgOaM7Jg_tUwVoBSzTgr12E3bX4-IQnIqBwDC-1BO08,1792
29
30
  ctools/obj.py,sha256=GYS1B8NyjtUIh0HlK9r8avC2eGbK2SJac4C1CGnfGhI,479
30
- ctools/pacth.py,sha256=D2bTpYZ05MvLJ9aPwsRPo3ze2jV-siKQ6s0zDUCg82E,2616
31
+ ctools/pacth.py,sha256=4TNWbgNqFSY_BBXKF4KzKauxL8W937wDkoqt84rU-Qg,2603
31
32
  ctools/plan_area_tools.py,sha256=yyFWIAvVe_p1e8HugR_h7r_W7dW9dQ-CGtgMz_Hz5QQ,3396
33
+ ctools/process_pool.py,sha256=LTXU8pxEhnf8dDD23z0OoMlru5z-hKr8EFNH3xYDvCs,950
32
34
  ctools/pty_tools.py,sha256=H-j2xlZ5A0Q75NMop1ghCs2C0BZPKOPjilRujnh5Ngg,1602
33
35
  ctools/resource_bundle_tools.py,sha256=8zW1-aj6jAYFBCoyslz5bL-5916G6Aif1RUy_ObbiVU,3793
34
36
  ctools/screenshot_tools.py,sha256=-DzWgguxTOTolzQXdZfUIHTOU3HNhp74fdomeSTBxKs,4538
@@ -38,17 +40,17 @@ ctools/ssh.py,sha256=3Oo4Lf6UyX-F_u_c1icHHDNzvVDHMKiWIO1u7E22QkM,217
38
40
  ctools/strDiff.py,sha256=QUtXOfsRLTFozH_zByqsC39JeuG3eZtrwGVeLyaHYUI,429
39
41
  ctools/string_tools.py,sha256=qjFn47lM27ISAa2Q4OKO7WOBdhyWC5lLHbME-uSeICE,2142
40
42
  ctools/sys_info.py,sha256=podW43pcn7z2f6FW2w1L9bU1HfeVjC3D34BdN_H6rf4,3625
41
- ctools/sys_log.py,sha256=S-H8z3Gf2lZPu8KL-N6h5Y-bGGcg3BIVEiCDk1DV9eM,2192
42
- ctools/thread_pool.py,sha256=1u7zOfXqrn426rC3knfk8TE1aDutDhvmsukP9NlGg68,795
43
- ctools/token_tools.py,sha256=yFMAtdoGWAPQexqilX6mLER7h2_OxzIdcUL-LqZ-56o,316
43
+ ctools/sys_log.py,sha256=oqb1S41LosdeZxtogFVgDk8R4sjiHhUeYJLCzHd728E,2805
44
+ ctools/thread_pool.py,sha256=pPtKCE1BjagbhqtWjKo5HA2LktcSQ5KQUJz9Fn2Jl0w,924
44
45
  ctools/upload_tools.py,sha256=sqe6K3ZWiyY58pFE5IO5mNaS1znnS7U4c4UqY8noED4,1068
46
+ ctools/web_base.py,sha256=lrEANgUluyxGu2nCxuk94HMuS2-VFpLXsyw870PxB6g,4545
45
47
  ctools/win_canvas.py,sha256=PAxI4i1jalfree9d1YG4damjc2EzaHZrgHZCTgk2GiM,2530
46
48
  ctools/win_control.py,sha256=zNu06wNCtOnfIg12LIMMy2hBb1rskVQ8QIZu9qx4CNY,3487
47
49
  ctools/wordFill.py,sha256=dB1OLt6GLmWdkDV8H20VWbJmY4ggNNI8iHD1ocae2iM,875
48
50
  ctools/word_fill.py,sha256=aIkzjAF2soSW6w2dO2CRZlveDcuIdr6v9DtyyyB8uxM,18216
49
51
  ctools/word_fill_entity.py,sha256=eX3G0Gy16hfGpavQSEkCIoKDdTnNgRRJrFvKliETZK8,985
50
52
  ctools/work_path.py,sha256=i4MTUobqNW2WMrT3mwEC_XYQ0_IhFmKoNpTX2W6A8Tc,1680
51
- gomyck_tools-1.0.0.dist-info/METADATA,sha256=QMUSjOazj7W24mtjuwh79X11mgZ1ALWQy2r2d_TQrg4,636
52
- gomyck_tools-1.0.0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
53
- gomyck_tools-1.0.0.dist-info/top_level.txt,sha256=-MiIH9FYRVKp1i5_SVRkaI-71WmF1sZSRrNWFU9ls3s,7
54
- gomyck_tools-1.0.0.dist-info/RECORD,,
53
+ gomyck_tools-1.0.2.dist-info/METADATA,sha256=di1uIqHBSRqYIzKZFrQZ_FJsp_BJ3Yi26POCoxFwDqk,711
54
+ gomyck_tools-1.0.2.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
55
+ gomyck_tools-1.0.2.dist-info/top_level.txt,sha256=-MiIH9FYRVKp1i5_SVRkaI-71WmF1sZSRrNWFU9ls3s,7
56
+ gomyck_tools-1.0.2.dist-info/RECORD,,
ctools/log.py DELETED
@@ -1,28 +0,0 @@
1
- import logging
2
- import os
3
- import time
4
-
5
-
6
- # 文件日志
7
- def flog(base_work_path: str = './', log_level: int = logging.INFO, mixin: bool = False) -> logging:
8
- try:
9
- os.mkdir(base_work_path + ".logs")
10
- except Exception:
11
- pass
12
- log_file = base_work_path + ".logs" + os.path.sep + "log-" + time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(time.time())) + ".ck"
13
- if mixin:
14
- handlers = [logging.FileHandler(filename=log_file, encoding='utf-8'), logging.StreamHandler()]
15
- else:
16
- handlers = [logging.FileHandler(filename=log_file, encoding='utf-8')]
17
- logging.basicConfig(level=log_level,
18
- format='%(asctime)s-%(levelname)s-%(thread)d-%(funcName)s-%(lineno)d - %(message)s',
19
- handlers=handlers)
20
- return logging.getLogger("ck-flog")
21
-
22
-
23
- # 控制台日志
24
- def clog(log_level: int = logging.INFO) -> logging:
25
- logging.basicConfig(level=log_level,
26
- format='%(asctime)s-%(levelname)s-%(thread)d-%(funcName)s-%(lineno)d - %(message)s',
27
- handlers=[logging.StreamHandler()])
28
- return logging.getLogger("ck-clog")
ctools/mvc.py DELETED
@@ -1,56 +0,0 @@
1
- from functools import wraps
2
-
3
- from bottle import request
4
-
5
-
6
- class PageInfo:
7
- def __init__(self, page_size, page_index):
8
- self.page_size = page_size
9
- self.page_index = page_index
10
-
11
-
12
- class FormDataParams:
13
- def __init__(self, data, files):
14
- self.data = data
15
- self.files = files
16
-
17
- class DictWrapper(dict):
18
- def __getattr__(self, key):
19
- return self.get(key)
20
-
21
- def __setattr__(self, key, value):
22
- self[key] = value
23
-
24
- def parameter_handler():
25
- def return_func(func):
26
- @wraps(func)
27
- def decorated(*args, **kwargs):
28
- if request.method == 'GET':
29
- queryStr = request.query.decode('utf-8')
30
- pageInfo = PageInfo(
31
- page_size=10 if request.headers.get('page_size') is None else int(request.headers.get('page_size')),
32
- page_index=1 if request.headers.get('page_index') is None else int(request.headers.get('page_index'))
33
- )
34
- try:
35
- return func(params=queryStr, *args, **kwargs)
36
- except TypeError:
37
- return func(params=queryStr, pageInfo=pageInfo, *args, **kwargs)
38
- elif request.method == 'POST':
39
- content_type = request.get_header('content-type')
40
- if content_type == 'application/json':
41
- params = request.json
42
- return func(params=DictWrapper(params), *args, **kwargs)
43
- elif content_type and 'multipart/form-data' in content_type:
44
- form_data = request.forms.decode()
45
- form_files = request.files.decode()
46
- params = FormDataParams(data=DictWrapper(form_data), files=form_files)
47
- return func(params=params, *args, **kwargs)
48
- else:
49
- params = request.query.decode('utf-8')
50
- return func(params=params, *args, **kwargs)
51
- else:
52
- return func(*args, **kwargs)
53
-
54
- return decorated
55
-
56
- return return_func
ctools/token_tools.py DELETED
@@ -1,13 +0,0 @@
1
- from business.controller.base import GlobalState
2
- from ctools import string_tools
3
-
4
-
5
- def get_tmp_token():
6
- tmp_token = string_tools.get_uuid()
7
- GlobalState.tmp_token.append(tmp_token)
8
- return tmp_token
9
-
10
-
11
- def remove_tmp_token(token: str):
12
- if token in GlobalState.tmp_token:
13
- GlobalState.tmp_token.remove(token)