gomyck-tools 1.2.7__py3-none-any.whl → 1.2.9__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))
@@ -123,18 +123,30 @@ def params_resolve(func):
123
123
  queryStr.page_info = page_info
124
124
  return func(params=queryStr, *args, **kwargs)
125
125
  elif request.method == 'POST':
126
+ query_params = request.query.decode('utf-8')
126
127
  content_type = request.get_header('content-type')
127
128
  if content_type == 'application/json':
128
129
  params = request.json or {}
129
- return func(params=DictWrapper(params), *args, **kwargs)
130
- elif content_type and 'multipart/form-data' in content_type:
130
+ dict_wrapper = DictWrapper(params)
131
+ dict_wrapper.query_params = query_params
132
+ return func(params=dict_wrapper, *args, **kwargs)
133
+ elif 'multipart/form-data' in content_type:
131
134
  form_data = request.forms.decode()
132
135
  form_files = request.files.decode()
133
- params = FormDataParams(data=DictWrapper(form_data), files=form_files)
134
- return func(params=params, *args, **kwargs)
135
- else:
136
- params = request.query.decode('utf-8')
137
- return func(params=params, *args, **kwargs)
136
+ dict_wrapper = DictWrapper(form_data)
137
+ dict_wrapper.query_params = query_params
138
+ dict_wrapper.files = form_files
139
+ return func(params=dict_wrapper, *args, **kwargs)
140
+ elif 'application/x-www-form-urlencoded' in content_type:
141
+ params = request.forms.decode()
142
+ dict_wrapper = DictWrapper(params.dict)
143
+ dict_wrapper.query_params = query_params
144
+ return func(params=dict_wrapper, *args, **kwargs)
145
+ elif 'text/plain' in content_type:
146
+ params = request.body.read().decode('utf-8')
147
+ dict_wrapper = DictWrapper({'body': params})
148
+ dict_wrapper.query_params = query_params
149
+ return func(params=dict_wrapper, *args, **kwargs)
138
150
  else:
139
151
  return func(*args, **kwargs)
140
152
  return decorated
@@ -144,17 +156,6 @@ class PageInfo:
144
156
  self.page_size = page_size
145
157
  self.page_index = page_index
146
158
 
147
- class FormDataParams:
148
- def __init__(self, data, files):
149
- self.data = data
150
- self.files = files
151
-
152
- def __getattr__(self, key):
153
- try:
154
- return self.data[key]
155
- except Exception:
156
- return self.files[key]
157
-
158
159
  # 通用的鉴权方法
159
160
  def common_auth_verify(aes_key):
160
161
  if request.path.startswith('/static') or request.path in GlobalState.withOutLoginURI:
@@ -162,7 +163,7 @@ def common_auth_verify(aes_key):
162
163
  auth_token = request.get_header('Authorization')
163
164
  if auth_token is None:
164
165
  auth_token = request.get_cookie('Authorization')
165
- payload = token.get_payload(auth_token, aes_key)
166
+ payload = ctoken.get_payload(auth_token, aes_key)
166
167
  if payload:
167
168
  return R.ok(to_json_str=False)
168
169
  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, response
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,49 +48,76 @@ 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
59
+ self.index_root = './'
60
+ self.index_filename = 'index.html'
61
+ self.is_tpl = False
62
+ self.tmp_args = {}
63
+ self.redirect_url = None
64
+ self.static_root = './static'
65
+ self.download_root = './download'
55
66
 
56
- def run(self):
57
- http_server = WSGIRefServer(port=self.port)
58
- 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
- self.bottle.run(server=http_server, quiet=self.quiet)
60
-
61
- def set_index(self, filename='index.html', root='./', **kwargs):
62
67
  @self.bottle.route(['/', '/index'])
63
68
  def index():
64
69
  try:
65
- return static_file(filename=filename, root=root, **kwargs)
70
+ if self.redirect_url: return redirect(self.redirect_url)
71
+ if self.is_tpl: return template(f"{self.index_root}/{self.index_filename}", self.tmp_args)
72
+ return static_file(filename=self.index_filename, root=self.index_root)
66
73
  except FileNotFoundError:
67
74
  abort(404, "File not found...")
68
75
 
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
- def set_static(self, root):
76
76
  @self.bottle.route('/static/<filepath:path>')
77
77
  def static(filepath):
78
78
  try:
79
- return static_file(filepath, root=root)
79
+ return static_file(filepath, root=self.static_root)
80
80
  except FileNotFoundError:
81
81
  abort(404, "File not found...")
82
82
 
83
- def set_download(self, root):
84
83
  @self.bottle.route('/download/<filepath:path>')
85
84
  def download(filepath):
86
- return static_file(filepath, root=root, download=True)
85
+ return static_file(filepath, root=self.download_root, download=True)
86
+
87
+ @self.bottle.route('/favicon.ico')
88
+ def favicon():
89
+ response.content_type = 'image/svg+xml'
90
+ svg_icon = '''<?xml version="1.0" encoding="UTF-8"?>
91
+ <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
92
+ <circle cx="16" cy="16" r="14" fill="#007bff"/>
93
+ <path d="M16 8a8 8 0 0 0-8 8h2a6 6 0 0 1 12 0h2a8 8 0 0 0-8-8z" fill="white"/>
94
+ <circle cx="16" cy="20" r="2" fill="white"/>
95
+ </svg>
96
+ '''
97
+ return svg_icon
98
+
99
+ def run(self):
100
+ http_server = WSGIRefServer(port=self.port)
101
+ 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)
102
+ self.bottle.run(server=http_server, quiet=self.quiet)
103
+
104
+ def set_index(self, filename='index.html', root='./', is_tpl=False, redirect_url=None, **kwargs):
105
+ self.index_root = root
106
+ self.index_filename = filename
107
+ self.is_tpl = is_tpl
108
+ self.redirect_url = redirect_url
109
+ self.tmp_args = kwargs
110
+
111
+ def set_static(self, root='./static'):
112
+ self.static_root = root
113
+
114
+ def set_download(self, root='./download'):
115
+ self.download_root = root
87
116
 
88
117
  def mount(self, context_path, app, **kwargs):
89
118
  self.bottle.mount(context_path, app, **kwargs)
90
119
 
91
- def init_bottle(app:Bottle=None, port=8888, quiet=False) -> CBottle:
120
+ def init_bottle(app:Bottle=None, port=_default_port, quiet=False) -> CBottle:
92
121
  bottle = app or Bottle()
93
122
  return CBottle(bottle, port, quiet)
94
123
 
@@ -100,7 +129,7 @@ class CustomWSGIHandler(WSGIRequestHandler):
100
129
 
101
130
  class WSGIRefServer(ServerAdapter):
102
131
 
103
- def __init__(self, host='0.0.0.0', port=8010):
132
+ def __init__(self, host='0.0.0.0', port=_default_port):
104
133
  super().__init__(host, port)
105
134
  self.server = None
106
135
 
@@ -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
@@ -11,6 +11,13 @@ from ctools import call, string_tools
11
11
  from ctools.thread_pool import thread_local
12
12
 
13
13
  """
14
+ class XXXX(BaseMixin):
15
+ __tablename__ = 't_xxx_info'
16
+ __table_args__ = {'comment': 'xxx信息表'}
17
+ server_content: Column = Column(String(50), nullable=True, default='', comment='123123')
18
+ server_ip: Column = Column(String(30), index=True)
19
+ user_id: Column = Column(BigInteger)
20
+
14
21
  database.init_db('postgresql://postgres:123456@192.168.3.107:32566/abc', default_schema='public', db_key='source', pool_size=100)
15
22
  with database.get_session('source') as s:
16
23
  s.execute(text('insert into xxx (name) values (:name)'), {'name': string_tools.get_random_str(5)})
@@ -30,6 +37,13 @@ def _init():
30
37
  global Base
31
38
  Base = declarative_base()
32
39
 
40
+ """
41
+ The string form of the URL is
42
+ dialect[+driver]://user:password@host/dbname[?key=value..]
43
+ where ``dialect`` is a database name such as ``mysql``, ``oracle``, ``postgresql``, etc.
44
+ and ``driver`` the name of a DBAPI such as ``psycopg2``, ``pyodbc``, ``cx_oracle``, etc. Alternatively
45
+ """
46
+
33
47
  # 密码里的@ 要替换成 %40
34
48
 
35
49
  # sqlite connect_args={"check_same_thread": False} db_url=sqlite:///{}.format(db_url)
@@ -49,6 +63,7 @@ def init_db(db_url: str, db_key: str='default', connect_args: dict={}, default_s
49
63
  sessionMakers[db_key] = sessionMaker
50
64
  inited_db[db_key] = True
51
65
  if default_schema: event.listen(engine, 'connect', lambda dbapi_connection, connection_record: _set_search_path(dbapi_connection, default_schema))
66
+ Base.metadata.create_all(engine)
52
67
 
53
68
  def _set_search_path(dbapi_connection, default_schema):
54
69
  with dbapi_connection.cursor() as cursor:
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/dict_wrapper.py CHANGED
@@ -7,7 +7,9 @@ class DictWrapper(dict):
7
7
  def __getattr__(self, key):
8
8
  res = self.get(key)
9
9
  if res is None:
10
- raise AttributeError(f"'{key}' not found in this Entity")
10
+ res = self.query_params.get(key)
11
+ if res is None:
12
+ raise AttributeError(f" ==>> {key} <<== Not Found In This Entity!!!")
11
13
  if isinstance(res, dict):
12
14
  return DictWrapper(res)
13
15
  return res
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
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.1
2
+ Name: gomyck-tools
3
+ Version: 1.2.9
4
+ Summary: A tools collection for python development by hao474798383
5
+ Home-page: https://blog.gomyck.com
6
+ Author: gomyck
7
+ Author-email: hao474798383@163.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: jsonpickle~=3.4.2
14
+ Requires-Dist: SQLAlchemy~=2.0.36
15
+ Requires-Dist: chardet~=5.2.0
16
+ Requires-Dist: psycopg2-binary~=2.9.10
17
+ Requires-Dist: croniter~=5.0.1
18
+ Requires-Dist: gmssl~=3.2.2
19
+ Requires-Dist: psutil~=6.1.0
20
+ Requires-Dist: jsonpath_ng~=1.7.0
21
+ Requires-Dist: bottle~=0.13.2
22
+ Requires-Dist: requests~=2.32.3
23
+ Requires-Dist: urllib3~=1.26.20
24
+ Requires-Dist: kafka-python~=2.0.2
25
+ Requires-Dist: bs4~=0.0.2
26
+ Requires-Dist: paho-mqtt~=2.1.0
27
+ Requires-Dist: fuzzywuzzy~=0.18.0
28
+ Requires-Dist: pymysql~=1.1.1
29
+ Requires-Dist: pyzipper==0.3.6
30
+ Requires-Dist: prometheus_client==0.21.1
31
+ Requires-Dist: paramiko==3.5.0
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
+
57
+
@@ -1,22 +1,27 @@
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=N4B9DL4k2y-eXzWkOAoeuqHJRSntYpJHWvDk33Ma0-w,6336
8
+ ctools/bottle_webserver.py,sha256=l7t_sN4ayywD1sR0kzuhGioOuaqGR9VhJh7e6Gbd6aE,4642
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
19
- ctools/dict_wrapper.py,sha256=6lZUyHomdn5QdjQTg7EL1sC_I6tOlh8ofMRQT3XGQjM,398
20
+ ctools/ctoken.py,sha256=NZSBGF3lJajJFLRIZoeXmpp8h5cKM0dAH2weySgeORc,882
21
+ ctools/czip.py,sha256=g-2s804R06Bnp19ByVsYeRbwx5HQf_KwrStvHimVyns,632
22
+ ctools/database.py,sha256=NVdYROhlQfElAoaUloiMeQLwxENS7etY8FTZKaW0rI8,6414
23
+ ctools/date_utils.py,sha256=h3rvlw_K2F0QTac2Zat_1us76R0P-Qj6_6NeQPfM3VE,1697
24
+ ctools/dict_wrapper.py,sha256=KYFeNcaaumFXVQePnh-z7q5ANK_Arapki1qOlQBoc3k,473
20
25
  ctools/douglas_rarefy.py,sha256=43WRjGGsQ_o1yPEXypA1Xv_yuo90RVo7qaYGRslx5gQ,4890
21
26
  ctools/download_tools.py,sha256=oJbG12Hojd0J17sAlvMU480P3abi4_AB9oZkEBGFPuo,1930
22
27
  ctools/enums.py,sha256=QbHa3j7j4-BDdwaga5Y0nYfA2uNSVJDHumYdIZdKVkM,118
@@ -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.9.dist-info/METADATA,sha256=WENt6ng1HZQLWvo5aTrEdurWq0HZc6AT-J092T1j62I,1354
60
+ gomyck_tools-1.2.9.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
61
+ gomyck_tools-1.2.9.dist-info/top_level.txt,sha256=-MiIH9FYRVKp1i5_SVRkaI-71WmF1sZSRrNWFU9ls3s,7
62
+ gomyck_tools-1.2.9.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.2)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,34 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: gomyck-tools
3
- Version: 1.2.7
4
- Summary: A ctools for python development by hao474798383
5
- Home-page: https://blog.gomyck.com
6
- Author: gomyck
7
- Author-email: hao474798383@163.com
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.8
12
- Description-Content-Type: text/markdown
13
- Requires-Dist: jsonpickle ~=3.4.2
14
- Requires-Dist: SQLAlchemy ~=2.0.36
15
- Requires-Dist: chardet ~=5.2.0
16
- Requires-Dist: psycopg2-binary ~=2.9.10
17
- Requires-Dist: croniter ~=5.0.1
18
- Requires-Dist: gmssl ~=3.2.2
19
- Requires-Dist: psutil ~=6.1.0
20
- Requires-Dist: jsonpath-ng ~=1.7.0
21
- Requires-Dist: bottle ~=0.13.2
22
- Requires-Dist: requests ~=2.32.3
23
- Requires-Dist: urllib3 ~=1.26.20
24
- Requires-Dist: kafka-python ~=2.0.2
25
- Requires-Dist: bs4 ~=0.0.2
26
- Requires-Dist: paho-mqtt ~=2.1.0
27
- Requires-Dist: fuzzywuzzy ~=0.18.0
28
- Requires-Dist: pymysql ~=1.1.1
29
- Requires-Dist: pyzipper ==0.3.6
30
- Requires-Dist: prometheus-client ==0.21.1
31
- Requires-Dist: paramiko ==3.5.0
32
- Requires-Dist: pyjwt ==2.10.1
33
-
34
- this package is for python development
File without changes