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 +10 -0
- ctools/bottle_web_base.py +33 -32
- ctools/bottle_webserver.py +57 -28
- ctools/bottle_websocket.py +13 -13
- ctools/cftp.py +74 -0
- ctools/coord_trans.py +127 -0
- ctools/credis.py +111 -0
- ctools/czip.py +19 -0
- ctools/database.py +15 -0
- ctools/date_utils.py +28 -29
- ctools/dict_wrapper.py +3 -1
- ctools/metrics.py +1 -1
- ctools/sm_tools.py +1 -1
- gomyck_tools-1.2.9.dist-info/METADATA +57 -0
- {gomyck_tools-1.2.7.dist-info → gomyck_tools-1.2.9.dist-info}/RECORD +18 -14
- {gomyck_tools-1.2.7.dist-info → gomyck_tools-1.2.9.dist-info}/WHEEL +1 -1
- gomyck_tools-1.2.7.dist-info/METADATA +0 -34
- /ctools/{token.py → ctoken.py} +0 -0
- {gomyck_tools-1.2.7.dist-info → gomyck_tools-1.2.9.dist-info}/top_level.txt +0 -0
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
|
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(
|
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(
|
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
|
55
|
-
|
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(
|
61
|
+
return R.error(resp=R.Code.cus_code(405, '请求方法错误: {}'.format(error.status_line)))
|
61
62
|
|
62
63
|
@app.error(500)
|
63
|
-
def
|
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
|
-
|
130
|
-
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
params
|
137
|
-
|
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 =
|
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)
|
ctools/bottle_webserver.py
CHANGED
@@ -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(
|
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'},
|
38
|
+
return R.ok(token.gen_token({'username': 'xxx'}, secret_key, 3600))
|
37
39
|
|
38
|
-
@app.get('/
|
40
|
+
@app.get('/demo')
|
39
41
|
@bottle_web_base.rule('DOC:DOWNLOAD')
|
40
|
-
def
|
41
|
-
print(
|
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=
|
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
|
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=
|
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=
|
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=
|
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=
|
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
|
|
ctools/bottle_websocket.py
CHANGED
@@ -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,
|
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=
|
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=
|
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=
|
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
|
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
|
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
|
36
|
-
return
|
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
|
39
|
-
|
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
|
42
|
-
|
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
|
-
|
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
ctools/sm_tools.py
CHANGED
@@ -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=
|
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=
|
8
|
-
ctools/bottle_webserver.py,sha256=
|
9
|
-
ctools/bottle_websocket.py,sha256=
|
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/
|
18
|
-
ctools/
|
19
|
-
ctools/
|
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=
|
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=
|
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.
|
56
|
-
gomyck_tools-1.2.
|
57
|
-
gomyck_tools-1.2.
|
58
|
-
gomyck_tools-1.2.
|
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,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
|
/ctools/{token.py → ctoken.py}
RENAMED
File without changes
|
File without changes
|