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.
Files changed (107) hide show
  1. ctools/__init__.py +21 -0
  2. ctools/ai/__init__.py +4 -0
  3. ctools/ai/llm_chat.py +184 -0
  4. ctools/ai/llm_client.py +163 -0
  5. ctools/ai/llm_exception.py +17 -0
  6. ctools/ai/mcp/__init__.py +4 -0
  7. ctools/ai/mcp/mcp_client.py +326 -0
  8. ctools/ai/tools/__init__.py +4 -0
  9. ctools/ai/tools/json_extract.py +202 -0
  10. ctools/ai/tools/quick_tools.py +149 -0
  11. ctools/ai/tools/think_process.py +11 -0
  12. ctools/ai/tools/tool_use_xml_parse.py +40 -0
  13. ctools/ai/tools/xml_extract.py +15 -0
  14. ctools/application.py +50 -47
  15. ctools/aspect.py +65 -0
  16. ctools/auto/__init__.py +4 -0
  17. ctools/{browser_element_tools.py → auto/browser_element.py} +18 -8
  18. ctools/{plan_area_tools.py → auto/plan_area.py} +5 -7
  19. ctools/{pty_tools.py → auto/pty_process.py} +6 -3
  20. ctools/{resource_bundle_tools.py → auto/resource_bundle.py} +4 -4
  21. ctools/{screenshot_tools.py → auto/screenshot.py} +7 -6
  22. ctools/{win_canvas.py → auto/win_canvas.py} +10 -4
  23. ctools/{win_control.py → auto/win_control.py} +14 -5
  24. ctools/call.py +34 -49
  25. ctools/cdate.py +84 -0
  26. ctools/cdebug.py +146 -0
  27. ctools/cid.py +20 -0
  28. ctools/cipher/__init__.py +4 -0
  29. ctools/{aes_tools.py → cipher/aes_util.py} +10 -0
  30. ctools/{b64.py → cipher/b64.py} +2 -0
  31. ctools/cipher/czip.py +133 -0
  32. ctools/cipher/rsa.py +75 -0
  33. ctools/{sign.py → cipher/sign.py} +2 -1
  34. ctools/{sm_tools.py → cipher/sm_util.py} +20 -4
  35. ctools/cjson.py +10 -10
  36. ctools/cron_lite.py +109 -97
  37. ctools/database/__init__.py +4 -0
  38. ctools/{database.py → database/database.py} +93 -26
  39. ctools/dict_wrapper.py +21 -0
  40. ctools/ex.py +4 -0
  41. ctools/geo/__init__.py +4 -0
  42. ctools/geo/coord_trans.py +127 -0
  43. ctools/geo/douglas_rarefy.py +139 -0
  44. ctools/metrics.py +56 -63
  45. ctools/office/__init__.py +4 -0
  46. ctools/office/cword.py +30 -0
  47. ctools/{word_fill.py → office/word_fill.py} +3 -6
  48. ctools/patch.py +88 -0
  49. ctools/{work_path.py → path_info.py} +34 -2
  50. ctools/pkg/__init__.py +4 -0
  51. ctools/pkg/dynamic_imp.py +38 -0
  52. ctools/pools/__init__.py +4 -0
  53. ctools/pools/process_pool.py +41 -0
  54. ctools/{thread_pool.py → pools/thread_pool.py} +13 -4
  55. ctools/similar.py +25 -0
  56. ctools/stream/__init__.py +4 -0
  57. ctools/stream/ckafka.py +164 -0
  58. ctools/stream/credis.py +127 -0
  59. ctools/{mqtt_utils.py → stream/mqtt_utils.py} +20 -12
  60. ctools/sys_info.py +36 -13
  61. ctools/sys_log.py +46 -27
  62. ctools/util/__init__.py +4 -0
  63. ctools/util/cftp.py +76 -0
  64. ctools/util/cklock.py +118 -0
  65. ctools/util/config_util.py +52 -0
  66. ctools/util/env_config.py +63 -0
  67. ctools/{html_soup.py → util/html_soup.py} +0 -7
  68. ctools/{http_utils.py → util/http_util.py} +4 -2
  69. ctools/{images_tools.py → util/image_process.py} +10 -1
  70. ctools/util/jb_cut.py +54 -0
  71. ctools/{id_worker_tools.py → util/snow_id.py} +8 -23
  72. ctools/web/__init__.py +4 -0
  73. ctools/web/aio_web_server.py +186 -0
  74. ctools/web/api_result.py +56 -0
  75. ctools/web/bottle_web_base.py +239 -0
  76. ctools/web/bottle_webserver.py +191 -0
  77. ctools/web/bottle_websocket.py +79 -0
  78. ctools/web/ctoken.py +103 -0
  79. ctools/{download_tools.py → web/download_util.py} +14 -12
  80. ctools/web/params_util.py +46 -0
  81. ctools/{upload_tools.py → web/upload_util.py} +3 -2
  82. gomyck_tools-1.4.7.dist-info/METADATA +70 -0
  83. gomyck_tools-1.4.7.dist-info/RECORD +88 -0
  84. {gomyck_tools-1.0.0.dist-info → gomyck_tools-1.4.7.dist-info}/WHEEL +1 -1
  85. gomyck_tools-1.4.7.dist-info/licenses/LICENSE +13 -0
  86. ctools/bashPath.py +0 -13
  87. ctools/bottle_server.py +0 -49
  88. ctools/console.py +0 -55
  89. ctools/date_utils.py +0 -44
  90. ctools/enums.py +0 -4
  91. ctools/excelOpt.py +0 -36
  92. ctools/imgDialog.py +0 -44
  93. ctools/license.py +0 -37
  94. ctools/log.py +0 -28
  95. ctools/mvc.py +0 -56
  96. ctools/obj.py +0 -20
  97. ctools/pacth.py +0 -73
  98. ctools/ssh.py +0 -9
  99. ctools/strDiff.py +0 -20
  100. ctools/string_tools.py +0 -101
  101. ctools/token_tools.py +0 -13
  102. ctools/wordFill.py +0 -24
  103. gomyck_tools-1.0.0.dist-info/METADATA +0 -20
  104. gomyck_tools-1.0.0.dist-info/RECORD +0 -54
  105. /ctools/{word_fill_entity.py → office/word_fill_entity.py} +0 -0
  106. /ctools/{compile_tools.py → util/compile_util.py} +0 -0
  107. {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 application, call
6
+ from ctools import call, path_info
7
7
 
8
- application.sync_config()
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-%M-%S", time.localtime(time.time())) + ".log"
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-%(levelname)s-%(thread)d-%(module)s(%(funcName)s:%(lineno)d) %(message)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-%(levelname)s-%(thread)d-%(module)s(%(funcName)s:%(lineno)d) %(message)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
- class GlobalLogger(object):
56
- def __init__(self, logger):
57
- sys.stdout = self
58
- sys.stderr = self
59
- self.log = logger
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 == '\n': return
63
- global neglect_keywords
64
- for neglect_keyword in neglect_keywords:
65
- if neglect_keyword in message: return
66
- self.log.info(message)
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
- pass
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(sys_log_path=application.Server.sysLogPath, mixin=True, log_level=logging.INFO if application.Authorization.enabled else logging.DEBUG)
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
- GlobalLogger(flog)
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)
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2025/6/11 09:29'
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=5)
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=5)
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
- from io import BytesIO
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
- WOKER_ID_SHIFT = SEQUENCE_BITS
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 IdWorker(object):
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
- def _gen_timestamp(self):
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
- print('clock is moving backwards. Rejecting requests until {}'.format(self.last_timestamp))
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 << WOKER_ID_SHIFT) | self.sequence
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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2025/6/11 09:12'