gomyck-tools 1.4.0__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.
- ctools/__init__.py +21 -0
- ctools/ai/env_config.py +20 -1
- ctools/ai/llm_chat.py +8 -8
- ctools/ai/llm_client.py +37 -28
- ctools/ai/mcp/mcp_client.py +33 -16
- ctools/ai/tools/json_extract.py +3 -2
- ctools/ai/tools/quick_tools.py +68 -21
- ctools/ai/tools/tool_use_xml_parse.py +2 -1
- ctools/ai/tools/xml_extract.py +3 -0
- ctools/application.py +19 -17
- ctools/auto/browser_element.py +11 -3
- ctools/auto/plan_area.py +2 -2
- ctools/auto/pty_process.py +0 -1
- ctools/auto/screenshot.py +3 -4
- ctools/auto/win_canvas.py +10 -4
- ctools/auto/win_control.py +8 -4
- ctools/call.py +18 -22
- ctools/cdate.py +43 -2
- ctools/cid.py +3 -4
- ctools/cipher/aes_util.py +2 -2
- ctools/cipher/b64.py +2 -0
- ctools/cipher/czip.py +3 -1
- ctools/cipher/rsa.py +6 -1
- ctools/cipher/sign.py +1 -0
- ctools/cipher/sm_util.py +3 -0
- ctools/cjson.py +5 -0
- ctools/cron_lite.py +10 -4
- ctools/database/database.py +37 -17
- ctools/dict_wrapper.py +1 -0
- ctools/ex.py +1 -0
- ctools/geo/coord_trans.py +94 -94
- ctools/geo/douglas_rarefy.py +13 -9
- ctools/metrics.py +6 -0
- ctools/office/cword.py +7 -7
- ctools/office/word_fill.py +1 -4
- ctools/path_info.py +29 -0
- ctools/pools/process_pool.py +6 -1
- ctools/pools/thread_pool.py +6 -2
- ctools/similar.py +3 -0
- ctools/stream/ckafka.py +11 -5
- ctools/stream/credis.py +29 -21
- ctools/stream/mqtt_utils.py +2 -2
- ctools/sys_info.py +8 -0
- ctools/sys_log.py +3 -0
- ctools/util/cftp.py +4 -2
- ctools/util/http_util.py +1 -0
- ctools/util/image_process.py +1 -1
- ctools/util/snow_id.py +3 -2
- ctools/web/__init__.py +2 -2
- ctools/web/aio_web_server.py +19 -9
- ctools/web/api_result.py +3 -2
- ctools/web/bottle_web_base.py +15 -2
- ctools/web/bottle_webserver.py +14 -8
- ctools/web/bottle_websocket.py +4 -0
- ctools/web/ctoken.py +5 -1
- ctools/web/download_util.py +1 -1
- ctools/web/params_util.py +4 -0
- ctools/web/upload_util.py +1 -1
- {gomyck_tools-1.4.0.dist-info → gomyck_tools-1.4.2.dist-info}/METADATA +2 -1
- gomyck_tools-1.4.2.dist-info/RECORD +82 -0
- gomyck_tools-1.4.0.dist-info/RECORD +0 -82
- {gomyck_tools-1.4.0.dist-info → gomyck_tools-1.4.2.dist-info}/WHEEL +0 -0
- {gomyck_tools-1.4.0.dist-info → gomyck_tools-1.4.2.dist-info}/licenses/LICENSE +0 -0
- {gomyck_tools-1.4.0.dist-info → gomyck_tools-1.4.2.dist-info}/top_level.txt +0 -0
ctools/database/database.py
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
import contextlib
|
2
2
|
import datetime
|
3
|
-
|
4
3
|
import math
|
4
|
+
|
5
5
|
from sqlalchemy import create_engine, Integer, Column, event
|
6
6
|
from sqlalchemy.ext.declarative import declarative_base
|
7
7
|
from sqlalchemy.orm import sessionmaker, Session
|
8
8
|
from sqlalchemy.sql import text
|
9
9
|
|
10
10
|
from ctools import call
|
11
|
-
from ctools.pools.thread_pool import thread_local
|
12
11
|
from ctools import cid
|
12
|
+
from ctools.pools.thread_pool import thread_local
|
13
|
+
from ctools.web.bottle_web_base import PageInfo
|
13
14
|
|
14
15
|
"""
|
15
16
|
class XXXX(BaseMixin):
|
@@ -30,14 +31,17 @@ inited_db = {}
|
|
30
31
|
engines = {}
|
31
32
|
sessionMakers = {}
|
32
33
|
|
33
|
-
|
34
|
+
|
35
|
+
def getEngine(db_key: str = 'default'):
|
34
36
|
return engines[db_key]
|
35
37
|
|
38
|
+
|
36
39
|
@call.init
|
37
40
|
def _init():
|
38
41
|
global Base
|
39
42
|
Base = declarative_base()
|
40
43
|
|
44
|
+
|
41
45
|
"""
|
42
46
|
The string form of the URL is
|
43
47
|
dialect[+driver]://user:password@host/dbname[?key=value..]
|
@@ -45,6 +49,7 @@ where ``dialect`` is a database name such as ``mysql``, ``oracle``, ``postgresql
|
|
45
49
|
and ``driver`` the name of a DBAPI such as ``psycopg2``, ``pyodbc``, ``cx_oracle``, etc. Alternatively
|
46
50
|
"""
|
47
51
|
|
52
|
+
|
48
53
|
# 密码里的@ 要替换成 %40
|
49
54
|
|
50
55
|
# sqlite connect_args={"check_same_thread": False} db_url=sqlite:///{}.format(db_url)
|
@@ -53,7 +58,7 @@ and ``driver`` the name of a DBAPI such as ``psycopg2``, ``pyodbc``, ``cx_oracle
|
|
53
58
|
# > PRAGMA journal_mode=WAL; 设置事务的模式, wal 允许读写并发, 但是会额外创建俩文件
|
54
59
|
# > PRAGMA synchronous=NORMAL; 设置写盘策略, 默认是 FULL, 日志,数据都落, 设置成 NORMAL, 日志写完就算事务完成
|
55
60
|
|
56
|
-
def init_db(db_url: str, db_key: str='default', connect_args: dict={}, default_schema: str=None, pool_size: int=5, max_overflow: int=25, echo: bool=False):
|
61
|
+
def init_db(db_url: str, db_key: str = 'default', connect_args: dict = {}, default_schema: str = None, pool_size: int = 5, max_overflow: int = 25, echo: bool = False, auto_gen_table: bool = True):
|
57
62
|
if db_url.startswith('mysql'):
|
58
63
|
import pymysql
|
59
64
|
pymysql.install_as_MySQLdb()
|
@@ -64,27 +69,31 @@ def init_db(db_url: str, db_key: str='default', connect_args: dict={}, default_s
|
|
64
69
|
sessionMakers[db_key] = sessionMaker
|
65
70
|
inited_db[db_key] = True
|
66
71
|
if default_schema: event.listen(engine, 'connect', lambda dbapi_connection, connection_record: _set_search_path(dbapi_connection, default_schema))
|
67
|
-
Base.metadata.create_all(engine)
|
72
|
+
if auto_gen_table: Base.metadata.create_all(engine)
|
73
|
+
|
68
74
|
|
69
75
|
def _set_search_path(dbapi_connection, default_schema):
|
70
76
|
with dbapi_connection.cursor() as cursor:
|
71
77
|
cursor.execute(f'SET search_path TO {default_schema}')
|
72
78
|
|
73
|
-
|
79
|
+
|
80
|
+
def _create_connection(db_url: str, pool_size: int = 5, max_overflow: int = 25, connect_args={}, echo: bool = False):
|
74
81
|
engine = create_engine('{}'.format(db_url),
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
+
echo=echo,
|
83
|
+
future=True,
|
84
|
+
pool_size=pool_size,
|
85
|
+
max_overflow=max_overflow,
|
86
|
+
pool_pre_ping=True,
|
87
|
+
pool_recycle=3600,
|
88
|
+
connect_args=connect_args)
|
82
89
|
sm = sessionmaker(bind=engine)
|
83
90
|
return engine, sm
|
84
91
|
|
92
|
+
|
85
93
|
def generate_custom_id():
|
86
94
|
return str(cid.get_snowflake_id())
|
87
95
|
|
96
|
+
|
88
97
|
class BaseMixin(Base):
|
89
98
|
__abstract__ = True
|
90
99
|
obj_id = Column(Integer, primary_key=True, default=generate_custom_id)
|
@@ -112,10 +121,16 @@ class BaseMixin(Base):
|
|
112
121
|
ret_state[key] = state[key]
|
113
122
|
return ret_state
|
114
123
|
|
124
|
+
@classmethod
|
125
|
+
def init(cls, data: dict):
|
126
|
+
valid_keys = cls.__table__.columns.keys()
|
127
|
+
filtered = {k: v for k, v in data.items() if k in valid_keys}
|
128
|
+
return cls(**filtered)
|
129
|
+
|
115
130
|
@contextlib.contextmanager
|
116
|
-
def get_session(db_key: str='default') -> Session:
|
131
|
+
def get_session(db_key: str = 'default') -> Session:
|
117
132
|
thread_local.db_key = db_key
|
118
|
-
if sm:=sessionMakers.get(db_key):
|
133
|
+
if sm := sessionMakers.get(db_key):
|
119
134
|
s = sm()
|
120
135
|
else:
|
121
136
|
raise ValueError("Invalid db_key: {}".format(db_key))
|
@@ -127,15 +142,17 @@ def get_session(db_key: str='default') -> Session:
|
|
127
142
|
finally:
|
128
143
|
s.close()
|
129
144
|
|
145
|
+
|
130
146
|
class PageInfoBuilder:
|
131
147
|
|
132
|
-
def __init__(self, pageInfo, total_count, records):
|
148
|
+
def __init__(self, pageInfo: PageInfo, total_count, records):
|
133
149
|
self.page_size = pageInfo.page_size
|
134
150
|
self.page_index = pageInfo.page_index
|
135
151
|
self.total_count = total_count
|
136
152
|
self.total_page = math.ceil(total_count / int(pageInfo.page_size))
|
137
153
|
self.records = records
|
138
154
|
|
155
|
+
|
139
156
|
def query_by_page(query, pageInfo):
|
140
157
|
records = query.offset((pageInfo.page_index - 1) * pageInfo.page_size).limit(pageInfo.page_size).all()
|
141
158
|
rs = []
|
@@ -143,6 +160,7 @@ def query_by_page(query, pageInfo):
|
|
143
160
|
rs.append(r)
|
144
161
|
return PageInfoBuilder(pageInfo, query.count(), rs)
|
145
162
|
|
163
|
+
|
146
164
|
def query4_crd_sql(session, sql: str, params: dict) -> []:
|
147
165
|
records = session.execute(text(sql), params).fetchall()
|
148
166
|
rs = []
|
@@ -153,6 +171,7 @@ def query4_crd_sql(session, sql: str, params: dict) -> []:
|
|
153
171
|
rs.append(data)
|
154
172
|
return rs
|
155
173
|
|
174
|
+
|
156
175
|
sqlite_and_pg_page_sql = """
|
157
176
|
limit :limit offset :offset
|
158
177
|
"""
|
@@ -160,7 +179,8 @@ mysql_page_sql = """
|
|
160
179
|
limit :offset, :limit
|
161
180
|
"""
|
162
181
|
|
163
|
-
|
182
|
+
|
183
|
+
def query_by_page4_crd_sql(session, sql: str, params: dict, pageInfo: PageInfo) -> []:
|
164
184
|
db_name = engines[thread_local.db_key].name
|
165
185
|
if db_name == 'postgresql' or db_name == 'sqlite':
|
166
186
|
page_sql = sqlite_and_pg_page_sql
|
ctools/dict_wrapper.py
CHANGED
ctools/ex.py
CHANGED
ctools/geo/coord_trans.py
CHANGED
@@ -6,122 +6,122 @@ pi = 3.1415926535897932384626 # π
|
|
6
6
|
a = 6378245.0 # 长半轴
|
7
7
|
ee = 0.00669342162296594323 # 偏心率平方
|
8
8
|
|
9
|
+
|
9
10
|
def gcj02_to_bd09(lng, lat):
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
11
|
+
"""
|
12
|
+
火星坐标系(GCJ-02)转百度坐标系(BD-09)
|
13
|
+
谷歌、高德——>百度
|
14
|
+
:param lng:火星坐标经度
|
15
|
+
:param lat:火星坐标纬度
|
16
|
+
:return:
|
17
|
+
"""
|
18
|
+
z = math.sqrt(lng * lng + lat * lat) + 0.00002 * math.sin(lat * x_pi)
|
19
|
+
theta = math.atan2(lat, lng) + 0.000003 * math.cos(lng * x_pi)
|
20
|
+
bd_lng = z * math.cos(theta) + 0.0065
|
21
|
+
bd_lat = z * math.sin(theta) + 0.006
|
22
|
+
return [bd_lng, bd_lat]
|
22
23
|
|
23
24
|
|
24
25
|
def bd09_to_gcj02(bd_lon, bd_lat):
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
26
|
+
"""
|
27
|
+
百度坐标系(BD-09)转火星坐标系(GCJ-02)
|
28
|
+
百度——>谷歌、高德
|
29
|
+
:param bd_lat:百度坐标纬度
|
30
|
+
:param bd_lon:百度坐标经度
|
31
|
+
:return:转换后的坐标列表形式
|
32
|
+
"""
|
33
|
+
x = bd_lon - 0.0065
|
34
|
+
y = bd_lat - 0.006
|
35
|
+
z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * x_pi)
|
36
|
+
theta = math.atan2(y, x) - 0.000003 * math.cos(x * x_pi)
|
37
|
+
gg_lng = z * math.cos(theta)
|
38
|
+
gg_lat = z * math.sin(theta)
|
39
|
+
return [gg_lng, gg_lat]
|
39
40
|
|
40
41
|
|
41
42
|
def wgs84_to_gcj02(lng, lat):
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
43
|
+
"""
|
44
|
+
WGS84转GCJ02(火星坐标系)
|
45
|
+
:param lng:WGS84坐标系的经度
|
46
|
+
:param lat:WGS84坐标系的纬度
|
47
|
+
:return:
|
48
|
+
"""
|
49
|
+
if out_of_china(lng, lat): # 判断是否在国内
|
50
|
+
return [lng, lat]
|
51
|
+
dlat = _transformlat(lng - 105.0, lat - 35.0)
|
52
|
+
dlng = _transformlng(lng - 105.0, lat - 35.0)
|
53
|
+
radlat = lat / 180.0 * pi
|
54
|
+
magic = math.sin(radlat)
|
55
|
+
magic = 1 - ee * magic * magic
|
56
|
+
sqrtmagic = math.sqrt(magic)
|
57
|
+
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
|
58
|
+
dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
|
59
|
+
mglat = lat + dlat
|
60
|
+
mglng = lng + dlng
|
61
|
+
return [mglng, mglat]
|
61
62
|
|
62
63
|
|
63
64
|
def gcj02_to_wgs84(lng, lat):
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
65
|
+
"""
|
66
|
+
GCJ02(火星坐标系)转GPS84
|
67
|
+
:param lng:火星坐标系的经度
|
68
|
+
:param lat:火星坐标系纬度
|
69
|
+
:return:
|
70
|
+
"""
|
71
|
+
if out_of_china(lng, lat):
|
72
|
+
return [lng, lat]
|
73
|
+
dlat = _transformlat(lng - 105.0, lat - 35.0)
|
74
|
+
dlng = _transformlng(lng - 105.0, lat - 35.0)
|
75
|
+
radlat = lat / 180.0 * pi
|
76
|
+
magic = math.sin(radlat)
|
77
|
+
magic = 1 - ee * magic * magic
|
78
|
+
sqrtmagic = math.sqrt(magic)
|
79
|
+
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
|
80
|
+
dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
|
81
|
+
mglat = lat + dlat
|
82
|
+
mglng = lng + dlng
|
83
|
+
return [lng * 2 - mglng, lat * 2 - mglat]
|
83
84
|
|
84
85
|
|
85
86
|
def bd09_to_wgs84(bd_lon, bd_lat):
|
86
|
-
|
87
|
-
|
87
|
+
lon, lat = bd09_to_gcj02(bd_lon, bd_lat)
|
88
|
+
return gcj02_to_wgs84(lon, lat)
|
88
89
|
|
89
90
|
|
90
91
|
def wgs84_to_bd09(lon, lat):
|
91
|
-
|
92
|
-
|
92
|
+
lon, lat = wgs84_to_gcj02(lon, lat)
|
93
|
+
return gcj02_to_bd09(lon, lat)
|
93
94
|
|
94
95
|
|
95
96
|
def _transformlat(lng, lat):
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
97
|
+
ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + \
|
98
|
+
0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng))
|
99
|
+
ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *
|
100
|
+
math.sin(2.0 * lng * pi)) * 2.0 / 3.0
|
101
|
+
ret += (20.0 * math.sin(lat * pi) + 40.0 *
|
102
|
+
math.sin(lat / 3.0 * pi)) * 2.0 / 3.0
|
103
|
+
ret += (160.0 * math.sin(lat / 12.0 * pi) + 320 *
|
104
|
+
math.sin(lat * pi / 30.0)) * 2.0 / 3.0
|
105
|
+
return ret
|
105
106
|
|
106
107
|
|
107
108
|
def _transformlng(lng, lat):
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
109
|
+
ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + \
|
110
|
+
0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng))
|
111
|
+
ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *
|
112
|
+
math.sin(2.0 * lng * pi)) * 2.0 / 3.0
|
113
|
+
ret += (20.0 * math.sin(lng * pi) + 40.0 *
|
114
|
+
math.sin(lng / 3.0 * pi)) * 2.0 / 3.0
|
115
|
+
ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 *
|
116
|
+
math.sin(lng / 30.0 * pi)) * 2.0 / 3.0
|
117
|
+
return ret
|
117
118
|
|
118
119
|
|
119
120
|
def out_of_china(lng, lat):
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
121
|
+
"""
|
122
|
+
判断是否在国内,不在国内不做偏移
|
123
|
+
:param lng:
|
124
|
+
:param lat:
|
125
|
+
:return:
|
126
|
+
"""
|
127
|
+
return not (lng > 73.66 and lng < 135.05 and lat > 3.86 and lat < 53.55)
|
ctools/geo/douglas_rarefy.py
CHANGED
@@ -4,6 +4,7 @@ __author__ = 'haoyang'
|
|
4
4
|
__date__ = '2024/9/19 14:02'
|
5
5
|
|
6
6
|
import math
|
7
|
+
|
7
8
|
from jsonpath_ng import parser
|
8
9
|
|
9
10
|
from ctools import cjson
|
@@ -13,6 +14,7 @@ from ctools.sys_log import flog as log
|
|
13
14
|
douglas_rarefy.DouglasRarefy(res, level=3).sparse_points()
|
14
15
|
"""
|
15
16
|
|
17
|
+
|
16
18
|
class THIN_LEVEL:
|
17
19
|
L1 = 0.00001
|
18
20
|
L2 = 0.00003
|
@@ -24,12 +26,14 @@ class THIN_LEVEL:
|
|
24
26
|
L8 = 0.0017
|
25
27
|
L9 = 0.0022
|
26
28
|
|
29
|
+
|
27
30
|
class Point:
|
28
31
|
def __init__(self, lng, lat, origin_data):
|
29
32
|
self.lng = lng
|
30
33
|
self.lat = lat
|
31
34
|
self.origin_data = origin_data
|
32
35
|
|
36
|
+
|
33
37
|
def _get_line_by_point(xy1, xy2):
|
34
38
|
"""
|
35
39
|
根据两个点求直线方程 ax + by + c = 0
|
@@ -68,7 +72,8 @@ class DouglasRarefy:
|
|
68
72
|
level default is L2, this level can be hold most of the points detail
|
69
73
|
ret_tpl is the result tpl, can be arrays and json or some can be json loads, exp: [{lng}, {lat}] OR {{"lng": {lng}, "lat": {lat}}}
|
70
74
|
"""
|
71
|
-
|
75
|
+
|
76
|
+
def __init__(self, points: [], level=THIN_LEVEL.L2, ret_tpl=None, get_lng=None, get_lat=None):
|
72
77
|
if not isinstance(points, list): raise Exception('points must be list obj !!')
|
73
78
|
if len(points) < 3: raise Exception('points length must be gt 2 !!')
|
74
79
|
self.points = points
|
@@ -105,11 +110,11 @@ class DouglasRarefy:
|
|
105
110
|
else:
|
106
111
|
return [cjson.loads(self.ret_tpl.format(lng=points[0].lng, lat=points[0].lat)), cjson.loads(self.ret_tpl.format(lng=points[-1].lng, lat=points[-1].lat))]
|
107
112
|
|
108
|
-
xy_first = points[0]
|
109
|
-
xy_end
|
110
|
-
a, b, c
|
111
|
-
d_max = 0
|
112
|
-
split = 0
|
113
|
+
xy_first = points[0] # 第一个点
|
114
|
+
xy_end = points[-1] # 最后一个点
|
115
|
+
a, b, c = _get_line_by_point(xy_first, xy_end) # 获取直线方程的 a, b, c 值
|
116
|
+
d_max = 0 # 记录点到直线的最大距离
|
117
|
+
split = 0 # 分割位置
|
113
118
|
for i in range(1, len(points) - 1):
|
114
119
|
d = _get_distance_from_point_to_line(a, b, c, points[i])
|
115
120
|
if d > d_max:
|
@@ -117,8 +122,8 @@ class DouglasRarefy:
|
|
117
122
|
d_max = d
|
118
123
|
if d_max > self.threshold:
|
119
124
|
# 如果存在点到首位点连成直线的距离大于 max_distance 的, 即需要再次划分
|
120
|
-
child_left
|
121
|
-
child_right = self._sparse_points(points[split:])
|
125
|
+
child_left = self._sparse_points(points[:split + 1]) # 递归处理左边部分
|
126
|
+
child_right = self._sparse_points(points[split:]) # 递归处理右边部分
|
122
127
|
# 合并结果,避免重复
|
123
128
|
return child_left + child_right[1:]
|
124
129
|
else:
|
@@ -132,4 +137,3 @@ class DouglasRarefy:
|
|
132
137
|
|
133
138
|
def sparse_points(self):
|
134
139
|
return self._sparse_points(self.data)
|
135
|
-
|
ctools/metrics.py
CHANGED
@@ -14,6 +14,7 @@ _metrics_initial = {}
|
|
14
14
|
_lock = threading.Lock()
|
15
15
|
_metrics_persistent_path = os.path.join(path_info.get_user_work_path('metrics', True), 'persistent.json')
|
16
16
|
|
17
|
+
|
17
18
|
# 认证中间件
|
18
19
|
# @app.before_request
|
19
20
|
# def check_authentication():
|
@@ -29,6 +30,7 @@ class MetricType(Enum):
|
|
29
30
|
SUMMARY = 'summary'
|
30
31
|
HISTOGRAM = 'histogram'
|
31
32
|
|
33
|
+
|
32
34
|
class Metric:
|
33
35
|
def __init__(self, metric_type: MetricType, metric_key: str, metric_labels: [], persistent: bool = False, buckets: [] = None, reset: bool = False, desc: str = ""):
|
34
36
|
self.metric_type = metric_type
|
@@ -51,6 +53,7 @@ class Metric:
|
|
51
53
|
raise Exception('metric type not found')
|
52
54
|
_metrics_initial[metric_key] = self
|
53
55
|
|
56
|
+
|
54
57
|
@call.once
|
55
58
|
def init(reset_persistent: bool = False):
|
56
59
|
if os.path.exists(_metrics_persistent_path) and not reset_persistent:
|
@@ -72,6 +75,7 @@ def init(reset_persistent: bool = False):
|
|
72
75
|
persistent_metrics()
|
73
76
|
start_http_server(port=_metrics_port)
|
74
77
|
|
78
|
+
|
75
79
|
@call.schd(5, start_by_call=True)
|
76
80
|
def persistent_metrics():
|
77
81
|
if _persistent_json and not _lock.locked():
|
@@ -80,6 +84,7 @@ def persistent_metrics():
|
|
80
84
|
persistent_file.write(cjson.dumps(_persistent_json))
|
81
85
|
persistent_file.flush()
|
82
86
|
|
87
|
+
|
83
88
|
def opt(metric_key: str, label_values: [], metric_value: int):
|
84
89
|
_lock.acquire(timeout=5)
|
85
90
|
try:
|
@@ -119,6 +124,7 @@ def opt(metric_key: str, label_values: [], metric_value: int):
|
|
119
124
|
log.error("添加指标信息异常: %s" % e)
|
120
125
|
_lock.release()
|
121
126
|
|
127
|
+
|
122
128
|
def _init_all_metrics():
|
123
129
|
Metric(MetricType.GAUGE, 'gomyck', ['g_label1', 'g_label2'], persistent=True, reset=True)
|
124
130
|
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
ctools/office/word_fill.py
CHANGED
@@ -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/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/pools/process_pool.py
CHANGED
@@ -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())
|
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)
|
ctools/pools/thread_pool.py
CHANGED
@@ -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)
|
16
|
-
_threadPool = ThreadPoolExecutor(max_workers=
|
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)
|