gomyck-tools 1.4.1__py3-none-any.whl → 1.4.2__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.
Files changed (64) hide show
  1. ctools/__init__.py +21 -0
  2. ctools/ai/env_config.py +18 -1
  3. ctools/ai/llm_chat.py +8 -8
  4. ctools/ai/llm_client.py +26 -24
  5. ctools/ai/mcp/mcp_client.py +33 -17
  6. ctools/ai/tools/json_extract.py +3 -2
  7. ctools/ai/tools/quick_tools.py +68 -21
  8. ctools/ai/tools/tool_use_xml_parse.py +2 -1
  9. ctools/ai/tools/xml_extract.py +3 -0
  10. ctools/application.py +19 -17
  11. ctools/auto/browser_element.py +11 -3
  12. ctools/auto/plan_area.py +2 -2
  13. ctools/auto/pty_process.py +0 -1
  14. ctools/auto/screenshot.py +3 -4
  15. ctools/auto/win_canvas.py +10 -4
  16. ctools/auto/win_control.py +8 -4
  17. ctools/call.py +18 -22
  18. ctools/cdate.py +43 -2
  19. ctools/cid.py +3 -4
  20. ctools/cipher/aes_util.py +2 -2
  21. ctools/cipher/b64.py +2 -0
  22. ctools/cipher/czip.py +3 -1
  23. ctools/cipher/rsa.py +6 -1
  24. ctools/cipher/sign.py +1 -0
  25. ctools/cipher/sm_util.py +3 -0
  26. ctools/cjson.py +5 -0
  27. ctools/cron_lite.py +10 -4
  28. ctools/database/database.py +37 -17
  29. ctools/dict_wrapper.py +1 -0
  30. ctools/ex.py +1 -0
  31. ctools/geo/coord_trans.py +94 -94
  32. ctools/geo/douglas_rarefy.py +13 -9
  33. ctools/metrics.py +6 -0
  34. ctools/office/cword.py +7 -7
  35. ctools/office/word_fill.py +1 -4
  36. ctools/path_info.py +29 -0
  37. ctools/pools/process_pool.py +6 -1
  38. ctools/pools/thread_pool.py +6 -2
  39. ctools/similar.py +3 -0
  40. ctools/stream/ckafka.py +11 -5
  41. ctools/stream/credis.py +29 -21
  42. ctools/stream/mqtt_utils.py +2 -2
  43. ctools/sys_info.py +8 -0
  44. ctools/sys_log.py +3 -0
  45. ctools/util/cftp.py +4 -2
  46. ctools/util/http_util.py +1 -0
  47. ctools/util/image_process.py +1 -1
  48. ctools/util/snow_id.py +3 -2
  49. ctools/web/__init__.py +2 -2
  50. ctools/web/aio_web_server.py +19 -9
  51. ctools/web/api_result.py +3 -2
  52. ctools/web/bottle_web_base.py +15 -2
  53. ctools/web/bottle_webserver.py +14 -8
  54. ctools/web/bottle_websocket.py +4 -0
  55. ctools/web/ctoken.py +5 -1
  56. ctools/web/download_util.py +1 -1
  57. ctools/web/params_util.py +4 -0
  58. ctools/web/upload_util.py +1 -1
  59. {gomyck_tools-1.4.1.dist-info → gomyck_tools-1.4.2.dist-info}/METADATA +2 -1
  60. gomyck_tools-1.4.2.dist-info/RECORD +82 -0
  61. gomyck_tools-1.4.1.dist-info/RECORD +0 -82
  62. {gomyck_tools-1.4.1.dist-info → gomyck_tools-1.4.2.dist-info}/WHEEL +0 -0
  63. {gomyck_tools-1.4.1.dist-info → gomyck_tools-1.4.2.dist-info}/licenses/LICENSE +0 -0
  64. {gomyck_tools-1.4.1.dist-info → gomyck_tools-1.4.2.dist-info}/top_level.txt +0 -0
ctools/similar.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from fuzzywuzzy import fuzz, process
2
2
 
3
+
3
4
  def best_match(query: str, choices: list[str], score_cutoff: int = 70):
4
5
  """
5
6
  获取最接近 query 的匹配项
@@ -7,6 +8,7 @@ def best_match(query: str, choices: list[str], score_cutoff: int = 70):
7
8
  """
8
9
  return process.extractOne(query, choices, scorer=fuzz.ratio, score_cutoff=score_cutoff)
9
10
 
11
+
10
12
  def all_matches(query: str, choices: list[str], limit: int = 5):
11
13
  """
12
14
  获取多个相似匹配项
@@ -14,6 +16,7 @@ def all_matches(query: str, choices: list[str], limit: int = 5):
14
16
  """
15
17
  return process.extract(query, choices, scorer=fuzz.ratio, limit=limit)
16
18
 
19
+
17
20
  def is_similar(s1: str, s2: str, threshold: int = 85):
18
21
  """
19
22
  判断两个字符串是否相似
ctools/stream/ckafka.py CHANGED
@@ -41,6 +41,8 @@ consumer.receive_msg('jqxx', callBack=consumer_callback)
41
41
 
42
42
  while True: time.sleep(1)
43
43
  """
44
+
45
+
44
46
  class KafkaInstance:
45
47
  def __init__(self, producer: KafkaProducer, consumer: KafkaConsumer):
46
48
  self.start_consumer = False
@@ -52,7 +54,7 @@ class KafkaInstance:
52
54
  # FutureRecordMetadata 可以添加回调, 来监听是否发送成功
53
55
  # r.add_callback(lambda x: print(x))
54
56
  # r.get() 可以同步获取结果
55
- def send_msg(self, topic, msg, key: str=None, partition:int=None) -> FutureRecordMetadata:
57
+ def send_msg(self, topic, msg, key: str = None, partition: int = None) -> FutureRecordMetadata:
56
58
  if self.producer is None: raise RuntimeError("Producer is not initialized")
57
59
  if self.quited: return
58
60
  return self.producer.send(topic=topic, value=msg, key=None if key is None else key.encode('utf-8'), partition=partition)
@@ -85,10 +87,14 @@ class KafkaInstance:
85
87
 
86
88
  def shutdown(self):
87
89
  self.quited = True
88
- try: self.consumer.close()
89
- except Exception: pass
90
- try: self.producer.close()
91
- except Exception: pass
90
+ try:
91
+ self.consumer.close()
92
+ except Exception:
93
+ pass
94
+ try:
95
+ self.producer.close()
96
+ except Exception:
97
+ pass
92
98
 
93
99
 
94
100
  class CKafka:
ctools/stream/credis.py CHANGED
@@ -6,32 +6,33 @@ __date__ = '2025/2/14 11:09'
6
6
  import redis
7
7
  from redis import Redis
8
8
 
9
- from ctools.pools import thread_pool
10
9
  from ctools import cdate, cid
10
+ from ctools.pools import thread_pool
11
11
 
12
12
 
13
13
  def init_pool(host: str = 'localhost', port: int = 6379, db: int = 0, password: str = None,
14
14
  username: str = None, decode_responses: bool = True, max_connections: int = 75,
15
15
  health_check_interval: int = 30, retry_count: int = 3) -> Redis:
16
- for attempt in range(retry_count):
17
- try:
18
- r: Redis = redis.StrictRedis(
19
- host=host, port=port, db=db,
20
- username=username, password=password,
21
- retry_on_timeout=True,
22
- max_connections=max_connections,
23
- decode_responses=decode_responses,
24
- health_check_interval=health_check_interval,
25
- socket_connect_timeout=5,
26
- socket_timeout=5
27
- )
28
- if r.ping():
29
- print('CRedis connect {} {} success!'.format(host, port))
30
- return r
31
- except redis.ConnectionError as e:
32
- if attempt == retry_count - 1:
33
- raise Exception(f"Failed to connect to Redis after {retry_count} attempts: {str(e)}")
34
- print(f"Connection attempt {attempt + 1} failed, retrying...")
16
+ for attempt in range(retry_count):
17
+ try:
18
+ r: Redis = redis.StrictRedis(
19
+ host=host, port=port, db=db,
20
+ username=username, password=password,
21
+ retry_on_timeout=True,
22
+ max_connections=max_connections,
23
+ decode_responses=decode_responses,
24
+ health_check_interval=health_check_interval,
25
+ socket_connect_timeout=5,
26
+ socket_timeout=5
27
+ )
28
+ if r.ping():
29
+ print('CRedis connect {} {} success!'.format(host, port))
30
+ return r
31
+ except redis.ConnectionError as e:
32
+ if attempt == retry_count - 1:
33
+ raise Exception(f"Failed to connect to Redis after {retry_count} attempts: {str(e)}")
34
+ print(f"Connection attempt {attempt + 1} failed, retrying...")
35
+
35
36
 
36
37
  def add_lock(r: Redis, key: str, timeout: int = 30):
37
38
  if r.exists(key):
@@ -42,17 +43,21 @@ def add_lock(r: Redis, key: str, timeout: int = 30):
42
43
  r.delete(key)
43
44
  return r.set(key, cdate.opt_time(seconds=timeout), nx=True, ex=timeout) is not None
44
45
 
46
+
45
47
  def remove_lock(r: Redis, key: str):
46
48
  r.delete(key)
47
49
 
50
+
48
51
  def subscribe(r: Redis, channel_name, callback):
49
52
  def thread_func():
50
53
  pubsub = r.pubsub()
51
54
  pubsub.subscribe(channel_name)
52
55
  for message in pubsub.listen():
53
56
  callback(message)
57
+
54
58
  thread_pool.submit(thread_func)
55
59
 
60
+
56
61
  def _process_pending_messages(r: Redis, stream_name: str, group_name: str, consumer_name: str, callback):
57
62
  """
58
63
  处理未确认的消息
@@ -84,7 +89,8 @@ def _process_pending_messages(r: Redis, stream_name: str, group_name: str, consu
84
89
  else:
85
90
  print("No pending messages found.")
86
91
 
87
- def stream_subscribe(r: Redis, stream_name, group_name, callback, from_id: str='$', noack: bool = False):
92
+
93
+ def stream_subscribe(r: Redis, stream_name, group_name, callback, from_id: str = '$', noack: bool = False):
88
94
  def thread_func():
89
95
  try:
90
96
  # $表示从最后面消费, 0表示从开始消费
@@ -107,7 +113,9 @@ def stream_subscribe(r: Redis, stream_name, group_name, callback, from_id: str='
107
113
  if res: r.xack(stream_name, group_name, message_id)
108
114
  except Exception as e:
109
115
  print('stream_subscribe error: ', e)
116
+
110
117
  thread_pool.submit(thread_func)
111
118
 
119
+
112
120
  def stream_publish(r: Redis, stream_name, message):
113
121
  r.xadd(stream_name, message)
@@ -5,17 +5,17 @@ from typing import Dict
5
5
  from paho.mqtt import client as mqtt
6
6
  from paho.mqtt.enums import CallbackAPIVersion
7
7
 
8
+ from ctools import cid, cdate
8
9
  from ctools import sys_log, cjson, sys_info
9
10
  from ctools.cipher import sm_util
10
11
  from ctools.dict_wrapper import DictWrapper as DictToObj
11
12
  from ctools.pools import thread_pool
12
- from ctools import cid, cdate
13
13
 
14
14
 
15
15
  class MQTTEvent(Enum):
16
-
17
16
  pass
18
17
 
18
+
19
19
  '''
20
20
  MQTT服务使用示例:
21
21
 
ctools/sys_info.py CHANGED
@@ -7,13 +7,16 @@ from ctools.cipher import sm_util
7
7
 
8
8
  MACHINE_KEY = b'EnrGffoorbFyTYoS0902YyT1Fhehj4InpbezIDUuPOg='
9
9
 
10
+
10
11
  class MachineInfo:
11
12
  machine_code = None
12
13
 
14
+
13
15
  def get_user():
14
16
  import getpass
15
17
  return getpass.getuser()
16
18
 
19
+
17
20
  def get_machine_code():
18
21
  if MachineInfo.machine_code: return MachineInfo.machine_code
19
22
  destPath = os.path.join(path_info.get_user_work_path(), "AppData/Local/machine")
@@ -74,6 +77,7 @@ def get_hash_machine_code(origin_code):
74
77
  machine_code = hashlib.md5(code.encode()).hexdigest()
75
78
  return machine_code.upper()
76
79
 
80
+
77
81
  def get_public_ip():
78
82
  import requests
79
83
  try:
@@ -83,6 +87,7 @@ def get_public_ip():
83
87
  except Exception as e:
84
88
  return f"Failed to get public IP: {e}"
85
89
 
90
+
86
91
  def get_local_ipv4():
87
92
  import psutil
88
93
  import socket
@@ -102,6 +107,7 @@ def get_local_ipv4():
102
107
  s.close()
103
108
  return ip
104
109
 
110
+
105
111
  def get_remote_ipv4():
106
112
  from bottle import request
107
113
  try:
@@ -109,6 +115,7 @@ def get_remote_ipv4():
109
115
  except:
110
116
  return '127.0.0.1'
111
117
 
118
+
112
119
  def get_proc_pid_by(cmdline):
113
120
  import psutil
114
121
  """
@@ -127,6 +134,7 @@ def get_proc_pid_by(cmdline):
127
134
  pass
128
135
  return pid_list
129
136
 
137
+
130
138
  def get_os_architecture():
131
139
  if '64' in platform.machine():
132
140
  return '64'
ctools/sys_log.py CHANGED
@@ -48,6 +48,7 @@ def _console_log(log_level: int = logging.INFO) -> logging:
48
48
  import io
49
49
  import logging
50
50
 
51
+
51
52
  class StreamToLogger(io.StringIO):
52
53
  def __init__(self, logger: logging.Logger, level: int = logging.INFO):
53
54
  super().__init__()
@@ -80,6 +81,7 @@ class StreamToLogger(io.StringIO):
80
81
  def fileno(self):
81
82
  return sys.__stdout__.fileno()
82
83
 
84
+
83
85
  @call.init
84
86
  def _init_log() -> None:
85
87
  global flog, clog
@@ -88,6 +90,7 @@ def _init_log() -> None:
88
90
  sys.stdout = StreamToLogger(flog, level=logging.INFO)
89
91
  sys.stderr = StreamToLogger(flog, level=logging.ERROR)
90
92
 
93
+
91
94
  def setLevel(log_level=logging.INFO):
92
95
  flog.setLevel(log_level)
93
96
  clog.setLevel(log_level)
ctools/util/cftp.py CHANGED
@@ -7,6 +7,7 @@ import io
7
7
  import time
8
8
  from ftplib import FTP, error_perm, error_temp
9
9
 
10
+
10
11
  class CFTP:
11
12
  """
12
13
  with open('xx/xx.md', 'rb') as file:
@@ -15,16 +16,17 @@ class CFTP:
15
16
  ftp_password = 'x'
16
17
  CFTP(ftp_host, ftp_username, ftp_password).upload_file_to_ftp('xx.md', file)
17
18
  """
19
+
18
20
  def __init__(self, host, username, password, timeout=30, max_retries=3, retry_delay=5):
19
21
  self.host = host
20
22
  self.username = username
21
23
  self.password = password
22
- self.ftp:FTP = None
24
+ self.ftp: FTP = None
23
25
  self.timeout = timeout
24
26
  self.max_retries = max_retries
25
27
  self.retry_delay = retry_delay
26
28
 
27
- def upload_file_to_ftp(self, file_name, file:io.BytesIO, ftp_directory='/'):
29
+ def upload_file_to_ftp(self, file_name, file: io.BytesIO, ftp_directory='/'):
28
30
  if not file_name: raise Exception('文件名不能为空')
29
31
  if not file: raise Exception('文件不能为空')
30
32
  for attempt in range(self.max_retries):
ctools/util/http_util.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import requests
2
2
 
3
+
3
4
  def get(url, params=None, headers=None):
4
5
  result = ""
5
6
  try:
@@ -6,6 +6,7 @@ from PIL import Image
6
6
  def get_size(image_path):
7
7
  return Image.open(image_path).size
8
8
 
9
+
9
10
  def change_color(image_path, area=None, rgb_color=None):
10
11
  """
11
12
  修改图片指定区域颜色
@@ -24,4 +25,3 @@ def change_color(image_path, area=None, rgb_color=None):
24
25
  img.save(img_bytes, format='JPEG')
25
26
  img_binary = img_bytes.getvalue()
26
27
  return img_binary
27
-
ctools/util/snow_id.py CHANGED
@@ -16,10 +16,12 @@ SEQUENCE_MASK = -1 ^ (-1 << SEQUENCE_BITS)
16
16
  # Twitter元年时间戳
17
17
  TWEPOCH = 1288834974657
18
18
 
19
+
19
20
  class SnowId(object):
20
21
  """
21
22
  用于生成IDs
22
23
  """
24
+
23
25
  def __init__(self, datacenter_id=0, worker_id=0, sequence=0):
24
26
  """
25
27
  初始化
@@ -53,8 +55,7 @@ class SnowId(object):
53
55
  timestamp = self._gen_timestamp()
54
56
  # 时钟回拨
55
57
  if timestamp < self.last_timestamp:
56
- print('clock is moving backwards. Rejecting requests until {}'.format(self.last_timestamp))
57
- raise
58
+ timestamp = self._til_next_millis(self.last_timestamp)
58
59
  if timestamp == self.last_timestamp:
59
60
  self.sequence = (self.sequence + 1) & SEQUENCE_MASK
60
61
  if self.sequence == 0:
ctools/web/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env python
2
2
  # -*- coding: UTF-8 -*-
3
- __author__='haoyang'
4
- __date__='2025/6/11 09:12'
3
+ __author__ = 'haoyang'
4
+ __date__ = '2025/6/11 09:12'
@@ -5,12 +5,12 @@
5
5
  __author__ = 'haoyang'
6
6
  __date__ = '2025/5/30 09:54'
7
7
 
8
+ import asyncio
8
9
  import sys
9
10
  from pathlib import Path
10
11
  from typing import Optional, Dict, Any
11
12
 
12
13
  from aiohttp import web
13
-
14
14
  from ctools import sys_info, cjson
15
15
  from ctools.sys_log import flog as log
16
16
  from ctools.web.api_result import R
@@ -30,13 +30,14 @@ async def response_wrapper_middleware(request, handler):
30
30
  else:
31
31
  return result
32
32
  except web.HTTPException as http_exc:
33
- raise http_exc
33
+ raise http_exc
34
34
  except Exception as e:
35
35
  log.error(f"Error in response_wrapper_middleware: {e}", exc_info=True)
36
36
  return web.json_response(text=R.error(str(e)), status=500, content_type='application/json')
37
37
 
38
+
38
39
  class AioHttpServer:
39
- def __init__(self, port: int = DEFAULT_PORT, app: Optional[web.Application] = None, routes: Optional[web.RouteTableDef] = None, async_func = None):
40
+ def __init__(self, port: int = DEFAULT_PORT, app: Optional[web.Application] = None, routes: Optional[web.RouteTableDef] = None, async_func=None):
40
41
  """
41
42
  Initialize the HTTP server.
42
43
 
@@ -142,26 +143,35 @@ class AioHttpServer:
142
143
  """Run the server."""
143
144
  if self.async_func:
144
145
  await self.async_func()
146
+
145
147
  print(
146
148
  'Server running at:\n'
147
149
  f'\tLocal: http://localhost:{self.port}\n'
148
150
  f'\tNetwork: http://{sys_info.get_local_ipv4()}:{self.port}',
149
151
  file=sys.stderr
150
152
  )
151
- await web._run_app(
152
- self.app,
153
- port=self.port,
154
- host='0.0.0.0'
155
- )
153
+ runner = None
154
+ try:
155
+ runner = web.AppRunner(self.app)
156
+ await runner.setup()
157
+ site = web.TCPSite(runner, host='0.0.0.0', port=self.port)
158
+ await site.start()
159
+ while True: await asyncio.sleep(3600)
160
+ except Exception as e:
161
+ print(f"Server failed to start: {e}", file=sys.stderr)
162
+ finally:
163
+ await runner.cleanup()
156
164
 
157
165
 
158
166
  def init_routes() -> web.RouteTableDef:
159
167
  return web.RouteTableDef()
160
168
 
161
- def init_server(routes: Optional[web.RouteTableDef] = None, app: Optional[web.Application] = None, port: int = DEFAULT_PORT, async_func = None) -> AioHttpServer:
169
+
170
+ def init_server(routes: Optional[web.RouteTableDef] = None, app: Optional[web.Application] = None, port: int = DEFAULT_PORT, async_func=None) -> AioHttpServer:
162
171
  """Initialize and return a new AioHttpServer instance."""
163
172
  return AioHttpServer(port=port, app=app, routes=routes, async_func=async_func)
164
173
 
174
+
165
175
  async def get_stream_resp(request, content_type: str = 'text/event-stream') -> web.StreamResponse:
166
176
  resp = web.StreamResponse(
167
177
  status=200,
ctools/web/api_result.py CHANGED
@@ -13,6 +13,7 @@ class _ResEnum(object):
13
13
  def __eq__(self, o: object) -> bool:
14
14
  return self.code == o
15
15
 
16
+
16
17
  class R(object):
17
18
  class Code:
18
19
 
@@ -21,8 +22,8 @@ class R(object):
21
22
  return _ResEnum(code, msg)
22
23
 
23
24
  SUCCESS = _ResEnum(200, "成功")
24
- FAIL = _ResEnum(400, "失败")
25
- ERROR = _ResEnum(500, "异常")
25
+ FAIL = _ResEnum(400, "失败")
26
+ ERROR = _ResEnum(500, "异常")
26
27
 
27
28
  def __init__(self, code: int, message: str, data=""):
28
29
  self.code = code
@@ -13,12 +13,14 @@ from ctools.web.api_result import R
13
13
  bottle.BaseRequest.MEMFILE_MAX = 1024 * 1024 * 50
14
14
  func_has_params = {}
15
15
 
16
+
16
17
  class GlobalState:
17
18
  lock = threading.Lock()
18
19
  withOutLoginURI = [
19
20
  '/',
20
21
  '/index',
21
- '/login'
22
+ '/login',
23
+ '/favicon.ico',
22
24
  ]
23
25
  allowRemoteCallURI = [
24
26
 
@@ -26,6 +28,7 @@ class GlobalState:
26
28
  token = {}
27
29
  interceptors = []
28
30
 
31
+
29
32
  def init_app(context_path=None):
30
33
  app = Bottle()
31
34
  app.context_path = context_path
@@ -73,6 +76,7 @@ def init_app(context_path=None):
73
76
  app.install(params_resolve)
74
77
  return app
75
78
 
79
+
76
80
  def enable_cors():
77
81
  response.headers['Access-Control-Allow-Origin'] = '*'
78
82
  response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
@@ -80,14 +84,17 @@ def enable_cors():
80
84
  response.headers['Access-Control-Allow-Headers'] = request_headers if request_headers else ''
81
85
  response.headers['Access-Control-Expose-Headers'] = '*'
82
86
 
87
+
83
88
  # annotation
84
89
  def before_intercept(order=0):
85
90
  def decorator(func):
86
91
  log.info("add before interceptor: {}".format(func.__name__))
87
92
  GlobalState.interceptors.append({'order': order, 'func': func})
88
93
  GlobalState.interceptors = sorted(GlobalState.interceptors, key=lambda x: x['order'])
94
+
89
95
  return decorator
90
96
 
97
+
91
98
  # annotation
92
99
  def rule(key):
93
100
  def return_func(func):
@@ -97,9 +104,12 @@ def rule(key):
97
104
  # log.error("系统未授权! {} {}".format(request.fullpath, '当前请求的模块未授权!请联系管理员!'))
98
105
  # return R.error(resp=R.Code.cus_code(9999, "系统未授权! {}".format('当前请求的模块未授权!请联系管理员!')))
99
106
  return func(*args, **kwargs)
107
+
100
108
  return decorated
109
+
101
110
  return return_func
102
111
 
112
+
103
113
  # annotation or plugins, has auto install, don't need to call
104
114
  def params_resolve(func):
105
115
  @wraps(func)
@@ -149,13 +159,16 @@ def params_resolve(func):
149
159
  return func(params=dict_wrapper, *args, **kwargs)
150
160
  else:
151
161
  return func(*args, **kwargs)
162
+
152
163
  return decorated
153
164
 
165
+
154
166
  class PageInfo:
155
167
  def __init__(self, page_size, page_index):
156
168
  self.page_size = page_size
157
169
  self.page_index = page_index
158
170
 
171
+
159
172
  # 通用的鉴权方法
160
173
  def common_auth_verify(aes_key):
161
174
  if request.path.startswith('/static') or request.path in GlobalState.withOutLoginURI:
@@ -166,4 +179,4 @@ def common_auth_verify(aes_key):
166
179
  payload = ctoken.get_payload(auth_token, aes_key)
167
180
  if payload:
168
181
  return R.ok(to_json_str=False)
169
- return R.error(resp=R.Code.cus_code(401, "未授权"), to_json_str=False)
182
+ return R.error(resp=R.Code.cus_code(401, "请登录!"), to_json_str=False)
@@ -22,8 +22,8 @@ def get_ws_modules():
22
22
  """
23
23
 
24
24
  """
25
- from ctools import bottle_web_base, ctoken, bottle_webserver
26
- from ctools.api_result import R
25
+ from ctools.web import bottle_web_base, ctoken, bottle_webserver
26
+ from ctools.web.api_result import R
27
27
 
28
28
  secret_key = "xxx"
29
29
  app = bottle_web_base.init_app('子模块写 context_path, 主模块就不用写任何东西')
@@ -42,14 +42,16 @@ def login(params):
42
42
  def demo(params):
43
43
  print(params)
44
44
 
45
- main_app = bottle_webserver.init_bottle() # 这里可以传 APP 当做主模块, 但是 context_path 就不好使了, 上下文必须是 /
46
- main_app.mount(app.context_path, app)
47
- main_app.set_index(r'轨迹点位压缩.html')
48
- main_app.run()
45
+ if __name__ == '__main__':
46
+ main_app = bottle_webserver.init_bottle() # 这里可以传 APP 当做主模块, 但是 context_path 就不好使了, 上下文必须是 /
47
+ main_app.mount(app.context_path, app)
48
+ main_app.set_index(r'index.html')
49
+ main_app.run()
49
50
  """
50
51
 
51
52
  _default_port = 8888
52
53
 
54
+
53
55
  class CBottle:
54
56
 
55
57
  def __init__(self, bottle: Bottle, port=_default_port, quiet=False):
@@ -98,7 +100,7 @@ class CBottle:
98
100
 
99
101
  def run(self):
100
102
  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)
103
+ 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
104
  self.bottle.run(server=http_server, quiet=self.quiet)
103
105
 
104
106
  def set_index(self, filename='index.html', root='./', is_tpl=False, redirect_url=None, **kwargs):
@@ -118,16 +120,20 @@ class CBottle:
118
120
  if not context_path: return
119
121
  self.bottle.mount(context_path, app, **kwargs)
120
122
 
121
- def init_bottle(app:Bottle=None, port=_default_port, quiet=False) -> CBottle:
123
+
124
+ def init_bottle(app: Bottle = None, port=_default_port, quiet=False) -> CBottle:
122
125
  bottle = app or Bottle()
123
126
  return CBottle(bottle, port, quiet)
124
127
 
128
+
125
129
  class ThreadedWSGIServer(ThreadingMixIn, WSGIServer):
126
130
  daemon_threads = True
127
131
 
132
+
128
133
  class CustomWSGIHandler(WSGIRequestHandler):
129
134
  def log_request(*args, **kw): pass
130
135
 
136
+
131
137
  class WSGIRefServer(ServerAdapter):
132
138
 
133
139
  def __init__(self, host='0.0.0.0', port=_default_port):
@@ -33,6 +33,7 @@ socket_app.run()
33
33
 
34
34
  _default_port = 8887
35
35
 
36
+
36
37
  class CBottle:
37
38
 
38
39
  def __init__(self, bottle: Bottle, port=_default_port, quiet=False):
@@ -47,10 +48,12 @@ class CBottle:
47
48
  def mount(self, context_path, app):
48
49
  self.bottle.mount(context_path, app)
49
50
 
51
+
50
52
  def init_bottle(port=_default_port, quiet=False) -> CBottle:
51
53
  bottle = Bottle()
52
54
  return CBottle(bottle, port, quiet)
53
55
 
56
+
54
57
  class CustomWebSocketHandler(WebSocketHandler):
55
58
  def log_request(self):
56
59
  if '101' not in str(self.status):
@@ -60,6 +63,7 @@ class CustomWebSocketHandler(WebSocketHandler):
60
63
  return
61
64
  self.logger.info(log_msg)
62
65
 
66
+
63
67
  class WebSocketServer(ServerAdapter):
64
68
 
65
69
  def __init__(self, host='0.0.0.0', port=_default_port):
ctools/web/ctoken.py CHANGED
@@ -12,10 +12,12 @@ from ctools.dict_wrapper import DictWrapper
12
12
 
13
13
  token_header = 'Authorization'
14
14
 
15
- def gen_token(payload: {}, secret_key, expired: int=3600) -> str:
15
+
16
+ def gen_token(payload: {}, secret_key, expired: int = 3600) -> str:
16
17
  payload.update({'exp': time.time() + expired})
17
18
  return jwt.encode(payload, secret_key, algorithm='HS256')
18
19
 
20
+
19
21
  def get_payload(token, secret_key):
20
22
  try:
21
23
  payload = jwt.decode(token, secret_key, algorithms=['HS256'])
@@ -23,9 +25,11 @@ def get_payload(token, secret_key):
23
25
  except Exception as e:
24
26
  return None
25
27
 
28
+
26
29
  def get_token(key):
27
30
  return get_payload(request.get_header(token_header), key)
28
31
 
32
+
29
33
  def is_valid(key):
30
34
  return get_payload(request.get_header(token_header), key) is not None
31
35
 
@@ -13,7 +13,7 @@ log = sys_log.flog
13
13
  """
14
14
 
15
15
 
16
- def download(file_path: str, download_name:str=None):
16
+ def download(file_path: str, download_name: str = None):
17
17
  """
18
18
  文件下载
19
19
  :param file_path: 静态文件路径
ctools/web/params_util.py CHANGED
@@ -3,6 +3,7 @@
3
3
  __author__ = 'haoyang'
4
4
  __date__ = '2025/6/11 09:35'
5
5
 
6
+
6
7
  def is_list(v: str):
7
8
  try:
8
9
  list(v)
@@ -13,6 +14,7 @@ def is_list(v: str):
13
14
  except Exception:
14
15
  return False
15
16
 
17
+
16
18
  def is_digit(v: str):
17
19
  try:
18
20
  float(v)
@@ -20,12 +22,14 @@ def is_digit(v: str):
20
22
  except Exception:
21
23
  return False
22
24
 
25
+
23
26
  def is_bool(v: str):
24
27
  if v in ["False", "True"]:
25
28
  return True
26
29
  else:
27
30
  return False
28
31
 
32
+
29
33
  def dict_to_params(obj: dict):
30
34
  params = ""
31
35
  for k, v in obj.items():
ctools/web/upload_util.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import os
2
2
 
3
- from ctools import sys_log
4
3
  from ctools import cdate
4
+ from ctools import sys_log
5
5
 
6
6
  log = sys_log.flog
7
7