gomyck-tools 1.2.7__py3-none-any.whl → 1.2.8__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/aes_tools.py CHANGED
@@ -1,5 +1,13 @@
1
1
  from cryptography.fernet import Fernet
2
2
 
3
+ def generate_key():
4
+ """
5
+ 生成 AES key
6
+ Returns 32 bytes key
7
+ -------
8
+ """
9
+ key = Fernet.generate_key()
10
+ return key.decode()
3
11
 
4
12
  def aes_encrypt(sec_key, plaintext):
5
13
  """
@@ -23,3 +31,5 @@ def aes_decrypt(sec_key, ciphertext):
23
31
  cipher = Fernet(sec_key)
24
32
  decrypted_data = cipher.decrypt(ciphertext)
25
33
  return decrypted_data.decode()
34
+
35
+
ctools/bottle_web_base.py CHANGED
@@ -5,7 +5,7 @@ from functools import wraps
5
5
  import bottle
6
6
  from bottle import response, Bottle, request
7
7
 
8
- from ctools import token
8
+ from ctools import ctoken
9
9
  from ctools.dict_wrapper import DictWrapper
10
10
  from ctools.api_result import R
11
11
  from ctools.sys_log import flog as log
@@ -38,30 +38,30 @@ def init_app(context_path=None):
38
38
 
39
39
  @app.error(401)
40
40
  def unauthorized(error):
41
- after_request()
42
41
  response.status = 401
43
42
  log.error("系统未授权: {} {} {}".format(error.body, request.method, request.fullpath))
44
- return R.error(resp=R.Code.cus_code(9999, "系统未授权! {}".format(error.body)))
43
+ return R.error(resp=R.Code.cus_code(401, "系统未授权! {}".format(error.body)))
45
44
 
46
45
  @app.error(403)
47
46
  def unauthorized(error):
48
- after_request()
49
47
  response.status = 403
50
48
  log.error("访问受限: {} {} {}".format(error.body, request.method, request.fullpath))
51
- return R.error(resp=R.Code.cus_code(8888, "访问受限: {}".format(error.body)))
49
+ return R.error(resp=R.Code.cus_code(403, "访问受限: {}".format(error.body)))
50
+
51
+ @app.error(404)
52
+ def not_found(error):
53
+ response.status = 404
54
+ log.error("404 not found : {} {} {}".format(error.body, request.method, request.fullpath))
55
+ return R.error(resp=R.Code.cus_code(404, "资源未找到: {}".format(error.body)))
52
56
 
53
57
  @app.error(405)
54
- def cors_error(error):
55
- if request.method == 'OPTIONS':
56
- after_request()
57
- response.status = 405
58
- return
58
+ def method_not_allow(error):
59
+ response.status = 405
59
60
  log.error("请求方法错误: {} {} {}".format(error.status_line, request.method, request.fullpath))
60
- return R.error(msg='请求方法错误: {}'.format(error.status_line))
61
+ return R.error(resp=R.Code.cus_code(405, '请求方法错误: {}'.format(error.status_line)))
61
62
 
62
63
  @app.error(500)
63
- def cors_error(error):
64
- after_request()
64
+ def internal_error(error):
65
65
  response.status = 500
66
66
  log.error("系统发生错误: {} {} {}".format(error.body, request.method, request.fullpath))
67
67
  return R.error(msg='系统发生错误: {}'.format(error.exception))
@@ -162,7 +162,7 @@ def common_auth_verify(aes_key):
162
162
  auth_token = request.get_header('Authorization')
163
163
  if auth_token is None:
164
164
  auth_token = request.get_cookie('Authorization')
165
- payload = token.get_payload(auth_token, aes_key)
165
+ payload = ctoken.get_payload(auth_token, aes_key)
166
166
  if payload:
167
167
  return R.ok(to_json_str=False)
168
168
  return R.error(resp=R.Code.cus_code(401, "未授权"), to_json_str=False)
@@ -2,12 +2,10 @@ import sys
2
2
  from socketserver import ThreadingMixIn
3
3
  from wsgiref.simple_server import WSGIServer, WSGIRequestHandler, make_server
4
4
 
5
- from bottle import ServerAdapter, Bottle, template, static_file, abort
5
+ from bottle import ServerAdapter, Bottle, template, static_file, abort, redirect
6
6
 
7
7
  from ctools import sys_info
8
8
 
9
-
10
-
11
9
  """
12
10
  module_names = list(globals().keys())
13
11
  def get_modules():
@@ -24,21 +22,25 @@ def get_ws_modules():
24
22
  """
25
23
 
26
24
  """
25
+ from ctools import bottle_web_base, token, bottle_webserver
26
+ from ctools.api_result import R
27
+
28
+ secret_key = "xxx"
27
29
  app = bottle_web_base.init_app('子模块写 context_path, 主模块就不用写任何东西')
28
30
 
29
31
  # 通用的鉴权方法
30
32
  @bottle_web_base.before_intercept(0)
31
33
  def token_check():
32
- return bottle_web_base.common_auth_verify(aes_key)
34
+ return bottle_web_base.common_auth_verify(secret_key)
33
35
 
34
36
  @app.post('/login')
35
37
  def login(params):
36
- return R.ok(token.gen_token({'username': 'xxx'}, aes_key, 3600))
38
+ return R.ok(token.gen_token({'username': 'xxx'}, secret_key, 3600))
37
39
 
38
- @app.get('/queryList')
40
+ @app.get('/demo')
39
41
  @bottle_web_base.rule('DOC:DOWNLOAD')
40
- def query_list(params):
41
- print(123)
42
+ def demo(params):
43
+ print(params)
42
44
 
43
45
  main_app = bottle_webserver.init_bottle() # 这里可以传 APP 当做主模块, 但是 context_path 就不好使了, 上下文必须是 /
44
46
  main_app.mount(app.context_path, app)
@@ -46,9 +48,11 @@ main_app.set_index(r'轨迹点位压缩.html')
46
48
  main_app.run()
47
49
  """
48
50
 
51
+ _default_port = 8888
52
+
49
53
  class CBottle:
50
54
 
51
- def __init__(self, bottle: Bottle, port=8888, quiet=False):
55
+ def __init__(self, bottle: Bottle, port=_default_port, quiet=False):
52
56
  self.port = port
53
57
  self.quiet = quiet
54
58
  self.bottle = bottle
@@ -58,20 +62,16 @@ class CBottle:
58
62
  print('Click the link below to open the service homepage %s' % '\n \t\t http://localhost:%s \n \t\t http://%s:%s' % (self.port, sys_info.get_local_ipv4(), self.port), file=sys.stderr)
59
63
  self.bottle.run(server=http_server, quiet=self.quiet)
60
64
 
61
- def set_index(self, filename='index.html', root='./', **kwargs):
65
+ def set_index(self, root='./', filename='index.html', is_tpl=False, redirect_url=None, **kwargs):
62
66
  @self.bottle.route(['/', '/index'])
63
67
  def index():
64
68
  try:
69
+ if redirect_url: return redirect(redirect_url)
70
+ if is_tpl: return template(f"{root}/{filename}", **kwargs)
65
71
  return static_file(filename=filename, root=root, **kwargs)
66
72
  except FileNotFoundError:
67
73
  abort(404, "File not found...")
68
74
 
69
- def set_template(self, root, **kwargs):
70
- @self.bottle.route('/template/<filepath:path>')
71
- def index(filepath):
72
- template_path = f"{root}/{filepath}"
73
- return template(template_path, **kwargs)
74
-
75
75
  def set_static(self, root):
76
76
  @self.bottle.route('/static/<filepath:path>')
77
77
  def static(filepath):
@@ -88,7 +88,7 @@ class CBottle:
88
88
  def mount(self, context_path, app, **kwargs):
89
89
  self.bottle.mount(context_path, app, **kwargs)
90
90
 
91
- def init_bottle(app:Bottle=None, port=8888, quiet=False) -> CBottle:
91
+ def init_bottle(app:Bottle=None, port=_default_port, quiet=False) -> CBottle:
92
92
  bottle = app or Bottle()
93
93
  return CBottle(bottle, port, quiet)
94
94
 
@@ -100,7 +100,7 @@ class CustomWSGIHandler(WSGIRequestHandler):
100
100
 
101
101
  class WSGIRefServer(ServerAdapter):
102
102
 
103
- def __init__(self, host='0.0.0.0', port=8010):
103
+ def __init__(self, host='0.0.0.0', port=_default_port):
104
104
  super().__init__(host, port)
105
105
  self.server = None
106
106
 
@@ -3,15 +3,6 @@ from geventwebsocket.handler import WebSocketHandler
3
3
 
4
4
  from ctools import sys_log
5
5
 
6
- """
7
- app = bottle_web_base.init_app('/websocket_demo')
8
-
9
- @app.route('/script_debug', apply=[websocket])
10
- @bottle_web_base.rule('DOC:DOWNLOAD')
11
- def script_debug(ws: WebSocket):
12
- print(123)
13
- """
14
-
15
6
  """
16
7
  module_names = list(globals().keys())
17
8
  def get_modules():
@@ -28,14 +19,23 @@ def get_ws_modules():
28
19
  """
29
20
 
30
21
  """
22
+ ws_app = bottle_web_base.init_app('/websocket_demo')
23
+
24
+ @ws_app.route('/script_debug', apply=[websocket])
25
+ @bottle_web_base.rule('DOC:DOWNLOAD')
26
+ def script_debug(ws: WebSocket):
27
+ print(123)
28
+
31
29
  socket_app = bottle_websocket.init_bottle()
32
- socket_app.mount(app.context_path, app)
30
+ socket_app.mount(app.context_path, ws_app)
33
31
  socket_app.run()
34
32
  """
35
33
 
34
+ _default_port = 8887
35
+
36
36
  class CBottle:
37
37
 
38
- def __init__(self, bottle: Bottle, port=8888, quiet=False):
38
+ def __init__(self, bottle: Bottle, port=_default_port, quiet=False):
39
39
  self.port = port
40
40
  self.quiet = quiet
41
41
  self.bottle = bottle
@@ -47,7 +47,7 @@ class CBottle:
47
47
  def mount(self, context_path, app):
48
48
  self.bottle.mount(context_path, app)
49
49
 
50
- def init_bottle(port=8889, quiet=False) -> CBottle:
50
+ def init_bottle(port=_default_port, quiet=False) -> CBottle:
51
51
  bottle = Bottle()
52
52
  return CBottle(bottle, port, quiet)
53
53
 
@@ -62,7 +62,7 @@ class CustomWebSocketHandler(WebSocketHandler):
62
62
 
63
63
  class WebSocketServer(ServerAdapter):
64
64
 
65
- def __init__(self, host='0.0.0.0', port=8011):
65
+ def __init__(self, host='0.0.0.0', port=_default_port):
66
66
  super().__init__(host, port)
67
67
  self.server = None
68
68
 
ctools/cftp.py ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2025/1/24 08:53'
5
+
6
+ import io
7
+ import time
8
+ from ftplib import FTP, error_perm, error_temp
9
+
10
+ class CFTP:
11
+ """
12
+ with open('xx/xx.md', 'rb') as file:
13
+ ftp_host = 'x.x.x.x'
14
+ ftp_username = 'x'
15
+ ftp_password = 'x'
16
+ CFTP(ftp_host, ftp_username, ftp_password).upload_file_to_ftp('xx.md', file)
17
+ """
18
+ def __init__(self, host, username, password, timeout=30, max_retries=3, retry_delay=5):
19
+ self.host = host
20
+ self.username = username
21
+ self.password = password
22
+ self.ftp:FTP = None
23
+ self.timeout = timeout
24
+ self.max_retries = max_retries
25
+ self.retry_delay = retry_delay
26
+
27
+ def upload_file_to_ftp(self, file_name, file:io.BytesIO, ftp_directory='/'):
28
+ if not file_name: raise Exception('文件名不能为空')
29
+ if not file: raise Exception('文件不能为空')
30
+ for attempt in range(self.max_retries):
31
+ try:
32
+ if not self.ftp:
33
+ ftp = FTP(self.host, timeout=self.timeout)
34
+ ftp.login(self.username, self.password)
35
+ ftp.set_pasv(True)
36
+ self.ftp = ftp
37
+ try:
38
+ self.ftp.cwd(ftp_directory)
39
+ except error_perm:
40
+ print(f"FTP 目录不存在:{ftp_directory}")
41
+ self.ftp.mkd(ftp_directory)
42
+ print(f"FTP 目录创建成功:{ftp_directory}, 正在切换到目录:{ftp_directory}")
43
+ self.ftp.cwd(ftp_directory)
44
+ print(f"正在上传文件:{file_name}")
45
+ self.ftp.storbinary(f"STOR {file_name}", file)
46
+ self.ftp.quit()
47
+ print(f"文件成功上传到 FTP: {file_name}")
48
+ return
49
+ except (error_perm, error_temp) as e:
50
+ try:
51
+ self.ftp.quit()
52
+ except Exception:
53
+ pass
54
+ self.ftp = None
55
+ print(f"FTP 错误:{e}")
56
+ if attempt < self.max_retries - 1:
57
+ print(f"正在尝试重连... 第 {attempt + 1} 次重试...")
58
+ time.sleep(self.retry_delay)
59
+ else:
60
+ print("重试次数已用尽,上传失败。")
61
+ raise
62
+ except Exception as e:
63
+ try:
64
+ self.ftp.quit()
65
+ except Exception:
66
+ pass
67
+ self.ftp = None
68
+ print(f"连接或上传出现异常:{e}")
69
+ if attempt < self.max_retries - 1:
70
+ print(f"正在尝试重连... 第 {attempt + 1} 次重试...")
71
+ time.sleep(self.retry_delay)
72
+ else:
73
+ print("重试次数已用尽,上传失败。")
74
+ raise
ctools/coord_trans.py ADDED
@@ -0,0 +1,127 @@
1
+ # -*- coding: utf-8 -*-
2
+ import math
3
+
4
+ x_pi = 3.14159265358979324 * 3000.0 / 180.0
5
+ pi = 3.1415926535897932384626 # π
6
+ a = 6378245.0 # 长半轴
7
+ ee = 0.00669342162296594323 # 偏心率平方
8
+
9
+ def gcj02_to_bd09(lng, lat):
10
+ """
11
+ 火星坐标系(GCJ-02)转百度坐标系(BD-09)
12
+ 谷歌、高德——>百度
13
+ :param lng:火星坐标经度
14
+ :param lat:火星坐标纬度
15
+ :return:
16
+ """
17
+ z = math.sqrt(lng * lng + lat * lat) + 0.00002 * math.sin(lat * x_pi)
18
+ theta = math.atan2(lat, lng) + 0.000003 * math.cos(lng * x_pi)
19
+ bd_lng = z * math.cos(theta) + 0.0065
20
+ bd_lat = z * math.sin(theta) + 0.006
21
+ return [bd_lng, bd_lat]
22
+
23
+
24
+ def bd09_to_gcj02(bd_lon, bd_lat):
25
+ """
26
+ 百度坐标系(BD-09)转火星坐标系(GCJ-02)
27
+ 百度——>谷歌、高德
28
+ :param bd_lat:百度坐标纬度
29
+ :param bd_lon:百度坐标经度
30
+ :return:转换后的坐标列表形式
31
+ """
32
+ x = bd_lon - 0.0065
33
+ y = bd_lat - 0.006
34
+ z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * x_pi)
35
+ theta = math.atan2(y, x) - 0.000003 * math.cos(x * x_pi)
36
+ gg_lng = z * math.cos(theta)
37
+ gg_lat = z * math.sin(theta)
38
+ return [gg_lng, gg_lat]
39
+
40
+
41
+ def wgs84_to_gcj02(lng, lat):
42
+ """
43
+ WGS84转GCJ02(火星坐标系)
44
+ :param lng:WGS84坐标系的经度
45
+ :param lat:WGS84坐标系的纬度
46
+ :return:
47
+ """
48
+ if out_of_china(lng, lat): # 判断是否在国内
49
+ return [lng, lat]
50
+ dlat = _transformlat(lng - 105.0, lat - 35.0)
51
+ dlng = _transformlng(lng - 105.0, lat - 35.0)
52
+ radlat = lat / 180.0 * pi
53
+ magic = math.sin(radlat)
54
+ magic = 1 - ee * magic * magic
55
+ sqrtmagic = math.sqrt(magic)
56
+ dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
57
+ dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
58
+ mglat = lat + dlat
59
+ mglng = lng + dlng
60
+ return [mglng, mglat]
61
+
62
+
63
+ def gcj02_to_wgs84(lng, lat):
64
+ """
65
+ GCJ02(火星坐标系)转GPS84
66
+ :param lng:火星坐标系的经度
67
+ :param lat:火星坐标系纬度
68
+ :return:
69
+ """
70
+ if out_of_china(lng, lat):
71
+ return [lng, lat]
72
+ dlat = _transformlat(lng - 105.0, lat - 35.0)
73
+ dlng = _transformlng(lng - 105.0, lat - 35.0)
74
+ radlat = lat / 180.0 * pi
75
+ magic = math.sin(radlat)
76
+ magic = 1 - ee * magic * magic
77
+ sqrtmagic = math.sqrt(magic)
78
+ dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
79
+ dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
80
+ mglat = lat + dlat
81
+ mglng = lng + dlng
82
+ return [lng * 2 - mglng, lat * 2 - mglat]
83
+
84
+
85
+ def bd09_to_wgs84(bd_lon, bd_lat):
86
+ lon, lat = bd09_to_gcj02(bd_lon, bd_lat)
87
+ return gcj02_to_wgs84(lon, lat)
88
+
89
+
90
+ def wgs84_to_bd09(lon, lat):
91
+ lon, lat = wgs84_to_gcj02(lon, lat)
92
+ return gcj02_to_bd09(lon, lat)
93
+
94
+
95
+ def _transformlat(lng, lat):
96
+ ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + \
97
+ 0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng))
98
+ ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *
99
+ math.sin(2.0 * lng * pi)) * 2.0 / 3.0
100
+ ret += (20.0 * math.sin(lat * pi) + 40.0 *
101
+ math.sin(lat / 3.0 * pi)) * 2.0 / 3.0
102
+ ret += (160.0 * math.sin(lat / 12.0 * pi) + 320 *
103
+ math.sin(lat * pi / 30.0)) * 2.0 / 3.0
104
+ return ret
105
+
106
+
107
+ def _transformlng(lng, lat):
108
+ ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + \
109
+ 0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng))
110
+ ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *
111
+ math.sin(2.0 * lng * pi)) * 2.0 / 3.0
112
+ ret += (20.0 * math.sin(lng * pi) + 40.0 *
113
+ math.sin(lng / 3.0 * pi)) * 2.0 / 3.0
114
+ ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 *
115
+ math.sin(lng / 30.0 * pi)) * 2.0 / 3.0
116
+ return ret
117
+
118
+
119
+ def out_of_china(lng, lat):
120
+ """
121
+ 判断是否在国内,不在国内不做偏移
122
+ :param lng:
123
+ :param lat:
124
+ :return:
125
+ """
126
+ return not (lng > 73.66 and lng < 135.05 and lat > 3.86 and lat < 53.55)
127
+
ctools/credis.py ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2025/2/14 11:09'
5
+
6
+ import redis
7
+ from redis import Redis
8
+
9
+ from ctools import date_utils, thread_pool, string_tools
10
+
11
+ def init_pool(host: str = 'localhost', port: int = 6379, db: int = 0, password: str = None,
12
+ username: str = None, decode_responses: bool = True, max_connections: int = 75,
13
+ health_check_interval: int = 30, retry_count: int = 3) -> Redis:
14
+ for attempt in range(retry_count):
15
+ try:
16
+ r: Redis = redis.StrictRedis(
17
+ host=host, port=port, db=db,
18
+ username=username, password=password,
19
+ retry_on_timeout=True,
20
+ max_connections=max_connections,
21
+ decode_responses=decode_responses,
22
+ health_check_interval=health_check_interval,
23
+ socket_connect_timeout=5,
24
+ socket_timeout=5
25
+ )
26
+ if r.ping():
27
+ print('CRedis connect {} {} success!'.format(host, port))
28
+ return r
29
+ except redis.ConnectionError as e:
30
+ if attempt == retry_count - 1:
31
+ raise Exception(f"Failed to connect to Redis after {retry_count} attempts: {str(e)}")
32
+ print(f"Connection attempt {attempt + 1} failed, retrying...")
33
+
34
+ def add_lock(r: Redis, key: str, timeout: int = 30):
35
+ if r.exists(key):
36
+ expire_time = r.get(key)
37
+ if date_utils.time_diff_in_seconds(expire_time, date_utils.get_date_time()) > 0:
38
+ return True
39
+ else:
40
+ r.delete(key)
41
+ return r.set(key, date_utils.opt_time(seconds=timeout), nx=True, ex=timeout) is not None
42
+
43
+ def remove_lock(r: Redis, key: str):
44
+ r.delete(key)
45
+
46
+ def subscribe(r: Redis, channel_name, callback):
47
+ def thread_func():
48
+ pubsub = r.pubsub()
49
+ pubsub.subscribe(channel_name)
50
+ for message in pubsub.listen():
51
+ callback(message)
52
+ thread_pool.submit(thread_func)
53
+
54
+ def _process_pending_messages(r: Redis, stream_name: str, group_name: str, consumer_name: str, callback):
55
+ """
56
+ 处理未确认的消息
57
+ :param r: Redis 连接
58
+ :param stream_name: 流名称
59
+ :param group_name: 消费者组名称
60
+ :param consumer_name: 消费者名称
61
+ :param callback: 消息处理回调函数
62
+ """
63
+ # 检查未确认的消息
64
+ pending_messages = r.xpending(stream_name, group_name)
65
+ if pending_messages['pending'] > 0:
66
+ print(f"Found {pending_messages['pending']} pending messages.")
67
+ # 获取未确认的消息列表
68
+ pending_list = r.xpending_range(stream_name, group_name, min='-', max='+', count=pending_messages['pending'])
69
+ for message in pending_list:
70
+ message_id = message['message_id']
71
+ claimed_messages = r.xclaim(stream_name, group_name, consumer_name, min_idle_time=0, message_ids=[message_id])
72
+ if claimed_messages:
73
+ # 处理消息
74
+ for claimed_message in claimed_messages:
75
+ message_id, data = claimed_message
76
+ print(f"Processing pending message: {message_id}, data: {data}")
77
+ try:
78
+ if callback(message_id, data):
79
+ r.xack(stream_name, group_name, message_id)
80
+ except Exception as e:
81
+ print(f"Error processing message {message_id}: {e}")
82
+ else:
83
+ print("No pending messages found.")
84
+
85
+ def stream_subscribe(r: Redis, stream_name, group_name, callback, from_id: str='$', noack: bool = False):
86
+ def thread_func():
87
+ try:
88
+ # $表示从最后面消费, 0表示从开始消费
89
+ r.xgroup_create(name=stream_name, groupname=group_name, id=from_id, mkstream=True)
90
+ print(f"Consumer group '{group_name}' created successfully.")
91
+ except Exception as e:
92
+ if "already exists" in str(e):
93
+ print(f"Consumer group '{group_name}' already exists.")
94
+ else:
95
+ print(f"Error creating consumer group '{group_name}': {e}")
96
+ consumer_name = 'consumer-{}'.format(string_tools.get_uuid())
97
+ # 处理未确认的消息
98
+ _process_pending_messages(r, stream_name, group_name, consumer_name, callback)
99
+ while True:
100
+ messages = r.xreadgroup(group_name, consumer_name, {stream_name: '>'}, block=1000, noack=noack)
101
+ for message in messages:
102
+ try:
103
+ message_id, data = message[1][0]
104
+ res = callback(message_id, data)
105
+ if res: r.xack(stream_name, group_name, message_id)
106
+ except Exception as e:
107
+ print('stream_subscribe error: ', e)
108
+ thread_pool.submit(thread_func)
109
+
110
+ def stream_publish(r: Redis, stream_name, message):
111
+ r.xadd(stream_name, message)
ctools/czip.py ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2025/1/24 08:48'
5
+
6
+ import io
7
+ import time
8
+
9
+ import pyzipper
10
+
11
+ def add_file_to_zip(file_name, file_bytes:[], password=None) -> io.BytesIO:
12
+ zip_filename = "{}}_{}.zip".format(file_name, time.strftime('%Y-%m-%d_%H-%M-%S-%s', time.localtime(time.time())))
13
+ zipFile = io.BytesIO()
14
+ with pyzipper.AESZipFile(zipFile, 'w', compression=pyzipper.ZIP_DEFLATED, encryption=pyzipper.WZ_AES) as zipf:
15
+ if password: zipf.setpassword(password.encode('utf-8'))
16
+ for file in file_bytes:
17
+ zipf.writestr(zip_filename, file)
18
+ zipFile.seek(0)
19
+ return zipFile
ctools/database.py CHANGED
@@ -30,6 +30,13 @@ def _init():
30
30
  global Base
31
31
  Base = declarative_base()
32
32
 
33
+ """
34
+ The string form of the URL is
35
+ dialect[+driver]://user:password@host/dbname[?key=value..]
36
+ where ``dialect`` is a database name such as ``mysql``, ``oracle``, ``postgresql``, etc.
37
+ and ``driver`` the name of a DBAPI such as ``psycopg2``, ``pyodbc``, ``cx_oracle``, etc. Alternatively
38
+ """
39
+
33
40
  # 密码里的@ 要替换成 %40
34
41
 
35
42
  # sqlite connect_args={"check_same_thread": False} db_url=sqlite:///{}.format(db_url)
ctools/date_utils.py CHANGED
@@ -1,44 +1,43 @@
1
1
  import time
2
-
2
+ from datetime import datetime, timedelta
3
3
 
4
4
  def get_date():
5
- """
6
- 获取 %Y-%m-%d格式时间
7
- :return:
8
- """
9
5
  return time.strftime('%Y-%m-%d', time.localtime(time.time()))
10
6
 
11
-
12
7
  def get_time():
13
- """
14
- 获取 %H-%M-%S格式时间
15
- :return:
16
- """
17
8
  return time.strftime('%H-%M-%S', time.localtime(time.time()))
18
9
 
10
+ def get_date_time(fmt="%Y-%m-%d %H:%M:%S"):
11
+ return time.strftime(fmt, time.localtime(time.time()))
19
12
 
20
- def get_date_time(offset=0):
21
- """
22
- 获取 %Y-%m-%d %H:%M:%S格式时间
23
- :return:
24
- """
25
- return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time() + offset))
26
-
13
+ def str_to_datetime(val: str, fmt="%Y-%m-%d %H:%M:%S"):
14
+ return time.strptime(val, fmt)
27
15
 
28
- def get_file_time():
29
- """
30
- 获取 %Y-%m-%d %H:%M:%S 文件格式时间
31
- :return:
32
- """
33
- return time.strftime('%Y-%m-%d_%H-%M-%S-%s', time.localtime(time.time()))
16
+ def str_to_timestamp(val: str, fmt="%Y-%m-%d %H:%M:%S"):
17
+ return time.mktime(time.strptime(val, fmt))
34
18
 
35
- def get_timestamp(offset=0):
36
- return int(time.time() + offset)
19
+ def timestamp_to_str(timestamp: int=time.time(), fmt="%Y-%m-%d %H:%M:%S"):
20
+ return time.strftime(fmt, time.localtime(timestamp))
37
21
 
38
- def str_to_timestamp(val: str):
39
- return time.mktime(time.strptime(val, "%Y-%m-%d %H:%M:%S"))
22
+ def get_today_start_end(now: datetime.now()):
23
+ start = datetime(now.year, now.month, now.day, 0, 0, 0)
24
+ end = datetime(now.year, now.month, now.day, 23, 59, 59)
25
+ return start.strftime("%Y-%m-%d %H:%M:%S"), end.strftime("%Y-%m-%d %H:%M:%S")
40
26
 
41
- def str_to_datetime(val: str):
42
- return time.strptime(val, "%Y-%m-%d %H:%M:%S")
27
+ def get_week_start_end(now: datetime.now()):
28
+ start = now - timedelta(days=now.weekday()) # 本周一
29
+ end = start + timedelta(days=6) # 本周日
30
+ return start.strftime("%Y-%m-%d 00:00:00"), end.strftime("%Y-%m-%d 23:59:59")
43
31
 
32
+ def time_diff_in_seconds(sub_head: str=get_date_time(), sub_end: str=get_date_time()):
33
+ start_ts = str_to_timestamp(sub_head)
34
+ end_ts = str_to_timestamp(sub_end)
35
+ return int(start_ts - end_ts)
44
36
 
37
+ def opt_time(base_time=None, days=0, hours=0, minutes=0, seconds=0, weeks=0, fmt="%Y-%m-%d %H:%M:%S"):
38
+ if base_time is None:
39
+ base_time = datetime.now()
40
+ elif isinstance(base_time, str):
41
+ base_time = datetime.strptime(base_time, fmt)
42
+ new_time = base_time + timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds, weeks=weeks)
43
+ return new_time.strftime(fmt)
ctools/metrics.py CHANGED
@@ -7,7 +7,7 @@ from ctools import call, cjson, sys_log, work_path
7
7
 
8
8
  log = sys_log.flog
9
9
 
10
- _metrics_port = 8011
10
+ _metrics_port = 8889
11
11
  _persistent_json = {}
12
12
  _metrics_initial = {}
13
13
  _lock = threading.Lock()
ctools/sm_tools.py CHANGED
@@ -1,6 +1,6 @@
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
6
  sm2_crypt: sm2.CryptSM2 = None
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: gomyck-tools
3
- Version: 1.2.7
4
- Summary: A ctools for python development by hao474798383
3
+ Version: 1.2.8
4
+ Summary: A tools collection for python development by hao474798383
5
5
  Home-page: https://blog.gomyck.com
6
6
  Author: gomyck
7
7
  Author-email: hao474798383@163.com
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.8
11
+ Requires-Python: >=3.9
12
12
  Description-Content-Type: text/markdown
13
13
  Requires-Dist: jsonpickle ~=3.4.2
14
14
  Requires-Dist: SQLAlchemy ~=2.0.36
@@ -30,5 +30,28 @@ Requires-Dist: pyzipper ==0.3.6
30
30
  Requires-Dist: prometheus-client ==0.21.1
31
31
  Requires-Dist: paramiko ==3.5.0
32
32
  Requires-Dist: pyjwt ==2.10.1
33
+ Requires-Dist: cryptography ==43.0.1
34
+ Requires-Dist: redis ==5.2.1
35
+
36
+ # Gomyck-Tools
37
+
38
+ ## project
39
+
40
+ https://github.com/mzxc
41
+
42
+ ## install
43
+
44
+ This package need python version >= 3.9
45
+
46
+ ```shell
47
+ pip install gomyck-tools
48
+ ```
49
+
50
+ ## usage
51
+
52
+ ```python
53
+ from ctools import sys_log
54
+ sys_log.clog.info('hello world')
55
+ ```
56
+
33
57
 
34
- this package is for python development
@@ -1,21 +1,26 @@
1
1
  ctools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- ctools/aes_tools.py,sha256=ylUgyhlx7bNCTGrbWEZVwCObzYdJTQtwEc4ZMidL2Fo,563
2
+ ctools/aes_tools.py,sha256=L5Jg4QtVTdIxHe9zEpR8oMQx0IrYK68vjEYb_RmkhPA,699
3
3
  ctools/api_result.py,sha256=UeQXI_zuZB-uY5qECTpz1fC7EGy82yGQqWMx20tyRTw,1572
4
4
  ctools/application.py,sha256=DcuSt2m8cDuSftx6eKfJ5gA6_F9dDlzkj0K86EG4F7s,15884
5
5
  ctools/b64.py,sha256=_BdhX3p3-MaSSlU2wivN5qPxQfacR3VRBr1WC456tU0,194
6
6
  ctools/bashPath.py,sha256=BCN_EhYzqvwsxYso81omMNd3SbEociwSOyb9kLvu8V4,337
7
- ctools/bottle_web_base.py,sha256=VUAw3GBpoamLDXQJZQScbNPWIysVexns7QcMV-kcLSM,5822
8
- ctools/bottle_webserver.py,sha256=tZc9-By_kZgyfRKUSb7tCbEsa5d7X4V3Gcj80p9tb2Q,3489
9
- ctools/bottle_websocket.py,sha256=wjsjKVE_tlon0hYfnzBT0Sis6yNPe318vKgTfzWYbfQ,1903
7
+ ctools/bottle_web_base.py,sha256=lP4WIvmr-JjO6kDQ-NGIIe2l96hP-aXAiInYsqiWEAU,5974
8
+ ctools/bottle_webserver.py,sha256=p_obHp_9de-A3HbLF5orrqMmDayett9MeaKja7a0N9M,3601
9
+ ctools/bottle_websocket.py,sha256=zqCE1rGlMeC9oxFOULNd137IWIhdetq83Oq5OoH_zGI,1953
10
10
  ctools/browser_element_tools.py,sha256=IFR_tWu5on0LxhuC_4yT6EOjwCsC-juIoU8KQRDqR7E,9952
11
11
  ctools/call.py,sha256=BCr8wzt5qd70okv8IZn-9-EpjywleZgvA3u1vfZ_Kt8,1581
12
+ ctools/cftp.py,sha256=SkHPDvKu58jnMnl68u5WxWEiFWsm2C0CGa5_GR_Obcw,2481
12
13
  ctools/cjson.py,sha256=g5OldDeD7bmdw_4NNNPfAREGXNplQxY_l9vtTz-UBTs,1331
13
14
  ctools/ckafka.py,sha256=EiiGCFkSbq8yRjQKVjc2_V114hKb8fJAVIOks_XbQ3w,5944
14
15
  ctools/compile_tools.py,sha256=Nybh3vnkurIKnPnubdYzigjnzFu4GaTMKPvqFdibxmE,510
15
16
  ctools/console.py,sha256=EZumuyynwteKUhUxB_XoulHswDxHd75OQB34RiZ-OBM,1807
17
+ ctools/coord_trans.py,sha256=pzIHxC4aLwvOF3eJG47Dda3vIq-Zp42xnu_FwILDflU,3951
18
+ ctools/credis.py,sha256=sW7yDQvxa7B4dWvGwUH7GROq-7ElRMDhFT6g2C8ryfE,4522
16
19
  ctools/cron_lite.py,sha256=f9g7-64GsCxcAW-HUAvT6S-kooScl8zaJyqwHY-X_rE,8308
17
- ctools/database.py,sha256=M28inDYU4X69BFeVGCOwzKFtYHuQFgEm9_aoTXmrTzY,5803
18
- ctools/date_utils.py,sha256=qkIvn2TGQ97vUw2ppBfHE7IyCUqg0s1FzrB0BJAsaBo,868
20
+ ctools/ctoken.py,sha256=NZSBGF3lJajJFLRIZoeXmpp8h5cKM0dAH2weySgeORc,882
21
+ ctools/czip.py,sha256=g-2s804R06Bnp19ByVsYeRbwx5HQf_KwrStvHimVyns,632
22
+ ctools/database.py,sha256=619rlE6LMZ5x9ijLv8WVmtUwbSL5tCHGwt_K-V_VmQY,6094
23
+ ctools/date_utils.py,sha256=h3rvlw_K2F0QTac2Zat_1us76R0P-Qj6_6NeQPfM3VE,1697
19
24
  ctools/dict_wrapper.py,sha256=6lZUyHomdn5QdjQTg7EL1sC_I6tOlh8ofMRQT3XGQjM,398
20
25
  ctools/douglas_rarefy.py,sha256=43WRjGGsQ_o1yPEXypA1Xv_yuo90RVo7qaYGRslx5gQ,4890
21
26
  ctools/download_tools.py,sha256=oJbG12Hojd0J17sAlvMU480P3abi4_AB9oZkEBGFPuo,1930
@@ -26,7 +31,7 @@ ctools/html_soup.py,sha256=rnr8M3gn3gQGo-wNaNFXDjdzp8AAkv9o4yqfIIfO-zw,1567
26
31
  ctools/http_utils.py,sha256=dG26aci1_YxAyKwYqMKFw4wZAryLkDyvnQ3hURjB6Lk,768
27
32
  ctools/images_tools.py,sha256=TapXYCPqC7GesgrALecxxa_ApuT_dxUG5fqQIJF2bNY,670
28
33
  ctools/imgDialog.py,sha256=zFeyPmpnEn9Ih7-yuJJrePqW8Myj3jC9UYMTDk-umTs,1385
29
- ctools/metrics.py,sha256=xKy1uWqvg2BnS14dQxvs_qUQtMuVLlUKECSyErFnBtk,5247
34
+ ctools/metrics.py,sha256=Ld2OAeJLgXo66zIIn5eeD1AFIxTWw8JJeny--ge--6c,5247
30
35
  ctools/mqtt_utils.py,sha256=ZWSZiiNJLLlkHF95S6LmRmkYt-iIL4K73VdN3b1HaHw,10702
31
36
  ctools/obj.py,sha256=GYS1B8NyjtUIh0HlK9r8avC2eGbK2SJac4C1CGnfGhI,479
32
37
  ctools/pacth.py,sha256=MJ9Du-J9Gv62y4cZKls1jKbl5a5kL2y9bD-gzYUCveQ,2604
@@ -37,14 +42,13 @@ ctools/resource_bundle_tools.py,sha256=wA4fmD_ZEcrpcvUZKa60uDDX-nNQSVz1nBh0A2GVu
37
42
  ctools/rsa.py,sha256=c0q7oStlpSfTxmn900XMDjyOGS1A7tVsUIocr0nL2UI,2259
38
43
  ctools/screenshot_tools.py,sha256=KoljfgqW5x9aLwKdIfz0vR6v-fX4XjE92HudkDxC2hE,4539
39
44
  ctools/sign.py,sha256=JOkgpgsMbk7T3c3MOj1U6eiEndUG9XQ-uIX9e615A_Y,566
40
- ctools/sm_tools.py,sha256=CDfgupqs_nrZs-135ZvDu6QIx4aamJVdg7vuLs9_0yw,1678
45
+ ctools/sm_tools.py,sha256=R0m52TQE-CT7pvGTP27UWNCfdzpQ8C-ALz7p0mnOnLU,1672
41
46
  ctools/snow_id.py,sha256=hYinnRN-aOule4_9vfgXB7XnsU-56cIS3PhzAwWBc5E,2270
42
47
  ctools/str_diff.py,sha256=QUtXOfsRLTFozH_zByqsC39JeuG3eZtrwGVeLyaHYUI,429
43
48
  ctools/string_tools.py,sha256=itK59W4Ed4rQzuyHuioNgDRUcBlfb4ZoZnwmS9cJxiI,1887
44
49
  ctools/sys_info.py,sha256=NvKCuBlWHHiW4bDI4tYZUo3QusvODm1HlW6aAkrllnE,4248
45
50
  ctools/sys_log.py,sha256=oqb1S41LosdeZxtogFVgDk8R4sjiHhUeYJLCzHd728E,2805
46
51
  ctools/thread_pool.py,sha256=qb68ULHy1K7u3MC7WP49wDhmgUhgWazd9FRuFbClET4,925
47
- ctools/token.py,sha256=NZSBGF3lJajJFLRIZoeXmpp8h5cKM0dAH2weySgeORc,882
48
52
  ctools/upload_tools.py,sha256=sqe6K3ZWiyY58pFE5IO5mNaS1znnS7U4c4UqY8noED4,1068
49
53
  ctools/win_canvas.py,sha256=PAxI4i1jalfree9d1YG4damjc2EzaHZrgHZCTgk2GiM,2530
50
54
  ctools/win_control.py,sha256=35f9x_ijSyc4ZDkcT32e9ZIhr_ffNxadynrQfFuIdYo,3489
@@ -52,7 +56,7 @@ ctools/wordFill.py,sha256=dB1OLt6GLmWdkDV8H20VWbJmY4ggNNI8iHD1ocae2iM,875
52
56
  ctools/word_fill.py,sha256=xeo-P4DOjQUqd-o9XL3g66wQrE2diUPGwFywm8TdVyw,18210
53
57
  ctools/word_fill_entity.py,sha256=eX3G0Gy16hfGpavQSEkCIoKDdTnNgRRJrFvKliETZK8,985
54
58
  ctools/work_path.py,sha256=OmfYu-Jjg2huRY6Su8zJ_2EGFFhtBZFbobYTwbjJtG4,1817
55
- gomyck_tools-1.2.7.dist-info/METADATA,sha256=uS-JVNCRR951EEMSzzMThWcONySUcNIj-lG-uJhF4M0,1108
56
- gomyck_tools-1.2.7.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
57
- gomyck_tools-1.2.7.dist-info/top_level.txt,sha256=-MiIH9FYRVKp1i5_SVRkaI-71WmF1sZSRrNWFU9ls3s,7
58
- gomyck_tools-1.2.7.dist-info/RECORD,,
59
+ gomyck_tools-1.2.8.dist-info/METADATA,sha256=gUL87nZKJGUAwG9w5FJAMdTMQXDndYMOWKjK0ndbOL8,1376
60
+ gomyck_tools-1.2.8.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
61
+ gomyck_tools-1.2.8.dist-info/top_level.txt,sha256=-MiIH9FYRVKp1i5_SVRkaI-71WmF1sZSRrNWFU9ls3s,7
62
+ gomyck_tools-1.2.8.dist-info/RECORD,,
File without changes