gomyck-tools 1.0.1__py3-none-any.whl → 1.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ctools/api_result.py +51 -0
- ctools/bottle_server.py +34 -1
- ctools/call.py +3 -3
- ctools/cjson.py +3 -10
- ctools/database.py +8 -0
- ctools/douglas_rarefy.py +130 -0
- ctools/ex.py +1 -1
- ctools/http_utils.py +2 -2
- ctools/process_pool.py +35 -0
- ctools/sys_log.py +26 -12
- ctools/web_base.py +143 -0
- {gomyck_tools-1.0.1.dist-info → gomyck_tools-1.0.3.dist-info}/METADATA +2 -2
- {gomyck_tools-1.0.1.dist-info → gomyck_tools-1.0.3.dist-info}/RECORD +15 -13
- ctools/log.py +0 -28
- ctools/mvc.py +0 -56
- {gomyck_tools-1.0.1.dist-info → gomyck_tools-1.0.3.dist-info}/WHEEL +0 -0
- {gomyck_tools-1.0.1.dist-info → gomyck_tools-1.0.3.dist-info}/top_level.txt +0 -0
ctools/api_result.py
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
from ctools import cjson
|
2
|
+
|
3
|
+
cjson.str_value_keys = [
|
4
|
+
"obj_id",
|
5
|
+
"script_id",
|
6
|
+
"template_id"
|
7
|
+
]
|
8
|
+
|
9
|
+
|
10
|
+
class _ResEnum(object):
|
11
|
+
def __init__(self, code: int, message: str):
|
12
|
+
self.code = code
|
13
|
+
self.message = message
|
14
|
+
|
15
|
+
def __eq__(self, o: object) -> bool:
|
16
|
+
return self.code == o
|
17
|
+
|
18
|
+
class R(object):
|
19
|
+
class Code:
|
20
|
+
|
21
|
+
@staticmethod
|
22
|
+
def cus_code(code, msg):
|
23
|
+
return _ResEnum(code, msg)
|
24
|
+
|
25
|
+
SUCCESS = _ResEnum(200, "成功")
|
26
|
+
FAIL = _ResEnum(400, "失败")
|
27
|
+
ERROR = _ResEnum(500, "异常")
|
28
|
+
|
29
|
+
def __init__(self, code: int, message: str, data=""):
|
30
|
+
self.code = code
|
31
|
+
self.message = message
|
32
|
+
self.data = data
|
33
|
+
|
34
|
+
def _to_json(self):
|
35
|
+
return cjson.unify_to_str(cjson.dumps(self))
|
36
|
+
|
37
|
+
@staticmethod
|
38
|
+
def parser(r_json: str):
|
39
|
+
return R(**cjson.loads(r_json))
|
40
|
+
|
41
|
+
@staticmethod
|
42
|
+
def ok(data=None, resp=Code.SUCCESS, msg=None):
|
43
|
+
return R(resp.code, msg if msg is not None else resp.message, data)._to_json()
|
44
|
+
|
45
|
+
@staticmethod
|
46
|
+
def fail(msg=None, resp=Code.FAIL, data=None):
|
47
|
+
return R(resp.code, msg if msg is not None else resp.message, data)._to_json()
|
48
|
+
|
49
|
+
@staticmethod
|
50
|
+
def error(msg=None, resp=Code.ERROR, data=None):
|
51
|
+
return R(resp.code, msg if msg is not None else resp.message, data)._to_json()
|
ctools/bottle_server.py
CHANGED
@@ -4,6 +4,39 @@ from wsgiref.simple_server import WSGIServer, WSGIRequestHandler, make_server
|
|
4
4
|
from geventwebsocket.handler import WebSocketHandler
|
5
5
|
from ctools import sys_log
|
6
6
|
|
7
|
+
"""
|
8
|
+
app = init_app('/doc_download')
|
9
|
+
|
10
|
+
@app.get('/queryList')
|
11
|
+
@mvc.parameter_handler()
|
12
|
+
@rule('DOC:DOWNLOAD')
|
13
|
+
def query_list(params, pageInfo):
|
14
|
+
"""
|
15
|
+
|
16
|
+
"""
|
17
|
+
module_names = list(globals().keys())
|
18
|
+
def get_modules():
|
19
|
+
mods = []
|
20
|
+
for modname in module_names:
|
21
|
+
if modname == 'base' or modname == 'online' or modname.startswith('__') or modname == 'importlib': continue
|
22
|
+
module = globals()[modname]
|
23
|
+
mods.append(module)
|
24
|
+
return mods
|
25
|
+
|
26
|
+
def get_ws_modules():
|
27
|
+
from . import websocket
|
28
|
+
return [websocket]
|
29
|
+
"""
|
30
|
+
|
31
|
+
"""
|
32
|
+
http_app = Bottle()
|
33
|
+
for module in controller.get_modules():
|
34
|
+
log.debug('正在挂载http路由: {}'.format(module.app.context_path))
|
35
|
+
http_app.mount(module.app.context_path, module.app)
|
36
|
+
http_server = bottle_server.WSGIRefServer(port=application.Server.port)
|
37
|
+
http_app.run(server=http_server, quiet=True)
|
38
|
+
"""
|
39
|
+
|
7
40
|
class ThreadedWSGIServer(ThreadingMixIn, WSGIServer): pass
|
8
41
|
|
9
42
|
class CustomWSGIHandler(WSGIRequestHandler):
|
@@ -36,7 +69,7 @@ class WSGIRefServer(ServerAdapter):
|
|
36
69
|
|
37
70
|
class WebSocketServer(ServerAdapter):
|
38
71
|
|
39
|
-
def __init__(self, host='0.0.0.0', port=
|
72
|
+
def __init__(self, host='0.0.0.0', port=8011):
|
40
73
|
super().__init__(host, port)
|
41
74
|
self.server = None
|
42
75
|
|
ctools/call.py
CHANGED
@@ -3,7 +3,7 @@ import threading
|
|
3
3
|
import time
|
4
4
|
from functools import wraps
|
5
5
|
|
6
|
-
|
6
|
+
# annotation
|
7
7
|
def once(func):
|
8
8
|
"""
|
9
9
|
decorator to initialize a function once
|
@@ -24,7 +24,7 @@ def once(func):
|
|
24
24
|
|
25
25
|
return wrapper
|
26
26
|
|
27
|
-
|
27
|
+
# annotation
|
28
28
|
def init(func):
|
29
29
|
"""
|
30
30
|
decorator to initialize a function automic
|
@@ -38,7 +38,7 @@ def init(func):
|
|
38
38
|
|
39
39
|
return wrapper
|
40
40
|
|
41
|
-
|
41
|
+
# annotation
|
42
42
|
def schd(interval_seconds, start_by_call: bool = False):
|
43
43
|
start_flag = False
|
44
44
|
run_flag = False
|
ctools/cjson.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
import jsonpickle
|
2
2
|
|
3
|
+
# 需要转换成str的属性
|
4
|
+
str_value_keys = []
|
3
5
|
jsonpickle.set_preferred_backend('json')
|
4
6
|
jsonpickle.set_encoder_options('json', ensure_ascii=False)
|
5
|
-
jsonpickle.set_decoder_options('json'
|
6
|
-
|
7
|
+
jsonpickle.set_decoder_options('json')
|
7
8
|
|
8
9
|
def dumps(obj) -> str:
|
9
10
|
"""
|
@@ -15,7 +16,6 @@ def dumps(obj) -> str:
|
|
15
16
|
if type(obj) == str: return obj
|
16
17
|
return f'{jsonpickle.encode(obj, unpicklable=False)}'
|
17
18
|
|
18
|
-
|
19
19
|
def loads(json_str: str) -> dict:
|
20
20
|
"""
|
21
21
|
将json字符串转换为对象
|
@@ -24,11 +24,6 @@ def loads(json_str: str) -> dict:
|
|
24
24
|
"""
|
25
25
|
return jsonpickle.decode(json_str)
|
26
26
|
|
27
|
-
|
28
|
-
# 需要转换成str的属性
|
29
|
-
str_value_keys = []
|
30
|
-
|
31
|
-
|
32
27
|
def unify_to_str(json_str: str) -> str:
|
33
28
|
if not str_value_keys: return json_str
|
34
29
|
obj = loads(json_str)
|
@@ -38,7 +33,6 @@ def unify_to_str(json_str: str) -> str:
|
|
38
33
|
_handle_dict(obj)
|
39
34
|
return dumps(obj)
|
40
35
|
|
41
|
-
|
42
36
|
def _handle_list(data):
|
43
37
|
for o in data:
|
44
38
|
if isinstance(o, list):
|
@@ -46,7 +40,6 @@ def _handle_list(data):
|
|
46
40
|
elif isinstance(o, dict):
|
47
41
|
_handle_dict(o)
|
48
42
|
|
49
|
-
|
50
43
|
def _handle_dict(data):
|
51
44
|
for k, v in data.items():
|
52
45
|
if isinstance(v, list):
|
ctools/database.py
CHANGED
@@ -8,6 +8,13 @@ from sqlalchemy.sql import text
|
|
8
8
|
from ctools import call, string_tools
|
9
9
|
from ctools.thread_pool import thread_local
|
10
10
|
|
11
|
+
"""
|
12
|
+
database.init_db('postgresql://postgres:123456@192.168.3.107:32566/abc', default_schema='public', db_key='source', pool_size=100)
|
13
|
+
with database.get_session('source') as s:
|
14
|
+
s.execute(text('insert into xxx (name) values (:name)'), {'name': string_tools.get_random_str(5)})
|
15
|
+
s.commit()
|
16
|
+
"""
|
17
|
+
|
11
18
|
Base = None
|
12
19
|
inited_db = {}
|
13
20
|
engines = {}
|
@@ -22,6 +29,7 @@ def _init():
|
|
22
29
|
Base = declarative_base()
|
23
30
|
|
24
31
|
# 密码里的@ 要替换成 %40
|
32
|
+
# sqlite connect_args={"check_same_thread": False} sqlite:///{}.format(db_url)
|
25
33
|
def init_db(db_url: str, db_key: str='default', connect_args: dict={}, default_schema: str=None, pool_size: int=5, max_overflow: int=25, echo: bool=False):
|
26
34
|
if inited_db.get(db_key): raise Exception('db {} already init!!!'.format(db_key))
|
27
35
|
global engines, sessionMakers
|
ctools/douglas_rarefy.py
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: UTF-8 -*-
|
3
|
+
__author__ = 'haoyang'
|
4
|
+
__date__ = '2024/9/19 14:02'
|
5
|
+
|
6
|
+
import math
|
7
|
+
from ctools import cjson
|
8
|
+
from ctools.sys_log import flog as log
|
9
|
+
from jsonpath_ng import parser
|
10
|
+
|
11
|
+
class THIN_LEVEL:
|
12
|
+
L1 = 0.00001
|
13
|
+
L2 = 0.00003
|
14
|
+
L3 = 0.00009
|
15
|
+
L4 = 0.0002
|
16
|
+
L5 = 0.0004
|
17
|
+
L6 = 0.0007
|
18
|
+
L7 = 0.0011
|
19
|
+
L8 = 0.0017
|
20
|
+
L9 = 0.0022
|
21
|
+
|
22
|
+
class Point:
|
23
|
+
def __init__(self, lng, lat, origin_data):
|
24
|
+
self.lng = lng
|
25
|
+
self.lat = lat
|
26
|
+
self.origin_data = origin_data
|
27
|
+
|
28
|
+
def _get_line_by_point(xy1, xy2):
|
29
|
+
"""
|
30
|
+
根据两个点求直线方程 ax + by + c = 0
|
31
|
+
:param xy1: 点1, 例如 {'lat': 1, 'lng': 1}
|
32
|
+
:param xy2: 点2, 例如 {'lat': 2, 'lng': 2}
|
33
|
+
:return: 直线方程的三个参数 [a, b, c]
|
34
|
+
"""
|
35
|
+
x1 = xy1.lng
|
36
|
+
y1 = xy1.lat
|
37
|
+
x2 = xy2.lng
|
38
|
+
y2 = xy2.lat
|
39
|
+
a = y2 - y1
|
40
|
+
b = x1 - x2
|
41
|
+
c = (y1 - y2) * x1 - y1 * (x1 - x2)
|
42
|
+
return [a, b, c]
|
43
|
+
|
44
|
+
|
45
|
+
def _get_distance_from_point_to_line(a, b, c, xy):
|
46
|
+
"""
|
47
|
+
点到直线的距离,直线方程为 ax + by + c = 0
|
48
|
+
:param a: 直线参数a
|
49
|
+
:param b: 直线参数b
|
50
|
+
:param c: 直线参数c
|
51
|
+
:param xy: 点坐标,例如 {'lat': 2, 'lng': 2}
|
52
|
+
:return: 距离
|
53
|
+
"""
|
54
|
+
x = xy.lng
|
55
|
+
y = xy.lat
|
56
|
+
return abs((a * x + b * y + c) / math.sqrt(a * a + b * b))
|
57
|
+
|
58
|
+
|
59
|
+
class DouglasRarefy:
|
60
|
+
"""
|
61
|
+
DouglasRarefy Use Guide:
|
62
|
+
points must be arrays, element can be dict or arrays, when element is arrays, index 0 must be lng, index 1 must be lat, and element can be use max column num is 2 (lng, lat) in ret_tpl
|
63
|
+
level default is L2, this level can be hold most of the points detail
|
64
|
+
ret_tpl is the result tpl, can be arrays and json or some can be json loads, exp: [{lng}, {lat}] OR {{"lng": {lng}, "lat": {lat}}}
|
65
|
+
"""
|
66
|
+
def __init__(self, points:[], level=THIN_LEVEL.L2, ret_tpl=None, get_lng=None, get_lat=None):
|
67
|
+
if not isinstance(points, list): raise Exception('points must be list obj !!')
|
68
|
+
if len(points) < 3: raise Exception('points length must be gt 2 !!')
|
69
|
+
self.points = points
|
70
|
+
self.threshold = THIN_LEVEL.L2 if level is None else (getattr(THIN_LEVEL, "L{}".format(int(level))) if int(level) >= 1 else level)
|
71
|
+
log.debug("threshold is: {}".format(self.threshold))
|
72
|
+
self.is_json = isinstance(points[0], dict)
|
73
|
+
self.get_lng = get_lng
|
74
|
+
self.get_lat = get_lat
|
75
|
+
if self.is_json:
|
76
|
+
if not self.get_lng: self.get_lng = '$.lng'
|
77
|
+
if not self.get_lat: self.get_lat = '$.lat'
|
78
|
+
else:
|
79
|
+
if not self.get_lng: self.get_lng = '$.[0]'
|
80
|
+
if not self.get_lat: self.get_lat = '$.[1]'
|
81
|
+
log.debug("get_lng is: {}, get_lat is: {}".format(self.get_lng, self.get_lat))
|
82
|
+
self.lng_parser = parser.parse(self.get_lng)
|
83
|
+
self.lat_parser = parser.parse(self.get_lat)
|
84
|
+
log.debug("is_json is: {}".format(self.is_json))
|
85
|
+
self.ret_tpl = ret_tpl
|
86
|
+
log.debug("ret_tpl is: {}".format(self.ret_tpl))
|
87
|
+
self.data = [Point(self.lng_parser.find(p)[0].value, self.lat_parser.find(p)[0].value, p) for p in self.points]
|
88
|
+
|
89
|
+
def _sparse_points(self, points):
|
90
|
+
"""
|
91
|
+
点位压缩
|
92
|
+
:return: 稀疏后的点集
|
93
|
+
"""
|
94
|
+
if len(points) < 3:
|
95
|
+
if not self.ret_tpl:
|
96
|
+
return [points[0].origin_data, points[-1].origin_data]
|
97
|
+
else:
|
98
|
+
if self.is_json:
|
99
|
+
return [cjson.loads(self.ret_tpl.format(**points[0].origin_data)), cjson.loads(self.ret_tpl.format(**points[-1].origin_data))]
|
100
|
+
else:
|
101
|
+
return [cjson.loads(self.ret_tpl.format(lng=points[0].lng, lat=points[0].lat)), cjson.loads(self.ret_tpl.format(lng=points[-1].lng, lat=points[-1].lat))]
|
102
|
+
|
103
|
+
xy_first = points[0] # 第一个点
|
104
|
+
xy_end = points[-1] # 最后一个点
|
105
|
+
a, b, c = _get_line_by_point(xy_first, xy_end) # 获取直线方程的 a, b, c 值
|
106
|
+
d_max = 0 # 记录点到直线的最大距离
|
107
|
+
split = 0 # 分割位置
|
108
|
+
for i in range(1, len(points) - 1):
|
109
|
+
d = _get_distance_from_point_to_line(a, b, c, points[i])
|
110
|
+
if d > d_max:
|
111
|
+
split = i
|
112
|
+
d_max = d
|
113
|
+
if d_max > self.threshold:
|
114
|
+
# 如果存在点到首位点连成直线的距离大于 max_distance 的, 即需要再次划分
|
115
|
+
child_left = self._sparse_points(points[:split + 1]) # 递归处理左边部分
|
116
|
+
child_right = self._sparse_points(points[split:]) # 递归处理右边部分
|
117
|
+
# 合并结果,避免重复
|
118
|
+
return child_left + child_right[1:]
|
119
|
+
else:
|
120
|
+
if not self.ret_tpl:
|
121
|
+
return [points[0].origin_data, points[-1].origin_data]
|
122
|
+
else:
|
123
|
+
if self.is_json:
|
124
|
+
return [cjson.loads(self.ret_tpl.format(**points[0].origin_data)), cjson.loads(self.ret_tpl.format(**points[-1].origin_data))]
|
125
|
+
else:
|
126
|
+
return [cjson.loads(self.ret_tpl.format(lng=points[0].lng, lat=points[0].lat)), cjson.loads(self.ret_tpl.format(lng=points[-1].lng, lat=points[-1].lat))]
|
127
|
+
|
128
|
+
def sparse_points(self):
|
129
|
+
return self._sparse_points(self.data)
|
130
|
+
|
ctools/ex.py
CHANGED
ctools/http_utils.py
CHANGED
@@ -4,7 +4,7 @@ import requests
|
|
4
4
|
def get(url, params=None, headers=None):
|
5
5
|
result = ""
|
6
6
|
try:
|
7
|
-
response = requests.get(url, params=params, headers=headers, timeout=
|
7
|
+
response = requests.get(url, params=params, headers=headers, timeout=60)
|
8
8
|
response.raise_for_status()
|
9
9
|
if response.status_code == 200:
|
10
10
|
result = response.content
|
@@ -15,7 +15,7 @@ def get(url, params=None, headers=None):
|
|
15
15
|
|
16
16
|
def post(url, data=None, json=None, headers=None, files=None):
|
17
17
|
result = ""
|
18
|
-
response = requests.post(url, data=data, json=json, files=files, headers=headers, timeout=
|
18
|
+
response = requests.post(url, data=data, json=json, files=files, headers=headers, timeout=60)
|
19
19
|
response.raise_for_status()
|
20
20
|
if response.status_code == 200:
|
21
21
|
result = response.content
|
ctools/process_pool.py
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: UTF-8 -*-
|
3
|
+
__author__ = 'haoyang'
|
4
|
+
__date__ = '2024/9/20 12:00'
|
5
|
+
import os
|
6
|
+
import time
|
7
|
+
from concurrent.futures import ProcessPoolExecutor
|
8
|
+
from ctools import call
|
9
|
+
|
10
|
+
_process_pool: ProcessPoolExecutor = None
|
11
|
+
|
12
|
+
@call.init
|
13
|
+
def init():
|
14
|
+
global _process_pool
|
15
|
+
max_workers = min(32, os.cpu_count()) # 最多 32 个
|
16
|
+
_process_pool = ProcessPoolExecutor(max_workers=max_workers)
|
17
|
+
|
18
|
+
def cb(f, callback):
|
19
|
+
exc = f.exception()
|
20
|
+
if exc:
|
21
|
+
if callback: callback(exc)
|
22
|
+
raise exc
|
23
|
+
else:
|
24
|
+
if callback: callback(f.result())
|
25
|
+
|
26
|
+
def submit(func, *args, callback=None, **kwargs):
|
27
|
+
if _process_pool is None: raise Exception('process pool is not init')
|
28
|
+
future = _process_pool.submit(func, *args, **kwargs)
|
29
|
+
future.add_done_callback(lambda f: cb(f, callback))
|
30
|
+
time.sleep(0.01)
|
31
|
+
return future
|
32
|
+
|
33
|
+
def shutdown(wait=True):
|
34
|
+
if _process_pool is None: raise Exception('process pool is not init')
|
35
|
+
_process_pool.shutdown(wait=wait)
|
ctools/sys_log.py
CHANGED
@@ -2,19 +2,15 @@ import logging
|
|
2
2
|
import os
|
3
3
|
import sys
|
4
4
|
import time
|
5
|
+
import traceback
|
5
6
|
|
6
|
-
from ctools import call
|
7
|
+
from ctools import call, work_path
|
7
8
|
|
8
|
-
clog
|
9
|
+
clog: logging.Logger = None
|
10
|
+
flog: logging.Logger = None
|
9
11
|
|
10
12
|
neglect_keywords = [
|
11
13
|
"OPTIONS",
|
12
|
-
'/scheduleInfo/list',
|
13
|
-
'/sys/get_sys_state',
|
14
|
-
'/scheduleInfo/tmpList',
|
15
|
-
'/sys/getSysLog',
|
16
|
-
'/downloadManage/static_file',
|
17
|
-
'/downloadManage/static_images'
|
18
14
|
]
|
19
15
|
|
20
16
|
|
@@ -25,7 +21,7 @@ def _file_log(sys_log_path: str = './', log_level: int = logging.INFO, mixin: bo
|
|
25
21
|
os.mkdir(sys_log_path)
|
26
22
|
except Exception:
|
27
23
|
pass
|
28
|
-
log_file = sys_log_path + os.path.sep + "log-" + time.strftime("%Y-%m-%d-%H
|
24
|
+
log_file = sys_log_path + os.path.sep + "log-" + time.strftime("%Y-%m-%d-%H", time.localtime(time.time())) + ".log"
|
29
25
|
if mixin:
|
30
26
|
handlers = [logging.FileHandler(filename=log_file, encoding='utf-8'), logging.StreamHandler()]
|
31
27
|
else:
|
@@ -54,14 +50,28 @@ class GlobalLogger(object):
|
|
54
50
|
def __init__(self, logger):
|
55
51
|
sys.stdout = self
|
56
52
|
sys.stderr = self
|
53
|
+
sys.excepthook = self.handle_exception
|
57
54
|
self.log = logger
|
58
55
|
|
59
56
|
def write(self, message):
|
60
|
-
if message == '\n': return
|
57
|
+
if message == '\n' or message == '\r\n': return
|
61
58
|
global neglect_keywords
|
62
59
|
for neglect_keyword in neglect_keywords:
|
63
60
|
if neglect_keyword in message: return
|
64
|
-
|
61
|
+
try:
|
62
|
+
stack = traceback.extract_stack(limit=3)
|
63
|
+
caller = stack[-2]
|
64
|
+
location = f"{os.path.splitext(os.path.basename(caller.filename))[0]}({caller.name}:{caller.lineno})"
|
65
|
+
self.log.info(f"{location} {message.strip()}")
|
66
|
+
except Exception:
|
67
|
+
self.log.info(message.strip())
|
68
|
+
|
69
|
+
def handle_exception(self, exc_type, exc_value, exc_traceback):
|
70
|
+
if issubclass(exc_type, KeyboardInterrupt):
|
71
|
+
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
72
|
+
return
|
73
|
+
formatted_exception = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))
|
74
|
+
self.log.error(f"An error occurred:\n{formatted_exception.strip()}")
|
65
75
|
|
66
76
|
def flush(self):
|
67
77
|
pass
|
@@ -70,6 +80,10 @@ class GlobalLogger(object):
|
|
70
80
|
@call.init
|
71
81
|
def _init_log() -> None:
|
72
82
|
global flog, clog
|
73
|
-
flog = _file_log(sys_log_path='
|
83
|
+
flog = _file_log(sys_log_path='{}/ck-py-log/'.format(work_path.get_user_work_path()), mixin=True, log_level=logging.DEBUG)
|
74
84
|
clog = _console_log()
|
75
85
|
GlobalLogger(flog)
|
86
|
+
|
87
|
+
def setLevel(log_level=logging.INFO):
|
88
|
+
flog.setLevel(log_level)
|
89
|
+
clog.setLevel(log_level)
|
ctools/web_base.py
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
import threading
|
2
|
+
from functools import wraps
|
3
|
+
|
4
|
+
import bottle
|
5
|
+
from bottle import response, Bottle, abort, request
|
6
|
+
from ctools.api_result import R
|
7
|
+
from ctools.sys_log import flog as log
|
8
|
+
|
9
|
+
bottle.BaseRequest.MEMFILE_MAX = 1024 * 1024 * 50
|
10
|
+
|
11
|
+
class GlobalState:
|
12
|
+
lock = threading.Lock()
|
13
|
+
withOutLoginURI = [
|
14
|
+
'/login',
|
15
|
+
'/get_sys_version'
|
16
|
+
]
|
17
|
+
allowRemoteCallURI = [
|
18
|
+
|
19
|
+
]
|
20
|
+
token = {}
|
21
|
+
|
22
|
+
def init_app(context_path):
|
23
|
+
app = Bottle()
|
24
|
+
app.context_path = context_path
|
25
|
+
|
26
|
+
@app.hook('before_request')
|
27
|
+
def before_request():
|
28
|
+
# abort(401, '系统时间被篡改, 请重新校时!')
|
29
|
+
pass
|
30
|
+
|
31
|
+
@app.error(401)
|
32
|
+
def unauthorized(error):
|
33
|
+
after_request()
|
34
|
+
response.status = 200
|
35
|
+
log.error("系统未授权: {} {} {}".format(error.body, request.method, request.fullpath))
|
36
|
+
return R.error(resp=R.Code.cus_code(9999, "系统未授权! {}".format(error.body)))
|
37
|
+
|
38
|
+
@app.error(403)
|
39
|
+
def unauthorized(error):
|
40
|
+
after_request()
|
41
|
+
response.status = 200
|
42
|
+
log.error("访问受限: {} {} {}".format(error.body, request.method, request.fullpath))
|
43
|
+
return R.error(resp=R.Code.cus_code(8888, "访问受限: {}".format(error.body)))
|
44
|
+
|
45
|
+
@app.error(405)
|
46
|
+
def cors_error(error):
|
47
|
+
if request.method == 'OPTIONS':
|
48
|
+
after_request()
|
49
|
+
response.status = 200
|
50
|
+
return
|
51
|
+
log.error("请求方法错误: {} {} {}".format(error.status_line, request.method, request.fullpath))
|
52
|
+
return R.error(msg='请求方法错误: {}'.format(error.status_line))
|
53
|
+
|
54
|
+
@app.error(500)
|
55
|
+
def cors_error(error):
|
56
|
+
after_request()
|
57
|
+
response.status = 200
|
58
|
+
log.error("系统发生错误: {} {} {}".format(error.body, request.method, request.fullpath))
|
59
|
+
return R.error(msg='系统发生错误: {}'.format(error.exception))
|
60
|
+
|
61
|
+
@app.hook('after_request')
|
62
|
+
def after_request():
|
63
|
+
enable_cors()
|
64
|
+
return app
|
65
|
+
|
66
|
+
# annotation
|
67
|
+
def rule(key):
|
68
|
+
def return_func(func):
|
69
|
+
@wraps(func)
|
70
|
+
def decorated(*args, **kwargs):
|
71
|
+
# if GlobalState.licenseInfo is not None and key not in GlobalState.licenseInfo.access_module:
|
72
|
+
# log.error("系统未授权! {} {}".format(request.fullpath, '当前请求的模块未授权!请联系管理员!'))
|
73
|
+
# return R.error(resp=R.Code.cus_code(9999, "系统未授权! {}".format('当前请求的模块未授权!请联系管理员!')))
|
74
|
+
return func(*args, **kwargs)
|
75
|
+
return decorated
|
76
|
+
return return_func
|
77
|
+
|
78
|
+
|
79
|
+
def enable_cors():
|
80
|
+
response.headers['Access-Control-Allow-Origin'] = '*'
|
81
|
+
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
|
82
|
+
request_headers = request.headers.get('Access-Control-Request-Headers')
|
83
|
+
response.headers['Access-Control-Allow-Headers'] = request_headers if request_headers else ''
|
84
|
+
response.headers['Access-Control-Expose-Headers'] = '*'
|
85
|
+
|
86
|
+
# annotation
|
87
|
+
def params_resolve(func):
|
88
|
+
"""
|
89
|
+
Use guide:
|
90
|
+
@params_resolve
|
91
|
+
def xxx(params):
|
92
|
+
print(params.a)
|
93
|
+
print(params.b)
|
94
|
+
"""
|
95
|
+
@wraps(func)
|
96
|
+
def decorated(*args, **kwargs):
|
97
|
+
if request.method == 'GET':
|
98
|
+
queryStr = request.query.decode('utf-8')
|
99
|
+
pageInfo = PageInfo(
|
100
|
+
page_size=10 if request.headers.get('page_size') is None else int(request.headers.get('page_size')),
|
101
|
+
page_index=1 if request.headers.get('page_index') is None else int(request.headers.get('page_index'))
|
102
|
+
)
|
103
|
+
queryStr.pageInfo = pageInfo
|
104
|
+
return func(params=queryStr, *args, **kwargs)
|
105
|
+
elif request.method == 'POST':
|
106
|
+
content_type = request.get_header('content-type')
|
107
|
+
if content_type == 'application/json':
|
108
|
+
params = request.json
|
109
|
+
return func(params=DictWrapper(params), *args, **kwargs)
|
110
|
+
elif content_type and 'multipart/form-data' in content_type:
|
111
|
+
form_data = request.forms.decode()
|
112
|
+
form_files = request.files.decode()
|
113
|
+
params = FormDataParams(data=DictWrapper(form_data), files=form_files)
|
114
|
+
return func(params=params, *args, **kwargs)
|
115
|
+
else:
|
116
|
+
params = request.query.decode('utf-8')
|
117
|
+
return func(params=params, *args, **kwargs)
|
118
|
+
else:
|
119
|
+
return func(*args, **kwargs)
|
120
|
+
return decorated
|
121
|
+
|
122
|
+
class PageInfo:
|
123
|
+
def __init__(self, page_size, page_index):
|
124
|
+
self.page_size = page_size
|
125
|
+
self.page_index = page_index
|
126
|
+
|
127
|
+
class FormDataParams:
|
128
|
+
def __init__(self, data, files):
|
129
|
+
self.data = data
|
130
|
+
self.files = files
|
131
|
+
|
132
|
+
def __getattr__(self, key):
|
133
|
+
try:
|
134
|
+
return self.data[key]
|
135
|
+
except Exception:
|
136
|
+
return self.files[key]
|
137
|
+
|
138
|
+
class DictWrapper(dict):
|
139
|
+
def __getattr__(self, key):
|
140
|
+
return self.get(key)
|
141
|
+
|
142
|
+
def __setattr__(self, key, value):
|
143
|
+
self[key] = value
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: gomyck-tools
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.3
|
4
4
|
Summary: A ctools for python development by hao474798383
|
5
5
|
Home-page: https://blog.gomyck.com
|
6
6
|
Author: gomyck
|
@@ -15,8 +15,8 @@ Requires-Dist: SQLAlchemy >=2.0.32
|
|
15
15
|
Requires-Dist: chardet >=5.2.0
|
16
16
|
Requires-Dist: psycopg2-binary >=2.9.9
|
17
17
|
Requires-Dist: croniter >=3.0.3
|
18
|
-
Requires-Dist: pyautogui >=0.9.54
|
19
18
|
Requires-Dist: gmssl >=3.2.2
|
20
19
|
Requires-Dist: psutil >=6.0.0
|
20
|
+
Requires-Dist: jsonpath-ng >=1.6.1
|
21
21
|
|
22
22
|
this package is for python development
|
@@ -1,35 +1,36 @@
|
|
1
1
|
ctools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
ctools/aes_tools.py,sha256=ylUgyhlx7bNCTGrbWEZVwCObzYdJTQtwEc4ZMidL2Fo,563
|
3
|
+
ctools/api_result.py,sha256=NiM-R9K42G3m16B27sG_mqKrlexZzC5-LKoY8D5tO4s,1239
|
3
4
|
ctools/application.py,sha256=WviU7p9GOqducbGW3XGkP7jCNKmraCh6JGSYBC33CQk,16008
|
4
5
|
ctools/b64.py,sha256=_BdhX3p3-MaSSlU2wivN5qPxQfacR3VRBr1WC456tU0,194
|
5
6
|
ctools/bashPath.py,sha256=BCN_EhYzqvwsxYso81omMNd3SbEociwSOyb9kLvu8V4,337
|
6
|
-
ctools/bottle_server.py,sha256=
|
7
|
+
ctools/bottle_server.py,sha256=9gmw1og_8ZaOfmF8X9pwDlRxxeo648QmFvLl0shV0Dk,2303
|
7
8
|
ctools/browser_element_tools.py,sha256=tWNxUJ9m-hNTYtS0NRvmH9r5I-Qw55uKekwDYgt49bc,9951
|
8
|
-
ctools/call.py,sha256=
|
9
|
-
ctools/cjson.py,sha256=
|
9
|
+
ctools/call.py,sha256=BCr8wzt5qd70okv8IZn-9-EpjywleZgvA3u1vfZ_Kt8,1581
|
10
|
+
ctools/cjson.py,sha256=M2XrXnFXTJyKsXP2JRos2Bse4b6WXjr6TrA6y-EF_Ig,1245
|
10
11
|
ctools/ckafka.py,sha256=win6iV9-7FJDzcmqwQ5httN_NWjrBlWHc5b7N9E5tCQ,5187
|
11
12
|
ctools/compile_tools.py,sha256=Nybh3vnkurIKnPnubdYzigjnzFu4GaTMKPvqFdibxmE,510
|
12
13
|
ctools/console.py,sha256=1VAq_-fSGgHKYv6Qe53kHaJL0-3NpFRUZljahbJPcfk,1807
|
13
14
|
ctools/cron_lite.py,sha256=L3Le_ScIuKiLAfzdaCf8qoPUbFIMT9ASallQkmwJTfc,8307
|
14
|
-
ctools/database.py,sha256=
|
15
|
+
ctools/database.py,sha256=xUiJbRRgHkGqwTzFUhAguFfDkW1dVfG2HVPFPzNpkmk,5360
|
15
16
|
ctools/date_utils.py,sha256=-xI2anEzAonOvYwVmM1hCnkuLKodZ8pb33dS3dRxEIc,865
|
17
|
+
ctools/douglas_rarefy.py,sha256=2zjUa5nLkStsAsttVqe3PiJWpk108pw1m3CIRNm-Z94,4820
|
16
18
|
ctools/download_tools.py,sha256=h12HGr2IaVvl8IDAvpDftHycSACI5V7HF0Yk9kV4bVY,1750
|
17
19
|
ctools/enums.py,sha256=QbHa3j7j4-BDdwaga5Y0nYfA2uNSVJDHumYdIZdKVkM,118
|
18
|
-
ctools/ex.py,sha256=
|
20
|
+
ctools/ex.py,sha256=_UtbmDLrC7uZsoBtTdecuCZAlf2DA7fvojUf5fGZDVo,795
|
19
21
|
ctools/excelOpt.py,sha256=q3HLAb1JScTrMCvx_x-4WWnqKhyTEzQ-m5vtqFy8NZU,1138
|
20
22
|
ctools/html_soup.py,sha256=LabCo4yWI58fbFBPhunk3THWBf0BbHEWLgwyvSpTGR4,1903
|
21
|
-
ctools/http_utils.py,sha256=
|
23
|
+
ctools/http_utils.py,sha256=4dt7OTRZluB9wCK4HL0TBqVZrV3cj_AIs4-9mGDC1zA,616
|
22
24
|
ctools/id_worker_tools.py,sha256=xtfxpL8q4hHLT02JFx2jVXEXpasHq44ZFsOhO580JmE,2357
|
23
25
|
ctools/images_tools.py,sha256=hjYu-3tpjZ96yMqAM3XrFSUEOiUcyGk4DbAsz2_OeIs,669
|
24
26
|
ctools/imgDialog.py,sha256=DSq5cFofyP_el80AFConht_ZYRqxZhIz4CWjqBlJ0cw,1385
|
25
27
|
ctools/license.py,sha256=0Kh7lXL7LD1PQqyijHajFL0GbmZGNB88PA2WskbQn_s,1066
|
26
|
-
ctools/log.py,sha256=kLlT67by9kTV6UlFXuYPg5AkNnqFRZknoUqLGGIKj14,1097
|
27
28
|
ctools/metrics.py,sha256=vq9Fnq2fhmhS4yoHS4gO7ArKS033Eou8vpA779Uue0I,4414
|
28
29
|
ctools/mqtt_utils.py,sha256=9q_5CGvEhhvy8fZxaioguPSz8mvkdpCxnZ86GxjwTE0,10676
|
29
|
-
ctools/mvc.py,sha256=MgOaM7Jg_tUwVoBSzTgr12E3bX4-IQnIqBwDC-1BO08,1792
|
30
30
|
ctools/obj.py,sha256=GYS1B8NyjtUIh0HlK9r8avC2eGbK2SJac4C1CGnfGhI,479
|
31
31
|
ctools/pacth.py,sha256=4TNWbgNqFSY_BBXKF4KzKauxL8W937wDkoqt84rU-Qg,2603
|
32
32
|
ctools/plan_area_tools.py,sha256=yyFWIAvVe_p1e8HugR_h7r_W7dW9dQ-CGtgMz_Hz5QQ,3396
|
33
|
+
ctools/process_pool.py,sha256=LTXU8pxEhnf8dDD23z0OoMlru5z-hKr8EFNH3xYDvCs,950
|
33
34
|
ctools/pty_tools.py,sha256=H-j2xlZ5A0Q75NMop1ghCs2C0BZPKOPjilRujnh5Ngg,1602
|
34
35
|
ctools/resource_bundle_tools.py,sha256=8zW1-aj6jAYFBCoyslz5bL-5916G6Aif1RUy_ObbiVU,3793
|
35
36
|
ctools/screenshot_tools.py,sha256=-DzWgguxTOTolzQXdZfUIHTOU3HNhp74fdomeSTBxKs,4538
|
@@ -39,16 +40,17 @@ ctools/ssh.py,sha256=3Oo4Lf6UyX-F_u_c1icHHDNzvVDHMKiWIO1u7E22QkM,217
|
|
39
40
|
ctools/strDiff.py,sha256=QUtXOfsRLTFozH_zByqsC39JeuG3eZtrwGVeLyaHYUI,429
|
40
41
|
ctools/string_tools.py,sha256=qjFn47lM27ISAa2Q4OKO7WOBdhyWC5lLHbME-uSeICE,2142
|
41
42
|
ctools/sys_info.py,sha256=podW43pcn7z2f6FW2w1L9bU1HfeVjC3D34BdN_H6rf4,3625
|
42
|
-
ctools/sys_log.py,sha256=
|
43
|
+
ctools/sys_log.py,sha256=oqb1S41LosdeZxtogFVgDk8R4sjiHhUeYJLCzHd728E,2805
|
43
44
|
ctools/thread_pool.py,sha256=pPtKCE1BjagbhqtWjKo5HA2LktcSQ5KQUJz9Fn2Jl0w,924
|
44
45
|
ctools/upload_tools.py,sha256=sqe6K3ZWiyY58pFE5IO5mNaS1znnS7U4c4UqY8noED4,1068
|
46
|
+
ctools/web_base.py,sha256=lrEANgUluyxGu2nCxuk94HMuS2-VFpLXsyw870PxB6g,4545
|
45
47
|
ctools/win_canvas.py,sha256=PAxI4i1jalfree9d1YG4damjc2EzaHZrgHZCTgk2GiM,2530
|
46
48
|
ctools/win_control.py,sha256=zNu06wNCtOnfIg12LIMMy2hBb1rskVQ8QIZu9qx4CNY,3487
|
47
49
|
ctools/wordFill.py,sha256=dB1OLt6GLmWdkDV8H20VWbJmY4ggNNI8iHD1ocae2iM,875
|
48
50
|
ctools/word_fill.py,sha256=aIkzjAF2soSW6w2dO2CRZlveDcuIdr6v9DtyyyB8uxM,18216
|
49
51
|
ctools/word_fill_entity.py,sha256=eX3G0Gy16hfGpavQSEkCIoKDdTnNgRRJrFvKliETZK8,985
|
50
52
|
ctools/work_path.py,sha256=i4MTUobqNW2WMrT3mwEC_XYQ0_IhFmKoNpTX2W6A8Tc,1680
|
51
|
-
gomyck_tools-1.0.
|
52
|
-
gomyck_tools-1.0.
|
53
|
-
gomyck_tools-1.0.
|
54
|
-
gomyck_tools-1.0.
|
53
|
+
gomyck_tools-1.0.3.dist-info/METADATA,sha256=PQAV6lBWaVLR5zlq6_Zr0KbmCLDRaib8WVn3M4jngIo,712
|
54
|
+
gomyck_tools-1.0.3.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
55
|
+
gomyck_tools-1.0.3.dist-info/top_level.txt,sha256=-MiIH9FYRVKp1i5_SVRkaI-71WmF1sZSRrNWFU9ls3s,7
|
56
|
+
gomyck_tools-1.0.3.dist-info/RECORD,,
|
ctools/log.py
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
import os
|
3
|
-
import time
|
4
|
-
|
5
|
-
|
6
|
-
# 文件日志
|
7
|
-
def flog(base_work_path: str = './', log_level: int = logging.INFO, mixin: bool = False) -> logging:
|
8
|
-
try:
|
9
|
-
os.mkdir(base_work_path + ".logs")
|
10
|
-
except Exception:
|
11
|
-
pass
|
12
|
-
log_file = base_work_path + ".logs" + os.path.sep + "log-" + time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(time.time())) + ".ck"
|
13
|
-
if mixin:
|
14
|
-
handlers = [logging.FileHandler(filename=log_file, encoding='utf-8'), logging.StreamHandler()]
|
15
|
-
else:
|
16
|
-
handlers = [logging.FileHandler(filename=log_file, encoding='utf-8')]
|
17
|
-
logging.basicConfig(level=log_level,
|
18
|
-
format='%(asctime)s-%(levelname)s-%(thread)d-%(funcName)s-%(lineno)d - %(message)s',
|
19
|
-
handlers=handlers)
|
20
|
-
return logging.getLogger("ck-flog")
|
21
|
-
|
22
|
-
|
23
|
-
# 控制台日志
|
24
|
-
def clog(log_level: int = logging.INFO) -> logging:
|
25
|
-
logging.basicConfig(level=log_level,
|
26
|
-
format='%(asctime)s-%(levelname)s-%(thread)d-%(funcName)s-%(lineno)d - %(message)s',
|
27
|
-
handlers=[logging.StreamHandler()])
|
28
|
-
return logging.getLogger("ck-clog")
|
ctools/mvc.py
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
from functools import wraps
|
2
|
-
|
3
|
-
from bottle import request
|
4
|
-
|
5
|
-
|
6
|
-
class PageInfo:
|
7
|
-
def __init__(self, page_size, page_index):
|
8
|
-
self.page_size = page_size
|
9
|
-
self.page_index = page_index
|
10
|
-
|
11
|
-
|
12
|
-
class FormDataParams:
|
13
|
-
def __init__(self, data, files):
|
14
|
-
self.data = data
|
15
|
-
self.files = files
|
16
|
-
|
17
|
-
class DictWrapper(dict):
|
18
|
-
def __getattr__(self, key):
|
19
|
-
return self.get(key)
|
20
|
-
|
21
|
-
def __setattr__(self, key, value):
|
22
|
-
self[key] = value
|
23
|
-
|
24
|
-
def parameter_handler():
|
25
|
-
def return_func(func):
|
26
|
-
@wraps(func)
|
27
|
-
def decorated(*args, **kwargs):
|
28
|
-
if request.method == 'GET':
|
29
|
-
queryStr = request.query.decode('utf-8')
|
30
|
-
pageInfo = PageInfo(
|
31
|
-
page_size=10 if request.headers.get('page_size') is None else int(request.headers.get('page_size')),
|
32
|
-
page_index=1 if request.headers.get('page_index') is None else int(request.headers.get('page_index'))
|
33
|
-
)
|
34
|
-
try:
|
35
|
-
return func(params=queryStr, *args, **kwargs)
|
36
|
-
except TypeError:
|
37
|
-
return func(params=queryStr, pageInfo=pageInfo, *args, **kwargs)
|
38
|
-
elif request.method == 'POST':
|
39
|
-
content_type = request.get_header('content-type')
|
40
|
-
if content_type == 'application/json':
|
41
|
-
params = request.json
|
42
|
-
return func(params=DictWrapper(params), *args, **kwargs)
|
43
|
-
elif content_type and 'multipart/form-data' in content_type:
|
44
|
-
form_data = request.forms.decode()
|
45
|
-
form_files = request.files.decode()
|
46
|
-
params = FormDataParams(data=DictWrapper(form_data), files=form_files)
|
47
|
-
return func(params=params, *args, **kwargs)
|
48
|
-
else:
|
49
|
-
params = request.query.decode('utf-8')
|
50
|
-
return func(params=params, *args, **kwargs)
|
51
|
-
else:
|
52
|
-
return func(*args, **kwargs)
|
53
|
-
|
54
|
-
return decorated
|
55
|
-
|
56
|
-
return return_func
|
File without changes
|
File without changes
|