gomyck-tools 1.4.1__py3-none-any.whl → 1.4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) 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 +71 -22
  8. ctools/ai/tools/tool_use_xml_parse.py +2 -1
  9. ctools/ai/tools/xml_extract.py +3 -0
  10. ctools/application.py +21 -19
  11. ctools/aspect.py +65 -0
  12. ctools/auto/browser_element.py +11 -3
  13. ctools/auto/plan_area.py +2 -2
  14. ctools/auto/pty_process.py +0 -1
  15. ctools/auto/screenshot.py +3 -4
  16. ctools/auto/win_canvas.py +10 -4
  17. ctools/auto/win_control.py +8 -4
  18. ctools/call.py +32 -47
  19. ctools/cdate.py +43 -2
  20. ctools/cid.py +6 -4
  21. ctools/cipher/aes_util.py +2 -2
  22. ctools/cipher/b64.py +2 -0
  23. ctools/cipher/czip.py +3 -1
  24. ctools/cipher/rsa.py +6 -1
  25. ctools/cipher/sign.py +1 -0
  26. ctools/cipher/sm_util.py +3 -0
  27. ctools/cjson.py +5 -0
  28. ctools/cron_lite.py +10 -4
  29. ctools/database/database.py +52 -22
  30. ctools/dict_wrapper.py +1 -0
  31. ctools/ex.py +4 -0
  32. ctools/geo/coord_trans.py +94 -94
  33. ctools/geo/douglas_rarefy.py +13 -9
  34. ctools/metrics.py +6 -0
  35. ctools/office/cword.py +7 -7
  36. ctools/office/word_fill.py +1 -4
  37. ctools/patch.py +88 -0
  38. ctools/path_info.py +29 -0
  39. ctools/pkg/__init__.py +4 -0
  40. ctools/pkg/dynamic_imp.py +38 -0
  41. ctools/pools/process_pool.py +6 -1
  42. ctools/pools/thread_pool.py +6 -2
  43. ctools/similar.py +3 -0
  44. ctools/stream/ckafka.py +11 -5
  45. ctools/stream/credis.py +37 -23
  46. ctools/stream/mqtt_utils.py +2 -2
  47. ctools/sys_info.py +8 -0
  48. ctools/sys_log.py +4 -1
  49. ctools/util/cftp.py +4 -2
  50. ctools/util/cklock.py +118 -0
  51. ctools/util/config_util.py +52 -0
  52. ctools/util/http_util.py +1 -0
  53. ctools/util/image_process.py +8 -0
  54. ctools/util/jb_cut.py +53 -0
  55. ctools/util/snow_id.py +3 -2
  56. ctools/web/__init__.py +2 -2
  57. ctools/web/aio_web_server.py +19 -9
  58. ctools/web/api_result.py +3 -2
  59. ctools/web/bottle_web_base.py +134 -70
  60. ctools/web/bottle_webserver.py +41 -35
  61. ctools/web/bottle_websocket.py +4 -0
  62. ctools/web/ctoken.py +81 -13
  63. ctools/web/download_util.py +1 -1
  64. ctools/web/params_util.py +4 -0
  65. ctools/web/upload_util.py +1 -1
  66. {gomyck_tools-1.4.1.dist-info → gomyck_tools-1.4.3.dist-info}/METADATA +9 -11
  67. gomyck_tools-1.4.3.dist-info/RECORD +88 -0
  68. ctools/auto/pacth.py +0 -74
  69. gomyck_tools-1.4.1.dist-info/RECORD +0 -82
  70. {gomyck_tools-1.4.1.dist-info → gomyck_tools-1.4.3.dist-info}/WHEEL +0 -0
  71. {gomyck_tools-1.4.1.dist-info → gomyck_tools-1.4.3.dist-info}/licenses/LICENSE +0 -0
  72. {gomyck_tools-1.4.1.dist-info → gomyck_tools-1.4.3.dist-info}/top_level.txt +0 -0
ctools/office/cword.py CHANGED
@@ -12,13 +12,14 @@
12
12
 
13
13
  from docx import Document
14
14
 
15
+
15
16
  def merge_word_files(input_files: [], output_file: str):
16
- merged_doc = Document()
17
- for file in input_files:
18
- doc = Document(file)
19
- for element in doc.element.body:
20
- merged_doc.element.body.append(element)
21
- merged_doc.save(output_file)
17
+ merged_doc = Document()
18
+ for file in input_files:
19
+ doc = Document(file)
20
+ for element in doc.element.body:
21
+ merged_doc.element.body.append(element)
22
+ merged_doc.save(output_file)
22
23
 
23
24
 
24
25
  def read_word_file(input_file: str):
@@ -27,4 +28,3 @@ def read_word_file(input_file: str):
27
28
  for paragraph in doc.paragraphs:
28
29
  text.append(paragraph.text)
29
30
  return "\n".join(text)
30
-
@@ -469,6 +469,7 @@ def check_format(input_path: str):
469
469
  res = False
470
470
  return res
471
471
 
472
+
472
473
  def excel_optimize(input_path: str):
473
474
  """
474
475
  优化excel内容清除无法使用的隐患
@@ -492,7 +493,6 @@ def excel_optimize(input_path: str):
492
493
  except Exception as e:
493
494
  print("优化模板内容清除无法使用的隐患异常: %s" % e)
494
495
 
495
-
496
496
  # 示例用法:从第1行到第5行、从第1列到第3列的区域复制到Word文档中
497
497
  # excel_file = r'C:\Users\DELL\xxx/xxx-rpa/document\test-2024-04-01_09-05-26.xlsx' # Excel文件名
498
498
  # excel_file_1 = r'E:\test\c.xlsx' # Excel文件名
@@ -557,6 +557,3 @@ def excel_optimize(input_path: str):
557
557
  # print(check_format(input_path))
558
558
 
559
559
  # from docxtpl import DocxTemplate, RichText
560
-
561
-
562
-
ctools/patch.py ADDED
@@ -0,0 +1,88 @@
1
+ import os
2
+
3
+ from sqlalchemy.sql import text
4
+
5
+ from ctools import path_info
6
+ from ctools.database import database
7
+
8
+ """
9
+ from ctools import patch
10
+ def xx():
11
+ print('hello world')
12
+ def xx1():
13
+ print('hello world1')
14
+ def xx2():
15
+ print('hello world2')
16
+ patch_funcs = {
17
+ 'V1.0.2': xx,
18
+ 'V1.0.3': xx1,
19
+ 'V1.1.4': xx2
20
+ }
21
+ patch.sync_version("kwc", "V1.1.5", patch_funcs)
22
+ """
23
+
24
+ class Patch:
25
+
26
+ def __init__(self, oldVersion, newVersion, patch_func: dict) -> None:
27
+ super().__init__()
28
+ if oldVersion:
29
+ self.oldV = version_to_int(oldVersion)
30
+ else:
31
+ self.oldV = 0
32
+ self.currentV = version_to_int(newVersion)
33
+ self.snapshot = '-snapshot' in newVersion or (oldVersion is not None and '-snapshot' in oldVersion)
34
+ self.patch_func = patch_func
35
+
36
+ def apply_patch(self):
37
+ patch_methods = [method for method in self.patch_func.keys() if method and (method.startswith('V') or method.startswith('v'))]
38
+ patch_methods.sort(key=lambda x: version_to_int(x))
39
+ max_method_name = patch_methods[-1]
40
+ exec_max_method = False
41
+ for method_name in patch_methods:
42
+ slVersion = version_to_int(method_name)
43
+ if self.currentV > slVersion >= self.oldV:
44
+ if max_method_name == method_name: exec_max_method = True
45
+ method = self.patch_func[method_name]
46
+ print('start exec patch {}'.format(method_name))
47
+ method()
48
+ print('patch {} update success'.format(method_name))
49
+ if self.snapshot and not exec_max_method:
50
+ print('start exec snapshot patch {}'.format(max_method_name))
51
+ method = self.patch_func[max_method_name]
52
+ method()
53
+ print('snapshot patch {} update success'.format(max_method_name))
54
+
55
+ def version_to_int(version):
56
+ return int(version.replace('V', '').replace('v', '').replace('.', '').replace('-snapshot', ''))
57
+
58
+ def run_sqls(sqls):
59
+ with database.get_session() as s:
60
+ for sql in sqls.split(";"):
61
+ try:
62
+ s.execute(text(sql.strip()))
63
+ s.commit()
64
+ except Exception as e:
65
+ print('结构升级错误, 请检查!!! {}'.format(e.__cause__))
66
+
67
+ def sync_version(app_name, new_version, patch_func: dict):
68
+ destFilePath = os.path.join(path_info.get_user_work_path(".ck/{}".format(app_name), mkdir=True), "version")
69
+ if not os.path.exists(destFilePath):
70
+ patch = Patch(oldVersion=None, newVersion=new_version, patch_func=patch_func)
71
+ patch.apply_patch()
72
+ with open(destFilePath, 'w') as nv:
73
+ nv.write(new_version)
74
+ print('初始化安装, 版本信息为: {}'.format(new_version))
75
+ nv.flush()
76
+ else:
77
+ with open(destFilePath, 'r') as oldVersion:
78
+ oldV = oldVersion.readline()
79
+ print('本地版本信息为: {}, 程序版本信息为: {}'.format(oldV, new_version))
80
+ oldVersion.close()
81
+ if oldV >= new_version and '-snapshot' not in oldV: return
82
+ print('开始升级本地程序..')
83
+ patch = Patch(oldVersion=oldV, newVersion=new_version, patch_func=patch_func)
84
+ patch.apply_patch()
85
+ with open(destFilePath, 'w') as newVersion:
86
+ newVersion.write(new_version)
87
+ print('程序升级成功, 更新版本信息为: {}'.format(new_version))
88
+ newVersion.flush()
ctools/path_info.py CHANGED
@@ -1,6 +1,9 @@
1
1
  import inspect
2
2
  import os
3
3
  import sys
4
+ import importlib
5
+ from types import ModuleType
6
+ from pathlib import Path
4
7
 
5
8
 
6
9
  def get_current_path(subPath: str = '') -> str:
@@ -67,3 +70,29 @@ def get_install_path(subPath: str = '') -> str:
67
70
  :return: 安装包安装的路径
68
71
  """
69
72
  return os.path.join(os.getcwd(), subPath)
73
+
74
+ def get_package_path(package: str | ModuleType) -> Path:
75
+ """
76
+ 获取包所在目录
77
+ :param package: 包名字符串或已导入模块
78
+ :return: 包目录的 Path 对象
79
+ """
80
+ if isinstance(package, str):
81
+ module = importlib.import_module(package)
82
+ elif isinstance(package, ModuleType):
83
+ module = package
84
+ else:
85
+ raise TypeError("package 必须是字符串或模块对象")
86
+
87
+ return Path(module.__file__).resolve().parent
88
+
89
+
90
+ def get_resource_path(package: str | ModuleType, *subpaths: str) -> Path:
91
+ """
92
+ 获取包内资源文件路径(自动拼接子路径)
93
+ :param package: 包名或模块
94
+ :param subpaths: 可变参数,表示路径中的子目录或文件名
95
+ :return: 资源的绝对路径
96
+ """
97
+ base = get_package_path(package)
98
+ return base.joinpath(*subpaths)
ctools/pkg/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2025/7/15 11:02'
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2025/7/15 11:03'
5
+
6
+ import importlib
7
+ import pkgutil
8
+
9
+
10
+ def load_modules_from_package(package, exclude=None, recursive=True):
11
+ """
12
+ 递归加载指定包下所有模块(不包括包本身)
13
+
14
+ :param package: 要加载模块的包对象(如 mypkg.plugins)
15
+ :param exclude: 排除的模块完整路径列表(如 ['mypkg.plugins.demo.mod2'])
16
+ :param recursive: 是否递归子包
17
+ :return: 模块列表(不含子包本身,只包含模块)
18
+ """
19
+ if exclude is None: exclude = []
20
+ modules = []
21
+ for finder, modname, ispkg in pkgutil.iter_modules(package.__path__):
22
+ full_modname = f"{package.__name__}.{modname}"
23
+ if ispkg and recursive:
24
+ try:
25
+ subpkg = importlib.import_module(full_modname)
26
+ modules.extend(load_modules_from_package(subpkg, exclude, recursive))
27
+ except Exception as e:
28
+ print(f"递归子包 {full_modname} 失败:{e}")
29
+ continue
30
+ if full_modname in exclude:
31
+ continue
32
+ try:
33
+ module = importlib.import_module(full_modname)
34
+ modules.append(module)
35
+ except Exception as e:
36
+ print(f"!!!!!!加载模块 {full_modname} 失败:{e}!!!!!!")
37
+ continue
38
+ return modules
@@ -2,6 +2,7 @@
2
2
  # -*- coding: UTF-8 -*-
3
3
  __author__ = 'haoyang'
4
4
  __date__ = '2024/9/20 12:00'
5
+
5
6
  import os
6
7
  import time
7
8
  from concurrent.futures import ProcessPoolExecutor
@@ -10,12 +11,14 @@ from ctools import call
10
11
 
11
12
  _process_pool: ProcessPoolExecutor = None
12
13
 
14
+
13
15
  @call.init
14
16
  def init():
15
17
  global _process_pool
16
- max_workers = min(32, os.cpu_count()) # 最多 32 个
18
+ max_workers = min(32, os.cpu_count()) # 最多 32 个
17
19
  _process_pool = ProcessPoolExecutor(max_workers=max_workers)
18
20
 
21
+
19
22
  def cb(f, callback):
20
23
  exc = f.exception()
21
24
  if exc:
@@ -24,6 +27,7 @@ def cb(f, callback):
24
27
  else:
25
28
  if callback: callback(f.result())
26
29
 
30
+
27
31
  def submit(func, *args, callback=None, **kwargs):
28
32
  if _process_pool is None: raise Exception('process pool is not init')
29
33
  future = _process_pool.submit(func, *args, **kwargs)
@@ -31,6 +35,7 @@ def submit(func, *args, callback=None, **kwargs):
31
35
  time.sleep(0.01)
32
36
  return future
33
37
 
38
+
34
39
  def shutdown(wait=True):
35
40
  if _process_pool is None: raise Exception('process pool is not init')
36
41
  _process_pool.shutdown(wait=wait)
@@ -9,11 +9,13 @@ thread_local = threading.local()
9
9
 
10
10
  _threadPool: ThreadPoolExecutor = None
11
11
 
12
+
12
13
  @call.init
13
14
  def init():
14
15
  global _threadPool
15
- max_work_num = min(32, (os.cpu_count() or 1) + 4) # 最多 32 个
16
- _threadPool = ThreadPoolExecutor(max_workers= max_work_num, thread_name_prefix='ck-')
16
+ max_work_num = min(32, (os.cpu_count() or 1) + 4) # 最多 32 个
17
+ _threadPool = ThreadPoolExecutor(max_workers=max_work_num, thread_name_prefix='ck-')
18
+
17
19
 
18
20
  def cb(f, callback):
19
21
  exc = f.exception()
@@ -23,6 +25,7 @@ def cb(f, callback):
23
25
  else:
24
26
  if callback: callback(f.result())
25
27
 
28
+
26
29
  def submit(func, *args, callback=None, **kwargs):
27
30
  if _threadPool is None: raise Exception('thread pool is not init')
28
31
  future = _threadPool.submit(func, *args, **kwargs)
@@ -30,6 +33,7 @@ def submit(func, *args, callback=None, **kwargs):
30
33
  time.sleep(0.01)
31
34
  return future
32
35
 
36
+
33
37
  def shutdown(wait=True):
34
38
  if _threadPool is None: raise Exception('thread pool is not init')
35
39
  _threadPool.shutdown(wait=wait)
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,53 +6,64 @@ __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
+ # 最后一次连接的redis
13
+ _ck_redis: Redis = None
14
+
15
+ def get_redis(): return _ck_redis
12
16
 
13
17
  def init_pool(host: str = 'localhost', port: int = 6379, db: int = 0, password: str = None,
14
18
  username: str = None, decode_responses: bool = True, max_connections: int = 75,
15
19
  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...")
20
+ for attempt in range(retry_count):
21
+ try:
22
+ r: Redis = redis.StrictRedis(
23
+ host=host, port=port, db=db,
24
+ username=username, password=password,
25
+ retry_on_timeout=True,
26
+ max_connections=max_connections,
27
+ decode_responses=decode_responses,
28
+ health_check_interval=health_check_interval,
29
+ socket_connect_timeout=5,
30
+ socket_timeout=5
31
+ )
32
+ if r.ping():
33
+ print('CRedis connect {} {} success!'.format(host, port))
34
+ global _ck_redis
35
+ _ck_redis = r
36
+ return _ck_redis
37
+ except redis.ConnectionError as e:
38
+ if attempt == retry_count - 1:
39
+ raise Exception(f"Failed to connect to Redis after {retry_count} attempts: {str(e)}")
40
+ print(f"Connection attempt {attempt + 1} failed, retrying...")
41
+
35
42
 
36
43
  def add_lock(r: Redis, key: str, timeout: int = 30):
37
44
  if r.exists(key):
38
45
  expire_time = r.get(key)
39
- if cdate.time_diff_in_seconds(expire_time, cdate.get_date_time()) > 0:
40
- return True
46
+ if expire_time and cdate.time_diff_in_seconds(expire_time, cdate.get_date_time()) > 0:
47
+ return False
41
48
  else:
42
49
  r.delete(key)
43
50
  return r.set(key, cdate.opt_time(seconds=timeout), nx=True, ex=timeout) is not None
44
51
 
52
+
45
53
  def remove_lock(r: Redis, key: str):
46
54
  r.delete(key)
47
55
 
56
+
48
57
  def subscribe(r: Redis, channel_name, callback):
49
58
  def thread_func():
50
59
  pubsub = r.pubsub()
51
60
  pubsub.subscribe(channel_name)
52
61
  for message in pubsub.listen():
53
62
  callback(message)
63
+
54
64
  thread_pool.submit(thread_func)
55
65
 
66
+
56
67
  def _process_pending_messages(r: Redis, stream_name: str, group_name: str, consumer_name: str, callback):
57
68
  """
58
69
  处理未确认的消息
@@ -84,7 +95,8 @@ def _process_pending_messages(r: Redis, stream_name: str, group_name: str, consu
84
95
  else:
85
96
  print("No pending messages found.")
86
97
 
87
- def stream_subscribe(r: Redis, stream_name, group_name, callback, from_id: str='$', noack: bool = False):
98
+
99
+ def stream_subscribe(r: Redis, stream_name, group_name, callback, from_id: str = '$', noack: bool = False):
88
100
  def thread_func():
89
101
  try:
90
102
  # $表示从最后面消费, 0表示从开始消费
@@ -107,7 +119,9 @@ def stream_subscribe(r: Redis, stream_name, group_name, callback, from_id: str='
107
119
  if res: r.xack(stream_name, group_name, message_id)
108
120
  except Exception as e:
109
121
  print('stream_subscribe error: ', e)
122
+
110
123
  thread_pool.submit(thread_func)
111
124
 
125
+
112
126
  def stream_publish(r: Redis, stream_name, message):
113
127
  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,14 +81,16 @@ 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
86
- flog = _file_log(sys_log_path='{}/ck-py-log/'.format(path_info.get_user_work_path()), mixin=True, log_level=logging.DEBUG)
88
+ flog = _file_log(path_info.get_user_work_path(".ck/ck-py-log", mkdir=True), mixin=True, log_level=logging.DEBUG)
87
89
  clog = _console_log()
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):