gomyck-tools 1.4.2__py3-none-any.whl → 1.4.4__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/ai/llm_chat.py +14 -1
- ctools/ai/llm_client.py +1 -1
- ctools/ai/tools/quick_tools.py +4 -2
- ctools/application.py +2 -2
- ctools/aspect.py +65 -0
- ctools/call.py +26 -37
- ctools/cid.py +3 -0
- ctools/database/database.py +16 -6
- ctools/ex.py +3 -0
- ctools/patch.py +88 -0
- ctools/pkg/__init__.py +4 -0
- ctools/pkg/dynamic_imp.py +38 -0
- ctools/stream/credis.py +9 -3
- ctools/sys_log.py +1 -1
- ctools/util/cklock.py +118 -0
- ctools/util/config_util.py +52 -0
- ctools/util/image_process.py +8 -0
- ctools/util/jb_cut.py +53 -0
- ctools/web/bottle_web_base.py +124 -73
- ctools/web/bottle_webserver.py +33 -33
- ctools/web/ctoken.py +78 -14
- {gomyck_tools-1.4.2.dist-info → gomyck_tools-1.4.4.dist-info}/METADATA +12 -11
- {gomyck_tools-1.4.2.dist-info → gomyck_tools-1.4.4.dist-info}/RECORD +27 -21
- ctools/auto/pacth.py +0 -74
- /ctools/{ai → util}/env_config.py +0 -0
- {gomyck_tools-1.4.2.dist-info → gomyck_tools-1.4.4.dist-info}/WHEEL +0 -0
- {gomyck_tools-1.4.2.dist-info → gomyck_tools-1.4.4.dist-info}/licenses/LICENSE +0 -0
- {gomyck_tools-1.4.2.dist-info → gomyck_tools-1.4.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2025/7/16 14:19'
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
config = load_config("application.ini")
|
|
8
|
+
print(config)
|
|
9
|
+
print(config.base.app_name)
|
|
10
|
+
print(config.base.version)
|
|
11
|
+
"""
|
|
12
|
+
from configparser import ConfigParser
|
|
13
|
+
|
|
14
|
+
cache = {}
|
|
15
|
+
|
|
16
|
+
class AttrNoneNamespace:
|
|
17
|
+
def __init__(self):
|
|
18
|
+
pass
|
|
19
|
+
def __setattr__(self, key, value):
|
|
20
|
+
super().__setattr__(key, value)
|
|
21
|
+
def __getattr__(self, item):
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
def _convert_value(value: str):
|
|
25
|
+
val = value.strip()
|
|
26
|
+
if val.lower() in ('true', 'yes', 'on'):
|
|
27
|
+
return True
|
|
28
|
+
if val.lower() in ('false', 'no', 'off'):
|
|
29
|
+
return False
|
|
30
|
+
if val.isdigit():
|
|
31
|
+
return int(val)
|
|
32
|
+
try:
|
|
33
|
+
return float(val)
|
|
34
|
+
except ValueError:
|
|
35
|
+
return val
|
|
36
|
+
|
|
37
|
+
def _config_to_object(config: ConfigParser):
|
|
38
|
+
result = AttrNoneNamespace()
|
|
39
|
+
for section in config.sections():
|
|
40
|
+
section_obj = AttrNoneNamespace()
|
|
41
|
+
for key, value in config.items(section):
|
|
42
|
+
setattr(section_obj, key, _convert_value(value))
|
|
43
|
+
setattr(result, section, section_obj)
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
def load_config(file_path):
|
|
47
|
+
if file_path in cache: return cache[file_path]
|
|
48
|
+
config = ConfigParser()
|
|
49
|
+
config.read(file_path)
|
|
50
|
+
cf = _config_to_object(config)
|
|
51
|
+
cache[file_path] = cf
|
|
52
|
+
return cf
|
ctools/util/image_process.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import base64
|
|
1
2
|
from io import BytesIO
|
|
2
3
|
|
|
3
4
|
from PIL import Image
|
|
@@ -25,3 +26,10 @@ def change_color(image_path, area=None, rgb_color=None):
|
|
|
25
26
|
img.save(img_bytes, format='JPEG')
|
|
26
27
|
img_binary = img_bytes.getvalue()
|
|
27
28
|
return img_binary
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def img2b64(img: Image, fmt="PNG"):
|
|
32
|
+
buf = BytesIO()
|
|
33
|
+
img.save(buf, format=fmt.upper())
|
|
34
|
+
return base64.b64encode(buf.getvalue()).decode("utf-8")
|
|
35
|
+
|
ctools/util/jb_cut.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2025/7/15 13:08'
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import jieba
|
|
9
|
+
from jieba import posseg as pseg
|
|
10
|
+
|
|
11
|
+
jqfx_exclude = ('ul', 'uj', 'uz', 'a', 'c', 'm', 'f', 'ad', 'an', 'r', 'q', 'u', 't', 'd', 'p', 'x')
|
|
12
|
+
|
|
13
|
+
def add_dict(dic_word: str = None, dic_path: str = None):
|
|
14
|
+
"""
|
|
15
|
+
添加自定义词库(自定义词库添加之后, 如果不适用全模式切词, 有时也不好使, 因为权重没有默认的高)
|
|
16
|
+
:param dic_word: 一个单词
|
|
17
|
+
:param dic_path: 单词表文件地址
|
|
18
|
+
单词表文件格式:
|
|
19
|
+
单词 词频 标签
|
|
20
|
+
单词1 3 i
|
|
21
|
+
单词2 3 i
|
|
22
|
+
单词3 3 i
|
|
23
|
+
"""
|
|
24
|
+
if dic_word: jieba.add_word(dic_word)
|
|
25
|
+
if dic_path: jieba.load_userdict(dic_path)
|
|
26
|
+
|
|
27
|
+
def add_freq(word: ()):
|
|
28
|
+
"""
|
|
29
|
+
添加词频
|
|
30
|
+
:param word: 一个单词
|
|
31
|
+
"""
|
|
32
|
+
jieba.suggest_freq(word, True)
|
|
33
|
+
|
|
34
|
+
def get_declare_type_word(word: str, word_flag=(), use_paddle=False, exclude_flag="x"):
|
|
35
|
+
#import paddle
|
|
36
|
+
#paddle.enable_static()
|
|
37
|
+
#sys.stdout = sys.__stdout__
|
|
38
|
+
#sys.stderr = sys.__stderr__
|
|
39
|
+
"""
|
|
40
|
+
获取声明类型的单词
|
|
41
|
+
:param word: 单词
|
|
42
|
+
:param word_flag: 标签
|
|
43
|
+
:param use_paddle: 是否使用 paddle
|
|
44
|
+
:param exclude_flag: 排除的标签
|
|
45
|
+
:return: 筛选之后的单词(数组)
|
|
46
|
+
"""
|
|
47
|
+
# linux 可以开放下面的语句, 并发执行分词, windows 不支持
|
|
48
|
+
# if platform.system() == 'Linux': jieba.enable_parallel(4)
|
|
49
|
+
ret = []
|
|
50
|
+
for w, flag in pseg.cut(word, use_paddle=use_paddle):
|
|
51
|
+
if (flag in word_flag or len(word_flag) == 0) and flag not in exclude_flag:
|
|
52
|
+
ret.append((w, flag))
|
|
53
|
+
return ret
|
ctools/web/bottle_web_base.py
CHANGED
|
@@ -5,74 +5,99 @@ from functools import wraps
|
|
|
5
5
|
import bottle
|
|
6
6
|
from bottle import response, Bottle, request
|
|
7
7
|
|
|
8
|
+
from ctools import call
|
|
8
9
|
from ctools.dict_wrapper import DictWrapper
|
|
9
10
|
from ctools.sys_log import flog as log
|
|
11
|
+
from ctools.util.cklock import try_lock
|
|
10
12
|
from ctools.web import ctoken
|
|
11
13
|
from ctools.web.api_result import R
|
|
12
14
|
|
|
13
15
|
bottle.BaseRequest.MEMFILE_MAX = 1024 * 1024 * 50
|
|
14
16
|
func_has_params = {}
|
|
15
|
-
|
|
17
|
+
app_cache = {}
|
|
16
18
|
|
|
17
19
|
class GlobalState:
|
|
18
20
|
lock = threading.Lock()
|
|
19
|
-
withOutLoginURI =
|
|
21
|
+
withOutLoginURI = {
|
|
20
22
|
'/',
|
|
21
23
|
'/index',
|
|
22
24
|
'/login',
|
|
23
25
|
'/favicon.ico',
|
|
24
|
-
|
|
25
|
-
allowRemoteCallURI =
|
|
26
|
-
|
|
27
|
-
]
|
|
26
|
+
}
|
|
27
|
+
allowRemoteCallURI = set()
|
|
28
|
+
auth_ignore_func = set()
|
|
28
29
|
token = {}
|
|
29
30
|
interceptors = []
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
32
|
+
def join_path(*parts):
|
|
33
|
+
"""拼接 url"""
|
|
34
|
+
cleaned_parts = [p.strip('/') for p in parts if p and p.strip('/')]
|
|
35
|
+
return '/' + '/'.join(cleaned_parts)
|
|
36
|
+
|
|
37
|
+
@call.once
|
|
38
|
+
def cache_white_list(app):
|
|
39
|
+
"""缓存白名单"""
|
|
40
|
+
for route in app.routes:
|
|
41
|
+
real_func = route.config.get('mountpoint.target')
|
|
42
|
+
if not real_func: continue
|
|
43
|
+
for method, routes in real_func.router.static.items():
|
|
44
|
+
for path, tuples in routes.items():
|
|
45
|
+
req_func = inspect.getmodule(tuples[0].callback).__name__ + "." + tuples[0].callback.__name__
|
|
46
|
+
if req_func in GlobalState.auth_ignore_func:
|
|
47
|
+
print("add white list: {}".format(join_path(app.context_path, real_func.context_path, path)))
|
|
48
|
+
GlobalState.withOutLoginURI.add(join_path(app.context_path, real_func.context_path, path))
|
|
49
|
+
|
|
50
|
+
def init_app(context_path="/", main_app=False):
|
|
51
|
+
with try_lock(block=True):
|
|
52
|
+
cache_app = app_cache.get(context_path)
|
|
53
|
+
if cache_app: return cache_app
|
|
54
|
+
app = Bottle()
|
|
55
|
+
app_cache[context_path] = app
|
|
56
|
+
app.context_path = context_path if context_path else "/"
|
|
57
|
+
|
|
58
|
+
def init_main_app():
|
|
59
|
+
@app.hook('before_request')
|
|
60
|
+
def before_request():
|
|
61
|
+
if request.path.startswith('/static') or request.path in GlobalState.withOutLoginURI: return
|
|
62
|
+
for interceptor in GlobalState.interceptors:
|
|
63
|
+
res: R = interceptor['func']()
|
|
64
|
+
if res.code != 200: bottle.abort(res.code, res.message)
|
|
65
|
+
|
|
66
|
+
@app.error(401)
|
|
67
|
+
def unauthorized(error):
|
|
68
|
+
response.status = 401
|
|
69
|
+
log.error("系统未授权: {} {} {}".format(error.body, request.method, request.fullpath))
|
|
70
|
+
return R.error(resp=R.Code.cus_code(401, "系统未授权! {}".format(error.body)))
|
|
71
|
+
|
|
72
|
+
@app.error(403)
|
|
73
|
+
def unauthorized(error):
|
|
74
|
+
response.status = 403
|
|
75
|
+
log.error("访问受限: {} {} {}".format(error.body, request.method, request.fullpath))
|
|
76
|
+
return R.error(resp=R.Code.cus_code(403, "访问受限: {}".format(error.body)))
|
|
77
|
+
|
|
78
|
+
@app.error(404)
|
|
79
|
+
def not_found(error):
|
|
80
|
+
response.status = 404
|
|
81
|
+
log.error("404 not found : {} {} {}".format(error.body, request.method, request.fullpath))
|
|
82
|
+
return R.error(resp=R.Code.cus_code(404, "资源未找到: {}".format(error.body)))
|
|
83
|
+
|
|
84
|
+
@app.error(405)
|
|
85
|
+
def method_not_allow(error):
|
|
86
|
+
response.status = 405
|
|
87
|
+
log.error("请求方法错误: {} {} {}".format(error.status_line, request.method, request.fullpath))
|
|
88
|
+
return R.error(resp=R.Code.cus_code(405, '请求方法错误: {}'.format(error.status_line)))
|
|
89
|
+
|
|
90
|
+
@app.error(500)
|
|
91
|
+
def internal_error(error):
|
|
92
|
+
response.status = 500
|
|
93
|
+
log.error("系统发生错误: {} {} {}".format(error.body, request.method, request.fullpath))
|
|
94
|
+
return R.error(msg='系统发生错误: {}'.format(error.exception))
|
|
95
|
+
|
|
96
|
+
@app.hook('after_request')
|
|
97
|
+
def after_request():
|
|
98
|
+
enable_cors()
|
|
99
|
+
|
|
100
|
+
if main_app: init_main_app()
|
|
76
101
|
app.install(params_resolve)
|
|
77
102
|
return app
|
|
78
103
|
|
|
@@ -88,27 +113,45 @@ def enable_cors():
|
|
|
88
113
|
# annotation
|
|
89
114
|
def before_intercept(order=0):
|
|
90
115
|
def decorator(func):
|
|
116
|
+
for interceptor in GlobalState.interceptors:
|
|
117
|
+
if interceptor['func'].__name__ == func.__name__:
|
|
118
|
+
log.info("duplicate interceptor: {}".format(func.__name__))
|
|
119
|
+
return
|
|
91
120
|
log.info("add before interceptor: {}".format(func.__name__))
|
|
92
121
|
GlobalState.interceptors.append({'order': order, 'func': func})
|
|
93
122
|
GlobalState.interceptors = sorted(GlobalState.interceptors, key=lambda x: x['order'])
|
|
94
123
|
|
|
95
124
|
return decorator
|
|
96
125
|
|
|
126
|
+
# annotation
|
|
127
|
+
def auth_ignore(func):
|
|
128
|
+
"""忽略登录验证的接口"""
|
|
129
|
+
ignore_req_func = inspect.getmodule(func).__name__ + "." + func.__name__
|
|
130
|
+
if ignore_req_func in GlobalState.auth_ignore_func: raise Exception("duplicate ignore func: {}".format(ignore_req_func))
|
|
131
|
+
GlobalState.auth_ignore_func.add(ignore_req_func)
|
|
132
|
+
@wraps(func)
|
|
133
|
+
def decorated(*args, **kwargs):
|
|
134
|
+
return func(*args, **kwargs)
|
|
135
|
+
return decorated
|
|
97
136
|
|
|
98
137
|
# annotation
|
|
99
138
|
def rule(key):
|
|
100
139
|
def return_func(func):
|
|
101
140
|
@wraps(func)
|
|
102
141
|
def decorated(*args, **kwargs):
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
142
|
+
rules = ctoken.get_token_attr("rules") or []
|
|
143
|
+
if _match_rule_by_prefix(key, rules):
|
|
144
|
+
return func(*args, **kwargs)
|
|
145
|
+
else:
|
|
146
|
+
return R.error("权限不足, 请联系管理员: {}".format(key))
|
|
108
147
|
return decorated
|
|
109
|
-
|
|
110
148
|
return return_func
|
|
111
149
|
|
|
150
|
+
def _match_rule_by_prefix(key, rules):
|
|
151
|
+
for r in rules:
|
|
152
|
+
if key.startswith(r):
|
|
153
|
+
return True
|
|
154
|
+
return False
|
|
112
155
|
|
|
113
156
|
# annotation or plugins, has auto install, don't need to call
|
|
114
157
|
def params_resolve(func):
|
|
@@ -124,45 +167,59 @@ def params_resolve(func):
|
|
|
124
167
|
return func(*args, **kwargs)
|
|
125
168
|
else:
|
|
126
169
|
func_has_params[request.fullpath] = True
|
|
127
|
-
if request.method == 'GET':
|
|
170
|
+
if request.method == 'GET' or request.method == 'DELETE':
|
|
128
171
|
queryStr = request.query.decode('utf-8')
|
|
129
172
|
page_info = PageInfo(
|
|
130
173
|
page_size=10 if request.headers.get('page_size') is None else int(request.headers.get('page_size')),
|
|
131
174
|
page_index=1 if request.headers.get('page_index') is None else int(request.headers.get('page_index'))
|
|
132
175
|
)
|
|
176
|
+
queryStr = auto_exchange(func, queryStr)
|
|
133
177
|
queryStr.page_info = page_info
|
|
134
178
|
return func(params=queryStr, *args, **kwargs)
|
|
135
|
-
elif request.method == 'POST':
|
|
179
|
+
elif request.method == 'POST' or request.method == 'PUT':
|
|
136
180
|
query_params = request.query.decode('utf-8')
|
|
137
181
|
content_type = request.get_header('content-type')
|
|
138
182
|
if content_type == 'application/json':
|
|
139
183
|
params = request.json or {}
|
|
140
184
|
dict_wrapper = DictWrapper(params)
|
|
141
185
|
dict_wrapper.update(query_params.dict)
|
|
142
|
-
return func(params=dict_wrapper, *args, **kwargs)
|
|
186
|
+
return func(params=auto_exchange(func, dict_wrapper), *args, **kwargs)
|
|
143
187
|
elif 'multipart/form-data' in content_type:
|
|
144
188
|
form_data = request.forms.decode()
|
|
145
189
|
form_files = request.files.decode()
|
|
146
190
|
dict_wrapper = DictWrapper(form_data)
|
|
147
191
|
dict_wrapper.update(query_params.dict)
|
|
148
192
|
dict_wrapper.files = form_files
|
|
149
|
-
return func(params=dict_wrapper, *args, **kwargs)
|
|
193
|
+
return func(params=auto_exchange(func, dict_wrapper), *args, **kwargs)
|
|
150
194
|
elif 'application/x-www-form-urlencoded' in content_type:
|
|
151
195
|
params = request.forms.decode()
|
|
152
196
|
dict_wrapper = DictWrapper(params.dict)
|
|
153
197
|
dict_wrapper.update(query_params.dict)
|
|
154
|
-
return func(params=dict_wrapper, *args, **kwargs)
|
|
198
|
+
return func(params=auto_exchange(func, dict_wrapper), *args, **kwargs)
|
|
155
199
|
elif 'text/plain' in content_type:
|
|
156
200
|
params = request.body.read().decode('utf-8')
|
|
157
201
|
dict_wrapper = DictWrapper({'body': params})
|
|
158
202
|
dict_wrapper.update(query_params.dict)
|
|
159
|
-
return func(params=dict_wrapper, *args, **kwargs)
|
|
203
|
+
return func(params=auto_exchange(func, dict_wrapper), *args, **kwargs)
|
|
160
204
|
else:
|
|
161
205
|
return func(*args, **kwargs)
|
|
162
206
|
|
|
163
207
|
return decorated
|
|
164
208
|
|
|
165
|
-
|
|
209
|
+
# 自动转换参数类型
|
|
210
|
+
def auto_exchange(func, dict_wrapper):
|
|
211
|
+
model_class = func.__annotations__.get('params')
|
|
212
|
+
if model_class:
|
|
213
|
+
try:
|
|
214
|
+
model_instance = model_class(**dict_wrapper)
|
|
215
|
+
return model_instance
|
|
216
|
+
except Exception as e:
|
|
217
|
+
log.exception(e)
|
|
218
|
+
return dict_wrapper
|
|
219
|
+
else:
|
|
220
|
+
return dict_wrapper
|
|
221
|
+
|
|
222
|
+
# 分页信息对象
|
|
166
223
|
class PageInfo:
|
|
167
224
|
def __init__(self, page_size, page_index):
|
|
168
225
|
self.page_size = page_size
|
|
@@ -170,13 +227,7 @@ class PageInfo:
|
|
|
170
227
|
|
|
171
228
|
|
|
172
229
|
# 通用的鉴权方法
|
|
173
|
-
def common_auth_verify(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
auth_token = request.get_header('Authorization')
|
|
177
|
-
if auth_token is None:
|
|
178
|
-
auth_token = request.get_cookie('Authorization')
|
|
179
|
-
payload = ctoken.get_payload(auth_token, aes_key)
|
|
180
|
-
if payload:
|
|
181
|
-
return R.ok(to_json_str=False)
|
|
230
|
+
def common_auth_verify() -> R:
|
|
231
|
+
valid = ctoken.is_valid()
|
|
232
|
+
if valid: return R.ok(to_json_str=False)
|
|
182
233
|
return R.error(resp=R.Code.cus_code(401, "请登录!"), to_json_str=False)
|
ctools/web/bottle_webserver.py
CHANGED
|
@@ -5,48 +5,36 @@ from wsgiref.simple_server import WSGIServer, WSGIRequestHandler, make_server
|
|
|
5
5
|
from bottle import ServerAdapter, Bottle, template, static_file, abort, redirect, response
|
|
6
6
|
|
|
7
7
|
from ctools import sys_info
|
|
8
|
+
from ctools.pkg.dynamic_imp import load_modules_from_package
|
|
9
|
+
from ctools.web.bottle_web_base import cache_white_list
|
|
8
10
|
|
|
9
|
-
"""
|
|
10
|
-
module_names = list(globals().keys())
|
|
11
|
-
def get_modules():
|
|
12
|
-
mods = []
|
|
13
|
-
for modname in module_names:
|
|
14
|
-
if modname == 'base' or modname == 'online' or modname.startswith('__') or modname == 'importlib': continue
|
|
15
|
-
module = globals()[modname]
|
|
16
|
-
mods.append(module)
|
|
17
|
-
return mods
|
|
18
|
-
|
|
19
|
-
def get_ws_modules():
|
|
20
|
-
from . import websocket
|
|
21
|
-
return [websocket]
|
|
22
|
-
"""
|
|
23
11
|
|
|
24
12
|
"""
|
|
25
|
-
|
|
26
|
-
from ctools
|
|
13
|
+
import controllers
|
|
14
|
+
from ctools import patch
|
|
15
|
+
from ctools.database import database
|
|
16
|
+
from ctools.util.config_util import load_config
|
|
17
|
+
from ctools.web import bottle_web_base, bottle_webserver
|
|
18
|
+
from key_word_cloud.db_core.db_init import init_partitions
|
|
19
|
+
from patch_manager import patch_funcs
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
app = bottle_web_base.init_app('子模块写 context_path, 主模块就不用写任何东西')
|
|
21
|
+
database.init_db('postgresql://postgres:123123@192.168.xx.xx:5432/xxx', default_schema='xxx', auto_gen_table=False, echo=False)
|
|
30
22
|
|
|
31
|
-
|
|
32
|
-
@bottle_web_base.before_intercept(0)
|
|
33
|
-
def token_check():
|
|
34
|
-
return bottle_web_base.common_auth_verify(secret_key)
|
|
23
|
+
config = load_config('application.ini')
|
|
35
24
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return R.ok(ctoken.gen_token({'username': 'xxx'}, secret_key, 3600))
|
|
25
|
+
patch.sync_version(config.base.app_name, config.base.version, patch_funcs)
|
|
26
|
+
init_partitions()
|
|
39
27
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
28
|
+
app = bottle_web_base.init_app("/api", True)
|
|
29
|
+
|
|
30
|
+
@bottle_web_base.before_intercept(0)
|
|
31
|
+
def token_check():
|
|
32
|
+
return bottle_web_base.common_auth_verify(config.base.secret_key)
|
|
44
33
|
|
|
45
34
|
if __name__ == '__main__':
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
main_app.run()
|
|
35
|
+
main_server = bottle_webserver.init_bottle(app)
|
|
36
|
+
main_server.auto_mount(controllers)
|
|
37
|
+
main_server.run()
|
|
50
38
|
"""
|
|
51
39
|
|
|
52
40
|
_default_port = 8888
|
|
@@ -101,6 +89,7 @@ class CBottle:
|
|
|
101
89
|
def run(self):
|
|
102
90
|
http_server = WSGIRefServer(port=self.port)
|
|
103
91
|
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)
|
|
92
|
+
cache_white_list(self.bottle)
|
|
104
93
|
self.bottle.run(server=http_server, quiet=self.quiet)
|
|
105
94
|
|
|
106
95
|
def set_index(self, filename='index.html', root='./', is_tpl=False, redirect_url=None, **kwargs):
|
|
@@ -120,6 +109,17 @@ class CBottle:
|
|
|
120
109
|
if not context_path: return
|
|
121
110
|
self.bottle.mount(context_path, app, **kwargs)
|
|
122
111
|
|
|
112
|
+
def auto_mount(self, package, exclude=None, recursive=True):
|
|
113
|
+
for module in load_modules_from_package(package, exclude, recursive):
|
|
114
|
+
if self.bottle.context_path != '/':
|
|
115
|
+
if module.app.context_path == '/':
|
|
116
|
+
ctx_path = self.bottle.context_path
|
|
117
|
+
else:
|
|
118
|
+
ctx_path = self.bottle.context_path + module.app.context_path
|
|
119
|
+
else:
|
|
120
|
+
ctx_path = module.app.context_path
|
|
121
|
+
print("mount: %s on %s" % (ctx_path, module.__name__))
|
|
122
|
+
self.bottle.mount(ctx_path, module.app)
|
|
123
123
|
|
|
124
124
|
def init_bottle(app: Bottle = None, port=_default_port, quiet=False) -> CBottle:
|
|
125
125
|
bottle = app or Bottle()
|
ctools/web/ctoken.py
CHANGED
|
@@ -8,32 +8,96 @@ import time
|
|
|
8
8
|
import jwt
|
|
9
9
|
from bottle import request
|
|
10
10
|
|
|
11
|
+
from ctools import cid
|
|
11
12
|
from ctools.dict_wrapper import DictWrapper
|
|
12
13
|
|
|
13
|
-
token_header = 'Authorization'
|
|
14
14
|
|
|
15
|
+
class CToken:
|
|
16
|
+
token_audience = ["gomyck"]
|
|
17
|
+
token_secret_key = 'gomyck123'
|
|
18
|
+
token_header = 'Authorization'
|
|
15
19
|
|
|
16
|
-
def gen_token(payload
|
|
17
|
-
payload.update({'exp': time.time() + expired})
|
|
18
|
-
|
|
20
|
+
def gen_token(userid, username, payload={}, expired: int = 3600, rules= []) -> str:
|
|
21
|
+
if expired and expired > 0: payload.update({'exp': time.time() + expired})
|
|
22
|
+
payload.update({'iss': "gomyck", 'jti': cid.get_snowflake_id_str(), 'nbf': time.time(), 'iat': time.time()})
|
|
23
|
+
payload.update({'rules': rules})
|
|
24
|
+
payload.update({'uid': userid})
|
|
25
|
+
payload.update({'sub': username})
|
|
26
|
+
payload.update({'aud': CToken.token_audience})
|
|
27
|
+
payload.update({'rules': rules})
|
|
28
|
+
return jwt.encode(payload, CToken.token_secret_key, algorithm='HS256')
|
|
19
29
|
|
|
20
|
-
|
|
21
|
-
def get_payload(token, secret_key):
|
|
30
|
+
def get_payload(token=None):
|
|
22
31
|
try:
|
|
23
|
-
|
|
32
|
+
if token is None: token = get_token()
|
|
33
|
+
payload = jwt.decode(token, options={"verify_signature": False}, algorithms=['HS256'])
|
|
24
34
|
return DictWrapper(payload)
|
|
25
35
|
except Exception as e:
|
|
26
36
|
return None
|
|
27
37
|
|
|
38
|
+
def get_token_payload(token=None):
|
|
39
|
+
if token is None: token = get_token()
|
|
40
|
+
return get_payload(token)
|
|
41
|
+
|
|
42
|
+
def is_valid(token=None):
|
|
43
|
+
"""
|
|
44
|
+
判断token是否有效
|
|
45
|
+
:param token: token 信息
|
|
46
|
+
:return: 是否有效
|
|
47
|
+
"""
|
|
48
|
+
if token is None: token = get_token()
|
|
49
|
+
if token is None: return False
|
|
50
|
+
try:
|
|
51
|
+
jwt.decode(token, CToken.token_secret_key, algorithms=['HS256'], audience=CToken.token_audience)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print(e)
|
|
54
|
+
return False
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
def get_user_name():
|
|
58
|
+
"""
|
|
59
|
+
获取用户名称
|
|
60
|
+
:return: 用户ID
|
|
61
|
+
"""
|
|
62
|
+
token_obj = get_token_attr("sub")
|
|
63
|
+
return token_obj
|
|
64
|
+
|
|
65
|
+
def get_user_id():
|
|
66
|
+
"""
|
|
67
|
+
获取用户ID
|
|
68
|
+
:return: 用户ID
|
|
69
|
+
"""
|
|
70
|
+
token_obj = get_token_attr("uid")
|
|
71
|
+
return token_obj
|
|
72
|
+
|
|
73
|
+
def get_token_id():
|
|
74
|
+
"""
|
|
75
|
+
获取token id
|
|
76
|
+
:return: token id
|
|
77
|
+
"""
|
|
78
|
+
token_obj = get_token_attr("jti")
|
|
79
|
+
return token_obj
|
|
28
80
|
|
|
29
|
-
def
|
|
30
|
-
|
|
81
|
+
def get_app_flag() -> []:
|
|
82
|
+
"""
|
|
83
|
+
获取服务标识
|
|
84
|
+
:return: 服务标识 []
|
|
85
|
+
"""
|
|
86
|
+
token_obj = get_token_attr("aud")
|
|
87
|
+
return token_obj
|
|
31
88
|
|
|
89
|
+
def get_token_attr(attr):
|
|
90
|
+
token_obj = get_token_payload()
|
|
91
|
+
return getattr(token_obj, attr, None)
|
|
32
92
|
|
|
33
|
-
def
|
|
34
|
-
|
|
93
|
+
def get_token():
|
|
94
|
+
auth_token = request.get_header(CToken.token_header)
|
|
95
|
+
if auth_token is None:
|
|
96
|
+
auth_token = request.get_cookie(CToken.token_header)
|
|
97
|
+
return auth_token
|
|
35
98
|
|
|
36
99
|
# if __name__ == '__main__':
|
|
37
|
-
# token = gen_token(
|
|
38
|
-
#
|
|
39
|
-
# print(
|
|
100
|
+
# token = gen_token(123, username='xxx', expired=0)
|
|
101
|
+
# print(token)
|
|
102
|
+
# print(get_payload(token))
|
|
103
|
+
# print(is_valid(token))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gomyck-tools
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.4
|
|
4
4
|
Summary: A tools collection for python development by hao474798383
|
|
5
5
|
Author-email: gomyck <hao474798383@163.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -15,7 +15,7 @@ Requires-Dist: croniter~=5.0.1
|
|
|
15
15
|
Requires-Dist: gmssl~=3.2.2
|
|
16
16
|
Requires-Dist: psutil~=6.1.0
|
|
17
17
|
Requires-Dist: jsonpath_ng~=1.7.0
|
|
18
|
-
Requires-Dist: bottle~=0.13.
|
|
18
|
+
Requires-Dist: bottle~=0.13.4
|
|
19
19
|
Requires-Dist: requests~=2.32.3
|
|
20
20
|
Requires-Dist: urllib3~=1.26.20
|
|
21
21
|
Requires-Dist: kafka-python~=2.0.2
|
|
@@ -30,15 +30,16 @@ Requires-Dist: pyjwt==2.10.1
|
|
|
30
30
|
Requires-Dist: cryptography==44.0.2
|
|
31
31
|
Requires-Dist: redis==5.2.1
|
|
32
32
|
Requires-Dist: uvloop>=0.21.0
|
|
33
|
-
Provides-Extra:
|
|
34
|
-
Requires-Dist:
|
|
35
|
-
Requires-Dist:
|
|
36
|
-
|
|
37
|
-
Requires-Dist:
|
|
38
|
-
|
|
39
|
-
Requires-Dist:
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
Provides-Extra: kwc
|
|
34
|
+
Requires-Dist: imageio>=2.37.0; extra == "kwc"
|
|
35
|
+
Requires-Dist: wordcloud>=1.9.4; extra == "kwc"
|
|
36
|
+
Requires-Dist: jieba==0.42.1; extra == "kwc"
|
|
37
|
+
Requires-Dist: paddlepaddle==2.6.1; extra == "kwc"
|
|
38
|
+
Provides-Extra: cut
|
|
39
|
+
Requires-Dist: jieba==0.42.1; extra == "cut"
|
|
40
|
+
Requires-Dist: paddlepaddle==2.6.1; extra == "cut"
|
|
41
|
+
Provides-Extra: ml
|
|
42
|
+
Requires-Dist: scikit-learn>=1.7.1; extra == "ml"
|
|
42
43
|
Dynamic: license-file
|
|
43
44
|
|
|
44
45
|
# Gomyck-Tools
|