gomyck-tools 1.2.5__py3-none-any.whl → 1.2.7__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 CHANGED
@@ -29,7 +29,7 @@ class R(object):
29
29
  self.message = message
30
30
  self.data = data
31
31
 
32
- def _to_json(self):
32
+ def _to_json_str(self):
33
33
  return cjson.unify_to_str(cjson.dumps(self))
34
34
 
35
35
  @staticmethod
@@ -37,13 +37,19 @@ class R(object):
37
37
  return R(**cjson.loads(r_json))
38
38
 
39
39
  @staticmethod
40
- def ok(data=None, resp=Code.SUCCESS, msg=None):
41
- return R(resp.code, msg if msg is not None else resp.message, data)._to_json()
40
+ def ok(data=None, resp=Code.SUCCESS, msg=None, to_json_str=True):
41
+ if not to_json_str:
42
+ return R(resp.code, msg if msg is not None else resp.message, data)
43
+ return R(resp.code, msg if msg is not None else resp.message, data)._to_json_str()
42
44
 
43
45
  @staticmethod
44
- def fail(msg=None, resp=Code.FAIL, data=None):
45
- return R(resp.code, msg if msg is not None else resp.message, data)._to_json()
46
+ def fail(msg=None, resp=Code.FAIL, data=None, to_json_str=True):
47
+ if not to_json_str:
48
+ return R(resp.code, msg if msg is not None else resp.message, data)
49
+ return R(resp.code, msg if msg is not None else resp.message, data)._to_json_str()
46
50
 
47
51
  @staticmethod
48
- def error(msg=None, resp=Code.ERROR, data=None):
49
- return R(resp.code, msg if msg is not None else resp.message, data)._to_json()
52
+ def error(msg=None, resp=Code.ERROR, data=None, to_json_str=True):
53
+ if not to_json_str:
54
+ return R(resp.code, msg if msg is not None else resp.message, data)
55
+ return R(resp.code, msg if msg is not None else resp.message, data)._to_json_str()
ctools/bottle_web_base.py CHANGED
@@ -4,6 +4,8 @@ from functools import wraps
4
4
 
5
5
  import bottle
6
6
  from bottle import response, Bottle, request
7
+
8
+ from ctools import token
7
9
  from ctools.dict_wrapper import DictWrapper
8
10
  from ctools.api_result import R
9
11
  from ctools.sys_log import flog as log
@@ -14,13 +16,15 @@ func_has_params = {}
14
16
  class GlobalState:
15
17
  lock = threading.Lock()
16
18
  withOutLoginURI = [
17
- '/login',
18
- '/get_sys_version'
19
+ '/',
20
+ '/index',
21
+ '/login'
19
22
  ]
20
23
  allowRemoteCallURI = [
21
24
 
22
25
  ]
23
26
  token = {}
27
+ interceptors = []
24
28
 
25
29
  def init_app(context_path=None):
26
30
  app = Bottle()
@@ -28,20 +32,21 @@ def init_app(context_path=None):
28
32
 
29
33
  @app.hook('before_request')
30
34
  def before_request():
31
- # abort(401, '系统时间被篡改, 请重新校时!')
32
- pass
35
+ for interceptor in GlobalState.interceptors:
36
+ res: R = interceptor['func']()
37
+ if res.code != 200: bottle.abort(res.code, res.message)
33
38
 
34
39
  @app.error(401)
35
40
  def unauthorized(error):
36
41
  after_request()
37
- response.status = 200
42
+ response.status = 401
38
43
  log.error("系统未授权: {} {} {}".format(error.body, request.method, request.fullpath))
39
44
  return R.error(resp=R.Code.cus_code(9999, "系统未授权! {}".format(error.body)))
40
45
 
41
46
  @app.error(403)
42
47
  def unauthorized(error):
43
48
  after_request()
44
- response.status = 200
49
+ response.status = 403
45
50
  log.error("访问受限: {} {} {}".format(error.body, request.method, request.fullpath))
46
51
  return R.error(resp=R.Code.cus_code(8888, "访问受限: {}".format(error.body)))
47
52
 
@@ -49,7 +54,7 @@ def init_app(context_path=None):
49
54
  def cors_error(error):
50
55
  if request.method == 'OPTIONS':
51
56
  after_request()
52
- response.status = 200
57
+ response.status = 405
53
58
  return
54
59
  log.error("请求方法错误: {} {} {}".format(error.status_line, request.method, request.fullpath))
55
60
  return R.error(msg='请求方法错误: {}'.format(error.status_line))
@@ -57,7 +62,7 @@ def init_app(context_path=None):
57
62
  @app.error(500)
58
63
  def cors_error(error):
59
64
  after_request()
60
- response.status = 200
65
+ response.status = 500
61
66
  log.error("系统发生错误: {} {} {}".format(error.body, request.method, request.fullpath))
62
67
  return R.error(msg='系统发生错误: {}'.format(error.exception))
63
68
 
@@ -75,6 +80,14 @@ def enable_cors():
75
80
  response.headers['Access-Control-Allow-Headers'] = request_headers if request_headers else ''
76
81
  response.headers['Access-Control-Expose-Headers'] = '*'
77
82
 
83
+ # annotation
84
+ def before_intercept(order=0):
85
+ def decorator(func):
86
+ log.info("add before interceptor: {}".format(func.__name__))
87
+ GlobalState.interceptors.append({'order': order, 'func': func})
88
+ GlobalState.interceptors = sorted(GlobalState.interceptors, key=lambda x: x['order'])
89
+ return decorator
90
+
78
91
  # annotation
79
92
  def rule(key):
80
93
  def return_func(func):
@@ -142,4 +155,14 @@ class FormDataParams:
142
155
  except Exception:
143
156
  return self.files[key]
144
157
 
145
-
158
+ # 通用的鉴权方法
159
+ def common_auth_verify(aes_key):
160
+ if request.path.startswith('/static') or request.path in GlobalState.withOutLoginURI:
161
+ return R.ok(to_json_str=False)
162
+ auth_token = request.get_header('Authorization')
163
+ if auth_token is None:
164
+ auth_token = request.get_cookie('Authorization')
165
+ payload = token.get_payload(auth_token, aes_key)
166
+ if payload:
167
+ return R.ok(to_json_str=False)
168
+ return R.error(resp=R.Code.cus_code(401, "未授权"), to_json_str=False)
@@ -2,18 +2,11 @@ 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
5
+ from bottle import ServerAdapter, Bottle, template, static_file, abort
6
6
 
7
7
  from ctools import sys_info
8
8
 
9
- """
10
- app = bottle_web_base.init_app('子模块写 context_path, 主模块就不用写任何东西')
11
9
 
12
- @app.get('/queryList')
13
- @bottle_web_base.rule('DOC:DOWNLOAD')
14
- def query_list(params):
15
- print(123)
16
- """
17
10
 
18
11
  """
19
12
  module_names = list(globals().keys())
@@ -31,10 +24,26 @@ def get_ws_modules():
31
24
  """
32
25
 
33
26
  """
34
- http_app = bottle_webserver.init_bottle() # 这里可以传 APP 当做主模块
35
- http_app.mount(app.context_path, app)
36
- http_app.set_index(r'轨迹点位压缩.html')
37
- http_app.run()
27
+ app = bottle_web_base.init_app('子模块写 context_path, 主模块就不用写任何东西')
28
+
29
+ # 通用的鉴权方法
30
+ @bottle_web_base.before_intercept(0)
31
+ def token_check():
32
+ return bottle_web_base.common_auth_verify(aes_key)
33
+
34
+ @app.post('/login')
35
+ def login(params):
36
+ return R.ok(token.gen_token({'username': 'xxx'}, aes_key, 3600))
37
+
38
+ @app.get('/queryList')
39
+ @bottle_web_base.rule('DOC:DOWNLOAD')
40
+ def query_list(params):
41
+ print(123)
42
+
43
+ main_app = bottle_webserver.init_bottle() # 这里可以传 APP 当做主模块, 但是 context_path 就不好使了, 上下文必须是 /
44
+ main_app.mount(app.context_path, app)
45
+ main_app.set_index(r'轨迹点位压缩.html')
46
+ main_app.run()
38
47
  """
39
48
 
40
49
  class CBottle:
@@ -49,20 +58,32 @@ class CBottle:
49
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)
50
59
  self.bottle.run(server=http_server, quiet=self.quiet)
51
60
 
52
- def set_index(self, path, **kwargs):
61
+ def set_index(self, filename='index.html', root='./', **kwargs):
53
62
  @self.bottle.route(['/', '/index'])
54
63
  def index():
55
- return template(path, kwargs)
64
+ try:
65
+ return static_file(filename=filename, root=root, **kwargs)
66
+ except FileNotFoundError:
67
+ abort(404, "File not found...")
68
+
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)
56
74
 
57
75
  def set_static(self, root):
58
76
  @self.bottle.route('/static/<filepath:path>')
59
77
  def static(filepath):
60
- return static_file(filepath, root=root)
61
-
62
- def set_download(self, root, download_prefix=True):
63
- @self.bottle.route('/download/<filename>')
64
- def download(filename):
65
- return static_file(filename, root=root, download=download_prefix)
78
+ try:
79
+ return static_file(filepath, root=root)
80
+ except FileNotFoundError:
81
+ abort(404, "File not found...")
82
+
83
+ def set_download(self, root):
84
+ @self.bottle.route('/download/<filepath:path>')
85
+ def download(filepath):
86
+ return static_file(filepath, root=root, download=True)
66
87
 
67
88
  def mount(self, context_path, app, **kwargs):
68
89
  self.bottle.mount(context_path, app, **kwargs)
ctools/metrics.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import os
2
2
  import threading
3
- import time
4
3
  from enum import Enum
5
4
 
6
5
  from prometheus_client import Counter, Gauge, Summary, Histogram, start_http_server
@@ -12,7 +11,7 @@ _metrics_port = 8011
12
11
  _persistent_json = {}
13
12
  _metrics_initial = {}
14
13
  _lock = threading.Lock()
15
-
14
+ _metrics_persistent_path = os.path.join(work_path.get_user_work_path('metrics', True), 'persistent.json')
16
15
 
17
16
  # 认证中间件
18
17
  # @app.before_request
@@ -30,8 +29,7 @@ class MetricType(Enum):
30
29
  HISTOGRAM = 'histogram'
31
30
 
32
31
  class Metric:
33
- def __init__(self, metric_type: MetricType, metric_key: str, metric_labels: [],
34
- persistent: bool = False, buckets: [] = None, reset: bool = False, desc: str = ""):
32
+ def __init__(self, metric_type: MetricType, metric_key: str, metric_labels: [], persistent: bool = False, buckets: [] = None, reset: bool = False, desc: str = ""):
35
33
  self.metric_type = metric_type
36
34
  self.metric_key = metric_key
37
35
  self.metric_labels = metric_labels
@@ -54,9 +52,8 @@ class Metric:
54
52
 
55
53
  @call.once
56
54
  def init(reset_persistent: bool = False):
57
- persistent_path = os.path.join(work_path.get_current_path(), 'persistent.json')
58
- if os.path.exists(persistent_path) and not reset_persistent:
59
- with open(persistent_path, 'r') as persistent_file:
55
+ if os.path.exists(_metrics_persistent_path) and not reset_persistent:
56
+ with open(_metrics_persistent_path, 'r') as persistent_file:
60
57
  global _persistent_json
61
58
  try:
62
59
  content = persistent_file.readline()
@@ -66,6 +63,7 @@ def init(reset_persistent: bool = False):
66
63
  log.error('persistent.json is not valid json!!!!!')
67
64
  _persistent_json = {}
68
65
  _init_all_metrics()
66
+ _persistent_json = _persistent_json or {}
69
67
  for key, item in _persistent_json.items():
70
68
  metrics_key = key.split("-")[0]
71
69
  if '_labels' in key or metrics_key not in _metrics_initial: continue
@@ -77,7 +75,7 @@ def init(reset_persistent: bool = False):
77
75
  def persistent_metrics():
78
76
  if _persistent_json and not _lock.locked():
79
77
  log.info('begin persistent metrics to file...')
80
- with open(os.path.join(work_path.get_current_path(), 'persistent.json'), 'w') as persistent_file:
78
+ with open(_metrics_persistent_path, 'w') as persistent_file:
81
79
  persistent_file.write(cjson.dumps(_persistent_json))
82
80
  persistent_file.flush()
83
81
 
@@ -100,14 +98,16 @@ def opt(metric_key: str, label_values: [], metric_value: int):
100
98
  if metric_entity.metric_type == MetricType.COUNTER or metric_entity.metric_type == MetricType.GAUGE:
101
99
  if label_values is None or len(label_values) == 0:
102
100
  if metric_entity.metric_type == MetricType.COUNTER and metric_entity.reset:
103
- metric_entity.metric.reset()
104
- metric_entity.metric.inc(metric_value)
101
+ metric_entity.metric.labels('').reset()
102
+ if metric_entity.metric_type == MetricType.GAUGE and metric_entity.reset:
103
+ metric_entity.metric.labels('').set(0)
104
+ metric_entity.metric.labels('').inc(metric_value)
105
105
  else:
106
106
  if metric_entity.reset:
107
- try:
108
- metric_entity.metric.remove(*label_values)
109
- except Exception:
110
- pass
107
+ if metric_entity.metric_type == MetricType.COUNTER and metric_entity.reset:
108
+ metric_entity.metric.labels(*label_values).reset()
109
+ if metric_entity.metric_type == MetricType.GAUGE and metric_entity.reset:
110
+ metric_entity.metric.labels(*label_values).set(0)
111
111
  metric_entity.metric.labels(*label_values).inc(metric_value)
112
112
  else:
113
113
  if label_values is None or len(label_values) == 0:
ctools/token.py ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2025/1/21 16:01'
5
+
6
+ import time
7
+ import jwt
8
+ from bottle import request
9
+
10
+ from ctools.dict_wrapper import DictWrapper
11
+
12
+ token_header = 'Authorization'
13
+
14
+ def gen_token(payload: {}, secret_key, expired: int=3600) -> str:
15
+ payload.update({'exp': time.time() + expired})
16
+ return jwt.encode(payload, secret_key, algorithm='HS256')
17
+
18
+ def get_payload(token, secret_key):
19
+ try:
20
+ payload = jwt.decode(token, secret_key, algorithms=['HS256'])
21
+ return DictWrapper(payload)
22
+ except Exception as e:
23
+ return None
24
+
25
+ def get_token(key):
26
+ return get_payload(request.get_header(token_header), key)
27
+
28
+ def is_valid(key):
29
+ return get_payload(request.get_header(token_header), key) is not None
30
+
31
+ # if __name__ == '__main__':
32
+ # token = gen_token({"xx": 123}, '123')
33
+ # xx = get_payload(token, '123')
34
+ # print(xx.xx)
ctools/work_path.py CHANGED
@@ -33,13 +33,16 @@ def get_app_path(subPath: str = '') -> str:
33
33
  return os.path.join(base_path, subPath)
34
34
 
35
35
 
36
- def get_user_work_path(subPath: str = '') -> str:
36
+ def get_user_work_path(subPath: str = '', mkdir: bool = False):
37
37
  """
38
38
  获取用户工作目录
39
39
  :param subPath: 拼接的子路径
40
+ :param mkdir: 是否创建目录
40
41
  :return: 用户工作目录
41
42
  """
42
- return os.path.join(os.path.expanduser("~"), subPath)
43
+ path = os.path.join(os.path.expanduser("~"), subPath)
44
+ if mkdir and not os.path.exists(path): os.makedirs(path, exist_ok=True)
45
+ return path
43
46
 
44
47
 
45
48
  def get_user_temp_path(subPath: str = '') -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: gomyck-tools
3
- Version: 1.2.5
3
+ Version: 1.2.7
4
4
  Summary: A ctools for python development by hao474798383
5
5
  Home-page: https://blog.gomyck.com
6
6
  Author: gomyck
@@ -28,5 +28,7 @@ Requires-Dist: fuzzywuzzy ~=0.18.0
28
28
  Requires-Dist: pymysql ~=1.1.1
29
29
  Requires-Dist: pyzipper ==0.3.6
30
30
  Requires-Dist: prometheus-client ==0.21.1
31
+ Requires-Dist: paramiko ==3.5.0
32
+ Requires-Dist: pyjwt ==2.10.1
31
33
 
32
34
  this package is for python development
@@ -1,11 +1,11 @@
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=BLoJiTHFAIFRPCc4zer7NHTsRQFMY1TH-qmxSMk8iLU,1208
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=pP4lgaVVZZQBMg-9QZJ145kDv4F-hdSCSfH4BDeqyWU,4904
8
- ctools/bottle_webserver.py,sha256=hyTJrfJHAh4XXQ3zPMoF6lmdJ8FTatEpEASPfGrAa2s,2779
7
+ ctools/bottle_web_base.py,sha256=VUAw3GBpoamLDXQJZQScbNPWIysVexns7QcMV-kcLSM,5822
8
+ ctools/bottle_webserver.py,sha256=tZc9-By_kZgyfRKUSb7tCbEsa5d7X4V3Gcj80p9tb2Q,3489
9
9
  ctools/bottle_websocket.py,sha256=wjsjKVE_tlon0hYfnzBT0Sis6yNPe318vKgTfzWYbfQ,1903
10
10
  ctools/browser_element_tools.py,sha256=IFR_tWu5on0LxhuC_4yT6EOjwCsC-juIoU8KQRDqR7E,9952
11
11
  ctools/call.py,sha256=BCr8wzt5qd70okv8IZn-9-EpjywleZgvA3u1vfZ_Kt8,1581
@@ -26,7 +26,7 @@ ctools/html_soup.py,sha256=rnr8M3gn3gQGo-wNaNFXDjdzp8AAkv9o4yqfIIfO-zw,1567
26
26
  ctools/http_utils.py,sha256=dG26aci1_YxAyKwYqMKFw4wZAryLkDyvnQ3hURjB6Lk,768
27
27
  ctools/images_tools.py,sha256=TapXYCPqC7GesgrALecxxa_ApuT_dxUG5fqQIJF2bNY,670
28
28
  ctools/imgDialog.py,sha256=zFeyPmpnEn9Ih7-yuJJrePqW8Myj3jC9UYMTDk-umTs,1385
29
- ctools/metrics.py,sha256=5wC3luUmZKhDSN-0XMJzlfhk792u_tw0IBR6g3YHbHI,4893
29
+ ctools/metrics.py,sha256=xKy1uWqvg2BnS14dQxvs_qUQtMuVLlUKECSyErFnBtk,5247
30
30
  ctools/mqtt_utils.py,sha256=ZWSZiiNJLLlkHF95S6LmRmkYt-iIL4K73VdN3b1HaHw,10702
31
31
  ctools/obj.py,sha256=GYS1B8NyjtUIh0HlK9r8avC2eGbK2SJac4C1CGnfGhI,479
32
32
  ctools/pacth.py,sha256=MJ9Du-J9Gv62y4cZKls1jKbl5a5kL2y9bD-gzYUCveQ,2604
@@ -44,14 +44,15 @@ ctools/string_tools.py,sha256=itK59W4Ed4rQzuyHuioNgDRUcBlfb4ZoZnwmS9cJxiI,1887
44
44
  ctools/sys_info.py,sha256=NvKCuBlWHHiW4bDI4tYZUo3QusvODm1HlW6aAkrllnE,4248
45
45
  ctools/sys_log.py,sha256=oqb1S41LosdeZxtogFVgDk8R4sjiHhUeYJLCzHd728E,2805
46
46
  ctools/thread_pool.py,sha256=qb68ULHy1K7u3MC7WP49wDhmgUhgWazd9FRuFbClET4,925
47
+ ctools/token.py,sha256=NZSBGF3lJajJFLRIZoeXmpp8h5cKM0dAH2weySgeORc,882
47
48
  ctools/upload_tools.py,sha256=sqe6K3ZWiyY58pFE5IO5mNaS1znnS7U4c4UqY8noED4,1068
48
49
  ctools/win_canvas.py,sha256=PAxI4i1jalfree9d1YG4damjc2EzaHZrgHZCTgk2GiM,2530
49
50
  ctools/win_control.py,sha256=35f9x_ijSyc4ZDkcT32e9ZIhr_ffNxadynrQfFuIdYo,3489
50
51
  ctools/wordFill.py,sha256=dB1OLt6GLmWdkDV8H20VWbJmY4ggNNI8iHD1ocae2iM,875
51
52
  ctools/word_fill.py,sha256=xeo-P4DOjQUqd-o9XL3g66wQrE2diUPGwFywm8TdVyw,18210
52
53
  ctools/word_fill_entity.py,sha256=eX3G0Gy16hfGpavQSEkCIoKDdTnNgRRJrFvKliETZK8,985
53
- ctools/work_path.py,sha256=i4MTUobqNW2WMrT3mwEC_XYQ0_IhFmKoNpTX2W6A8Tc,1680
54
- gomyck_tools-1.2.5.dist-info/METADATA,sha256=EkR8czXmSe8KiXedT-SxkqIKQAXelkBo4YOjoJuFkIg,1046
55
- gomyck_tools-1.2.5.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
56
- gomyck_tools-1.2.5.dist-info/top_level.txt,sha256=-MiIH9FYRVKp1i5_SVRkaI-71WmF1sZSRrNWFU9ls3s,7
57
- gomyck_tools-1.2.5.dist-info/RECORD,,
54
+ ctools/work_path.py,sha256=OmfYu-Jjg2huRY6Su8zJ_2EGFFhtBZFbobYTwbjJtG4,1817
55
+ gomyck_tools-1.2.7.dist-info/METADATA,sha256=uS-JVNCRR951EEMSzzMThWcONySUcNIj-lG-uJhF4M0,1108
56
+ gomyck_tools-1.2.7.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
57
+ gomyck_tools-1.2.7.dist-info/top_level.txt,sha256=-MiIH9FYRVKp1i5_SVRkaI-71WmF1sZSRrNWFU9ls3s,7
58
+ gomyck_tools-1.2.7.dist-info/RECORD,,