gomyck-tools 1.0.0__py3-none-any.whl → 1.4.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/__init__.py +21 -0
- ctools/ai/__init__.py +4 -0
- ctools/ai/llm_chat.py +184 -0
- ctools/ai/llm_client.py +163 -0
- ctools/ai/llm_exception.py +17 -0
- ctools/ai/mcp/__init__.py +4 -0
- ctools/ai/mcp/mcp_client.py +326 -0
- ctools/ai/tools/__init__.py +4 -0
- ctools/ai/tools/json_extract.py +202 -0
- ctools/ai/tools/quick_tools.py +149 -0
- ctools/ai/tools/think_process.py +11 -0
- ctools/ai/tools/tool_use_xml_parse.py +40 -0
- ctools/ai/tools/xml_extract.py +15 -0
- ctools/application.py +50 -47
- ctools/aspect.py +65 -0
- ctools/auto/__init__.py +4 -0
- ctools/{browser_element_tools.py → auto/browser_element.py} +18 -8
- ctools/{plan_area_tools.py → auto/plan_area.py} +5 -7
- ctools/{pty_tools.py → auto/pty_process.py} +6 -3
- ctools/{resource_bundle_tools.py → auto/resource_bundle.py} +4 -4
- ctools/{screenshot_tools.py → auto/screenshot.py} +7 -6
- ctools/{win_canvas.py → auto/win_canvas.py} +10 -4
- ctools/{win_control.py → auto/win_control.py} +14 -5
- ctools/call.py +34 -49
- ctools/cdate.py +84 -0
- ctools/cdebug.py +146 -0
- ctools/cid.py +20 -0
- ctools/cipher/__init__.py +4 -0
- ctools/{aes_tools.py → cipher/aes_util.py} +10 -0
- ctools/{b64.py → cipher/b64.py} +2 -0
- ctools/cipher/czip.py +133 -0
- ctools/cipher/rsa.py +75 -0
- ctools/{sign.py → cipher/sign.py} +2 -1
- ctools/{sm_tools.py → cipher/sm_util.py} +20 -4
- ctools/cjson.py +10 -10
- ctools/cron_lite.py +109 -97
- ctools/database/__init__.py +4 -0
- ctools/{database.py → database/database.py} +93 -26
- ctools/dict_wrapper.py +21 -0
- ctools/ex.py +4 -0
- ctools/geo/__init__.py +4 -0
- ctools/geo/coord_trans.py +127 -0
- ctools/geo/douglas_rarefy.py +139 -0
- ctools/metrics.py +56 -63
- ctools/office/__init__.py +4 -0
- ctools/office/cword.py +30 -0
- ctools/{word_fill.py → office/word_fill.py} +3 -6
- ctools/patch.py +88 -0
- ctools/{work_path.py → path_info.py} +34 -2
- ctools/pkg/__init__.py +4 -0
- ctools/pkg/dynamic_imp.py +38 -0
- ctools/pools/__init__.py +4 -0
- ctools/pools/process_pool.py +41 -0
- ctools/{thread_pool.py → pools/thread_pool.py} +13 -4
- ctools/similar.py +25 -0
- ctools/stream/__init__.py +4 -0
- ctools/stream/ckafka.py +164 -0
- ctools/stream/credis.py +127 -0
- ctools/{mqtt_utils.py → stream/mqtt_utils.py} +20 -12
- ctools/sys_info.py +36 -13
- ctools/sys_log.py +46 -27
- ctools/util/__init__.py +4 -0
- ctools/util/cftp.py +76 -0
- ctools/util/cklock.py +118 -0
- ctools/util/config_util.py +52 -0
- ctools/util/env_config.py +63 -0
- ctools/{html_soup.py → util/html_soup.py} +0 -7
- ctools/{http_utils.py → util/http_util.py} +4 -2
- ctools/{images_tools.py → util/image_process.py} +10 -1
- ctools/util/jb_cut.py +54 -0
- ctools/{id_worker_tools.py → util/snow_id.py} +8 -23
- ctools/web/__init__.py +4 -0
- ctools/web/aio_web_server.py +186 -0
- ctools/web/api_result.py +56 -0
- ctools/web/bottle_web_base.py +239 -0
- ctools/web/bottle_webserver.py +191 -0
- ctools/web/bottle_websocket.py +79 -0
- ctools/web/ctoken.py +103 -0
- ctools/{download_tools.py → web/download_util.py} +14 -12
- ctools/web/params_util.py +46 -0
- ctools/{upload_tools.py → web/upload_util.py} +3 -2
- gomyck_tools-1.4.7.dist-info/METADATA +70 -0
- gomyck_tools-1.4.7.dist-info/RECORD +88 -0
- {gomyck_tools-1.0.0.dist-info → gomyck_tools-1.4.7.dist-info}/WHEEL +1 -1
- gomyck_tools-1.4.7.dist-info/licenses/LICENSE +13 -0
- ctools/bashPath.py +0 -13
- ctools/bottle_server.py +0 -49
- ctools/console.py +0 -55
- ctools/date_utils.py +0 -44
- ctools/enums.py +0 -4
- ctools/excelOpt.py +0 -36
- ctools/imgDialog.py +0 -44
- ctools/license.py +0 -37
- ctools/log.py +0 -28
- ctools/mvc.py +0 -56
- ctools/obj.py +0 -20
- ctools/pacth.py +0 -73
- ctools/ssh.py +0 -9
- ctools/strDiff.py +0 -20
- ctools/string_tools.py +0 -101
- ctools/token_tools.py +0 -13
- ctools/wordFill.py +0 -24
- gomyck_tools-1.0.0.dist-info/METADATA +0 -20
- gomyck_tools-1.0.0.dist-info/RECORD +0 -54
- /ctools/{word_fill_entity.py → office/word_fill_entity.py} +0 -0
- /ctools/{compile_tools.py → util/compile_util.py} +0 -0
- {gomyck_tools-1.0.0.dist-info → gomyck_tools-1.4.7.dist-info}/top_level.txt +0 -0
ctools/sys_log.py
CHANGED
|
@@ -3,20 +3,13 @@ import os
|
|
|
3
3
|
import sys
|
|
4
4
|
import time
|
|
5
5
|
|
|
6
|
-
from ctools import
|
|
6
|
+
from ctools import call, path_info
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
clog, flog = None, None
|
|
8
|
+
clog: logging.Logger = None
|
|
9
|
+
flog: logging.Logger = None
|
|
11
10
|
|
|
12
11
|
neglect_keywords = [
|
|
13
12
|
"OPTIONS",
|
|
14
|
-
'/scheduleInfo/list',
|
|
15
|
-
'/sys/get_sys_state',
|
|
16
|
-
'/scheduleInfo/tmpList',
|
|
17
|
-
'/sys/getSysLog',
|
|
18
|
-
'/downloadManage/static_file',
|
|
19
|
-
'/downloadManage/static_images'
|
|
20
13
|
]
|
|
21
14
|
|
|
22
15
|
|
|
@@ -27,13 +20,13 @@ def _file_log(sys_log_path: str = './', log_level: int = logging.INFO, mixin: bo
|
|
|
27
20
|
os.mkdir(sys_log_path)
|
|
28
21
|
except Exception:
|
|
29
22
|
pass
|
|
30
|
-
log_file = sys_log_path + os.path.sep + "log-" + time.strftime("%Y-%m-%d-%H
|
|
23
|
+
log_file = sys_log_path + os.path.sep + "log-" + time.strftime("%Y-%m-%d-%H", time.localtime(time.time())) + ".log"
|
|
31
24
|
if mixin:
|
|
32
25
|
handlers = [logging.FileHandler(filename=log_file, encoding='utf-8'), logging.StreamHandler()]
|
|
33
26
|
else:
|
|
34
27
|
handlers = [logging.FileHandler(filename=log_file, encoding='utf-8')]
|
|
35
28
|
logging.basicConfig(level=log_level,
|
|
36
|
-
format='%(asctime)s
|
|
29
|
+
format='%(asctime)s | %(levelname)-5s | T%(thread)d | %(module)s.%(funcName)s:%(lineno)d: %(message)s',
|
|
37
30
|
datefmt='%Y%m%d%H%M%S',
|
|
38
31
|
handlers=handlers)
|
|
39
32
|
logger = logging.getLogger('ck-flog')
|
|
@@ -45,33 +38,59 @@ def _file_log(sys_log_path: str = './', log_level: int = logging.INFO, mixin: bo
|
|
|
45
38
|
def _console_log(log_level: int = logging.INFO) -> logging:
|
|
46
39
|
handler = logging.StreamHandler()
|
|
47
40
|
logging.basicConfig(level=log_level,
|
|
48
|
-
format='%(asctime)s
|
|
41
|
+
format='%(asctime)s | %(levelname)-5s | T%(thread)d | %(name)s | %(module)s.%(funcName)s:%(lineno)d: %(message)s',
|
|
49
42
|
datefmt='%Y%m%d%H%M%S',
|
|
50
43
|
handlers=[handler])
|
|
51
44
|
logger = logging.getLogger('ck-clog')
|
|
52
45
|
return logger
|
|
53
46
|
|
|
54
47
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
import io
|
|
49
|
+
import logging
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class StreamToLogger(io.StringIO):
|
|
53
|
+
def __init__(self, logger: logging.Logger, level: int = logging.INFO):
|
|
54
|
+
super().__init__()
|
|
55
|
+
self.logger = logger
|
|
56
|
+
self.level = level
|
|
57
|
+
self._buffer = ''
|
|
60
58
|
|
|
61
|
-
def write(self, message):
|
|
62
|
-
if message
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
def write(self, message: str):
|
|
60
|
+
if not message:
|
|
61
|
+
return
|
|
62
|
+
self._buffer += message
|
|
63
|
+
if '\n' in self._buffer:
|
|
64
|
+
lines = self._buffer.splitlines(keepends=False)
|
|
65
|
+
for line in lines:
|
|
66
|
+
if line.strip():
|
|
67
|
+
try:
|
|
68
|
+
self.logger.log(self.level, line.strip(), stacklevel=3)
|
|
69
|
+
except Exception:
|
|
70
|
+
self.logger.log(self.level, line.strip())
|
|
71
|
+
self._buffer = ''
|
|
67
72
|
|
|
68
73
|
def flush(self):
|
|
69
|
-
|
|
74
|
+
if self._buffer.strip():
|
|
75
|
+
try:
|
|
76
|
+
self.logger.log(self.level, self._buffer.strip(), stacklevel=3)
|
|
77
|
+
except Exception:
|
|
78
|
+
self.logger.log(self.level, self._buffer.strip())
|
|
79
|
+
self._buffer = ''
|
|
80
|
+
|
|
81
|
+
def fileno(self):
|
|
82
|
+
return sys.__stdout__.fileno()
|
|
70
83
|
|
|
71
84
|
|
|
72
85
|
@call.init
|
|
73
86
|
def _init_log() -> None:
|
|
74
87
|
global flog, clog
|
|
75
|
-
flog = _file_log(
|
|
88
|
+
flog = _file_log(path_info.get_user_work_path(".ck/ck-py-log", mkdir=True), mixin=True, log_level=logging.DEBUG)
|
|
76
89
|
clog = _console_log()
|
|
77
|
-
|
|
90
|
+
sys.stdout = StreamToLogger(flog, level=logging.INFO)
|
|
91
|
+
sys.stderr = StreamToLogger(flog, level=logging.ERROR)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def setLevel(log_level=logging.INFO):
|
|
95
|
+
flog.setLevel(log_level)
|
|
96
|
+
clog.setLevel(log_level)
|
ctools/util/__init__.py
ADDED
ctools/util/cftp.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2025/1/24 08:53'
|
|
5
|
+
|
|
6
|
+
import io
|
|
7
|
+
import time
|
|
8
|
+
from ftplib import FTP, error_perm, error_temp
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CFTP:
|
|
12
|
+
"""
|
|
13
|
+
with open('xx/xx.md', 'rb') as file:
|
|
14
|
+
ftp_host = 'x.x.x.x'
|
|
15
|
+
ftp_username = 'x'
|
|
16
|
+
ftp_password = 'x'
|
|
17
|
+
CFTP(ftp_host, ftp_username, ftp_password).upload_file_to_ftp('xx.md', file)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, host, username, password, timeout=30, max_retries=3, retry_delay=5):
|
|
21
|
+
self.host = host
|
|
22
|
+
self.username = username
|
|
23
|
+
self.password = password
|
|
24
|
+
self.ftp: FTP = None
|
|
25
|
+
self.timeout = timeout
|
|
26
|
+
self.max_retries = max_retries
|
|
27
|
+
self.retry_delay = retry_delay
|
|
28
|
+
|
|
29
|
+
def upload_file_to_ftp(self, file_name, file: io.BytesIO, ftp_directory='/'):
|
|
30
|
+
if not file_name: raise Exception('文件名不能为空')
|
|
31
|
+
if not file: raise Exception('文件不能为空')
|
|
32
|
+
for attempt in range(self.max_retries):
|
|
33
|
+
try:
|
|
34
|
+
if not self.ftp:
|
|
35
|
+
ftp = FTP(self.host, timeout=self.timeout)
|
|
36
|
+
ftp.login(self.username, self.password)
|
|
37
|
+
ftp.set_pasv(True)
|
|
38
|
+
self.ftp = ftp
|
|
39
|
+
try:
|
|
40
|
+
self.ftp.cwd(ftp_directory)
|
|
41
|
+
except error_perm:
|
|
42
|
+
print(f"FTP 目录不存在:{ftp_directory}")
|
|
43
|
+
self.ftp.mkd(ftp_directory)
|
|
44
|
+
print(f"FTP 目录创建成功:{ftp_directory}, 正在切换到目录:{ftp_directory}")
|
|
45
|
+
self.ftp.cwd(ftp_directory)
|
|
46
|
+
print(f"正在上传文件:{file_name}")
|
|
47
|
+
self.ftp.storbinary(f"STOR {file_name}", file)
|
|
48
|
+
self.ftp.quit()
|
|
49
|
+
print(f"文件成功上传到 FTP: {file_name}")
|
|
50
|
+
return
|
|
51
|
+
except (error_perm, error_temp) as e:
|
|
52
|
+
try:
|
|
53
|
+
self.ftp.quit()
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
self.ftp = None
|
|
57
|
+
print(f"FTP 错误:{e}")
|
|
58
|
+
if attempt < self.max_retries - 1:
|
|
59
|
+
print(f"正在尝试重连... 第 {attempt + 1} 次重试...")
|
|
60
|
+
time.sleep(self.retry_delay)
|
|
61
|
+
else:
|
|
62
|
+
print("重试次数已用尽,上传失败。")
|
|
63
|
+
raise
|
|
64
|
+
except Exception as e:
|
|
65
|
+
try:
|
|
66
|
+
self.ftp.quit()
|
|
67
|
+
except Exception:
|
|
68
|
+
pass
|
|
69
|
+
self.ftp = None
|
|
70
|
+
print(f"连接或上传出现异常:{e}")
|
|
71
|
+
if attempt < self.max_retries - 1:
|
|
72
|
+
print(f"正在尝试重连... 第 {attempt + 1} 次重试...")
|
|
73
|
+
time.sleep(self.retry_delay)
|
|
74
|
+
else:
|
|
75
|
+
print("重试次数已用尽,上传失败。")
|
|
76
|
+
raise
|
ctools/util/cklock.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2025/7/18 15:46'
|
|
5
|
+
|
|
6
|
+
import contextvars
|
|
7
|
+
import threading
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from functools import wraps
|
|
10
|
+
|
|
11
|
+
from ctools.stream.credis import get_redis, add_lock, remove_lock
|
|
12
|
+
from ctools.web import ctoken
|
|
13
|
+
from ctools.web.api_result import R
|
|
14
|
+
|
|
15
|
+
# 全局锁容器
|
|
16
|
+
_lock_dict = {}
|
|
17
|
+
_lock_dict_lock = threading.Lock()
|
|
18
|
+
|
|
19
|
+
def try_acquire_lock(key: str) -> bool:
|
|
20
|
+
with _lock_dict_lock:
|
|
21
|
+
if key not in _lock_dict:
|
|
22
|
+
_lock_dict[key] = threading.Lock()
|
|
23
|
+
return _lock_dict[key].acquire(blocking=False)
|
|
24
|
+
|
|
25
|
+
def try_acquire_lock_block(key: str):
|
|
26
|
+
with _lock_dict_lock:
|
|
27
|
+
if key not in _lock_dict:
|
|
28
|
+
_lock_dict[key] = threading.Lock()
|
|
29
|
+
_lock = _lock_dict[key]
|
|
30
|
+
_lock.acquire() # 这里是阻塞的
|
|
31
|
+
|
|
32
|
+
def release_lock(key: str):
|
|
33
|
+
with _lock_dict_lock:
|
|
34
|
+
_lock = _lock_dict.get(key)
|
|
35
|
+
if _lock and _lock.locked():
|
|
36
|
+
_lock.release()
|
|
37
|
+
if _lock and not _lock.locked():
|
|
38
|
+
_lock_dict.pop(key, None)
|
|
39
|
+
|
|
40
|
+
@contextmanager
|
|
41
|
+
def try_lock(key: str="sys_lock", block=False):
|
|
42
|
+
if not block:
|
|
43
|
+
acquired = try_acquire_lock(key)
|
|
44
|
+
try:
|
|
45
|
+
yield acquired
|
|
46
|
+
finally:
|
|
47
|
+
if acquired:
|
|
48
|
+
release_lock(key)
|
|
49
|
+
else:
|
|
50
|
+
try_acquire_lock_block(key)
|
|
51
|
+
try:
|
|
52
|
+
yield
|
|
53
|
+
finally:
|
|
54
|
+
release_lock(key)
|
|
55
|
+
|
|
56
|
+
#annotation
|
|
57
|
+
"""
|
|
58
|
+
@lock("params.attr")
|
|
59
|
+
"""
|
|
60
|
+
# 上下文保存锁key集合
|
|
61
|
+
current_locks = contextvars.ContextVar("current_locks", default=set())
|
|
62
|
+
|
|
63
|
+
def lock(lock_attrs=None):
|
|
64
|
+
def decorator(func):
|
|
65
|
+
@wraps(func)
|
|
66
|
+
def wrapper(*args, **kwargs):
|
|
67
|
+
lock_key = ""
|
|
68
|
+
nonlocal lock_attrs
|
|
69
|
+
user_level_lock = False
|
|
70
|
+
|
|
71
|
+
if not lock_attrs:
|
|
72
|
+
user_id = ctoken.get_user_id()
|
|
73
|
+
if user_id:
|
|
74
|
+
user_level_lock = True
|
|
75
|
+
lock_key = f"USER_ID_LOCK_{user_id}"
|
|
76
|
+
else:
|
|
77
|
+
raise ValueError("请设置 lock_attrs 或使用 token!")
|
|
78
|
+
|
|
79
|
+
if not user_level_lock:
|
|
80
|
+
if isinstance(lock_attrs, str): lock_attrs = [lock_attrs]
|
|
81
|
+
try:
|
|
82
|
+
for attr in lock_attrs:
|
|
83
|
+
parts = attr.split(".")
|
|
84
|
+
if len(parts) != 2:
|
|
85
|
+
raise ValueError(f"lock_attr: {attr} 格式错误")
|
|
86
|
+
obj = kwargs.get(parts[0]) or args[0]
|
|
87
|
+
if obj is None:
|
|
88
|
+
raise ValueError(f"参数 {parts[0]} 不存在")
|
|
89
|
+
lock_key += f"_{getattr(obj, parts[1], None)}"
|
|
90
|
+
except Exception as e:
|
|
91
|
+
raise ValueError(f"生成锁键失败: {e}")
|
|
92
|
+
|
|
93
|
+
lock_set = current_locks.get()
|
|
94
|
+
if lock_key in lock_set:
|
|
95
|
+
return func(*args, **kwargs)
|
|
96
|
+
token = current_locks.set(lock_set | {lock_key})
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
if not get_redis():
|
|
100
|
+
with try_lock(lock_key) as locked:
|
|
101
|
+
if not locked:
|
|
102
|
+
return R.error("操作过于频繁, 请稍后再试")
|
|
103
|
+
return func(*args, **kwargs)
|
|
104
|
+
else:
|
|
105
|
+
locked = add_lock(get_redis(), lock_key)
|
|
106
|
+
try:
|
|
107
|
+
if locked:
|
|
108
|
+
return func(*args, **kwargs)
|
|
109
|
+
else:
|
|
110
|
+
return R.error("操作过于频繁, 请稍后再试")
|
|
111
|
+
finally:
|
|
112
|
+
if locked:
|
|
113
|
+
remove_lock(get_redis(), lock_key)
|
|
114
|
+
finally:
|
|
115
|
+
current_locks.reset(token)
|
|
116
|
+
return wrapper
|
|
117
|
+
return decorator
|
|
118
|
+
|
|
@@ -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
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2025/5/16 16:42'
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
from dotenv.main import DotEnv
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Configuration:
|
|
14
|
+
"""Manages configuration and environment variables for the MCP client."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, dotenv_path: str = ".env") -> None:
|
|
17
|
+
"""Initialize configuration with environment variables."""
|
|
18
|
+
if not os.path.exists(dotenv_path): raise FileNotFoundError(f"Could not find .env file at {dotenv_path}")
|
|
19
|
+
self.env = DotEnv(dotenv_path=dotenv_path)
|
|
20
|
+
for key, value in self.env.dict().items():
|
|
21
|
+
if value: os.environ[key] = value
|
|
22
|
+
|
|
23
|
+
def get_env(self, key: str, default: Optional[Any] = None) -> Any:
|
|
24
|
+
value = self.env.get(key)
|
|
25
|
+
if value:
|
|
26
|
+
val = value.strip().lower()
|
|
27
|
+
if val == "true": return True
|
|
28
|
+
if val == "false": return False
|
|
29
|
+
return value
|
|
30
|
+
value = os.getenv(key)
|
|
31
|
+
if value:
|
|
32
|
+
val = value.strip().lower()
|
|
33
|
+
if val == "true": return True
|
|
34
|
+
if val == "false": return False
|
|
35
|
+
return value
|
|
36
|
+
return default
|
|
37
|
+
|
|
38
|
+
def get_llm_api_key(self) -> str:
|
|
39
|
+
api_key = self.get_env("LLM_API_KEY")
|
|
40
|
+
if not api_key: raise ValueError("LLM_API_KEY not found in environment variables")
|
|
41
|
+
return api_key
|
|
42
|
+
|
|
43
|
+
def get_mcp_server_config(self) -> dict[str, Any]:
|
|
44
|
+
with open(self.get_env("MCP_CONFIG_PATH"), "r") as f:
|
|
45
|
+
return json.load(f)
|
|
46
|
+
|
|
47
|
+
def bool_env(key, default):
|
|
48
|
+
value = os.getenv(key)
|
|
49
|
+
if value:
|
|
50
|
+
val = value.strip().lower()
|
|
51
|
+
if val == "true": return True
|
|
52
|
+
if val == "false": return False
|
|
53
|
+
return default
|
|
54
|
+
|
|
55
|
+
def float_env(key, default):
|
|
56
|
+
value = os.getenv(key)
|
|
57
|
+
if value: return float(value)
|
|
58
|
+
return default
|
|
59
|
+
|
|
60
|
+
def int_env(key, default):
|
|
61
|
+
value = os.getenv(key)
|
|
62
|
+
if value: return int(value)
|
|
63
|
+
return default
|
|
@@ -33,10 +33,3 @@ def table2list(html, include_header=True, recursive_find=True,
|
|
|
33
33
|
row = [i.text for i in td]
|
|
34
34
|
rows.append(row)
|
|
35
35
|
return rows
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
for e in table2list(open(r'C:\Users\hylink\Desktop\A-DESC.txt', encoding='utf-8'), table_attrs={'class_': '123'}):
|
|
39
|
-
print(e)
|
|
40
|
-
|
|
41
|
-
for e in table2list(open(r'C:\Users\hylink\Desktop\DEMO.txt', encoding='utf-8'), recursive_find=False, header_cell_tag='div', table_tag='div', table_class='table', row_tag='div', cell_tag='div'):
|
|
42
|
-
print(e)
|
|
@@ -4,19 +4,21 @@ 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, verify=False)
|
|
8
8
|
response.raise_for_status()
|
|
9
9
|
if response.status_code == 200:
|
|
10
10
|
result = response.content
|
|
11
11
|
except Exception as e:
|
|
12
12
|
print("GET请求异常:", e)
|
|
13
|
+
if isinstance(result, bytes): return result.decode('utf-8')
|
|
13
14
|
return result
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def post(url, data=None, json=None, headers=None, files=None):
|
|
17
18
|
result = ""
|
|
18
|
-
response = requests.post(url, data=data, json=json, files=files, headers=headers, timeout=
|
|
19
|
+
response = requests.post(url, data=data, json=json, files=files, headers=headers, timeout=600, verify=False)
|
|
19
20
|
response.raise_for_status()
|
|
20
21
|
if response.status_code == 200:
|
|
21
22
|
result = response.content
|
|
23
|
+
if isinstance(result, bytes): return result.decode('utf-8')
|
|
22
24
|
return result
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from io import BytesIO
|
|
1
3
|
|
|
2
4
|
from PIL import Image
|
|
3
|
-
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
def get_size(image_path):
|
|
6
8
|
return Image.open(image_path).size
|
|
7
9
|
|
|
10
|
+
|
|
8
11
|
def change_color(image_path, area=None, rgb_color=None):
|
|
9
12
|
"""
|
|
10
13
|
修改图片指定区域颜色
|
|
@@ -24,3 +27,9 @@ def change_color(image_path, area=None, rgb_color=None):
|
|
|
24
27
|
img_binary = img_bytes.getvalue()
|
|
25
28
|
return img_binary
|
|
26
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,54 @@
|
|
|
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=jqfx_exclude, exclude_word=()):
|
|
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
|
+
:param exclude_word: 排除的词
|
|
46
|
+
:return: 筛选之后的单词(数组)
|
|
47
|
+
"""
|
|
48
|
+
# linux 可以开放下面的语句, 并发执行分词, windows 不支持
|
|
49
|
+
# if platform.system() == 'Linux': jieba.enable_parallel(4)
|
|
50
|
+
ret = []
|
|
51
|
+
for w, flag in pseg.cut(word, use_paddle=use_paddle):
|
|
52
|
+
if (flag in word_flag or len(word_flag) == 0) and flag not in exclude_flag and w not in exclude_word:
|
|
53
|
+
ret.append((w, flag))
|
|
54
|
+
return ret
|
|
@@ -4,49 +4,43 @@ import time
|
|
|
4
4
|
WORKER_ID_BITS = 5
|
|
5
5
|
DATACENTER_ID_BITS = 5
|
|
6
6
|
SEQUENCE_BITS = 12
|
|
7
|
-
|
|
8
7
|
# 最大取值计算
|
|
9
8
|
MAX_WORKER_ID = -1 ^ (-1 << WORKER_ID_BITS) # 2**5-1 0b11111
|
|
10
9
|
MAX_DATACENTER_ID = -1 ^ (-1 << DATACENTER_ID_BITS)
|
|
11
|
-
|
|
12
10
|
# 移位偏移计算
|
|
13
|
-
|
|
11
|
+
WORKER_ID_SHIFT = SEQUENCE_BITS
|
|
14
12
|
DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS
|
|
15
13
|
TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS
|
|
16
|
-
|
|
17
14
|
# 序号循环掩码
|
|
18
15
|
SEQUENCE_MASK = -1 ^ (-1 << SEQUENCE_BITS)
|
|
19
|
-
|
|
20
16
|
# Twitter元年时间戳
|
|
21
17
|
TWEPOCH = 1288834974657
|
|
22
18
|
|
|
23
19
|
|
|
24
|
-
class
|
|
20
|
+
class SnowId(object):
|
|
25
21
|
"""
|
|
26
22
|
用于生成IDs
|
|
27
23
|
"""
|
|
28
24
|
|
|
29
|
-
def __init__(self, datacenter_id, worker_id, sequence=0):
|
|
25
|
+
def __init__(self, datacenter_id=0, worker_id=0, sequence=0):
|
|
30
26
|
"""
|
|
31
27
|
初始化
|
|
32
28
|
:param datacenter_id: 数据中心(机器区域)ID
|
|
33
29
|
:param worker_id: 机器ID
|
|
34
|
-
:param sequence:
|
|
30
|
+
:param sequence: 起始序号
|
|
35
31
|
"""
|
|
36
32
|
# sanity check
|
|
37
33
|
if worker_id > MAX_WORKER_ID or worker_id < 0:
|
|
38
34
|
raise ValueError('worker_id值越界')
|
|
39
|
-
|
|
40
35
|
if datacenter_id > MAX_DATACENTER_ID or datacenter_id < 0:
|
|
41
36
|
raise ValueError('datacenter_id值越界')
|
|
42
|
-
|
|
43
37
|
self.worker_id = worker_id
|
|
44
38
|
self.datacenter_id = datacenter_id
|
|
45
39
|
self.sequence = sequence
|
|
46
|
-
|
|
47
40
|
self.last_timestamp = -1 # 上次计算的时间戳
|
|
48
41
|
|
|
49
|
-
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _gen_timestamp():
|
|
50
44
|
"""
|
|
51
45
|
生成整数时间戳
|
|
52
46
|
:return:int timestamp
|
|
@@ -59,23 +53,18 @@ class IdWorker(object):
|
|
|
59
53
|
:return:
|
|
60
54
|
"""
|
|
61
55
|
timestamp = self._gen_timestamp()
|
|
62
|
-
|
|
63
56
|
# 时钟回拨
|
|
64
57
|
if timestamp < self.last_timestamp:
|
|
65
|
-
|
|
66
|
-
raise
|
|
67
|
-
|
|
58
|
+
timestamp = self._til_next_millis(self.last_timestamp)
|
|
68
59
|
if timestamp == self.last_timestamp:
|
|
69
60
|
self.sequence = (self.sequence + 1) & SEQUENCE_MASK
|
|
70
61
|
if self.sequence == 0:
|
|
71
62
|
timestamp = self._til_next_millis(self.last_timestamp)
|
|
72
63
|
else:
|
|
73
64
|
self.sequence = 0
|
|
74
|
-
|
|
75
65
|
self.last_timestamp = timestamp
|
|
76
|
-
|
|
77
66
|
new_id = ((timestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT) | (self.datacenter_id << DATACENTER_ID_SHIFT) | \
|
|
78
|
-
(self.worker_id <<
|
|
67
|
+
(self.worker_id << WORKER_ID_SHIFT) | self.sequence
|
|
79
68
|
return new_id
|
|
80
69
|
|
|
81
70
|
def _til_next_millis(self, last_timestamp):
|
|
@@ -86,7 +75,3 @@ class IdWorker(object):
|
|
|
86
75
|
while timestamp <= last_timestamp:
|
|
87
76
|
timestamp = self._gen_timestamp()
|
|
88
77
|
return timestamp
|
|
89
|
-
|
|
90
|
-
# if __name__ == '__main__':
|
|
91
|
-
# worker = IdWorker(1, 2, 0)
|
|
92
|
-
# print(worker.get_id())
|
ctools/web/__init__.py
ADDED