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.
Files changed (64) hide show
  1. ctools/__init__.py +21 -0
  2. ctools/ai/env_config.py +20 -1
  3. ctools/ai/llm_chat.py +8 -8
  4. ctools/ai/llm_client.py +37 -28
  5. ctools/ai/mcp/mcp_client.py +33 -16
  6. ctools/ai/tools/json_extract.py +3 -2
  7. ctools/ai/tools/quick_tools.py +68 -21
  8. ctools/ai/tools/tool_use_xml_parse.py +2 -1
  9. ctools/ai/tools/xml_extract.py +3 -0
  10. ctools/application.py +19 -17
  11. ctools/auto/browser_element.py +11 -3
  12. ctools/auto/plan_area.py +2 -2
  13. ctools/auto/pty_process.py +0 -1
  14. ctools/auto/screenshot.py +3 -4
  15. ctools/auto/win_canvas.py +10 -4
  16. ctools/auto/win_control.py +8 -4
  17. ctools/call.py +18 -22
  18. ctools/cdate.py +43 -2
  19. ctools/cid.py +3 -4
  20. ctools/cipher/aes_util.py +2 -2
  21. ctools/cipher/b64.py +2 -0
  22. ctools/cipher/czip.py +3 -1
  23. ctools/cipher/rsa.py +6 -1
  24. ctools/cipher/sign.py +1 -0
  25. ctools/cipher/sm_util.py +3 -0
  26. ctools/cjson.py +5 -0
  27. ctools/cron_lite.py +10 -4
  28. ctools/database/database.py +37 -17
  29. ctools/dict_wrapper.py +1 -0
  30. ctools/ex.py +1 -0
  31. ctools/geo/coord_trans.py +94 -94
  32. ctools/geo/douglas_rarefy.py +13 -9
  33. ctools/metrics.py +6 -0
  34. ctools/office/cword.py +7 -7
  35. ctools/office/word_fill.py +1 -4
  36. ctools/path_info.py +29 -0
  37. ctools/pools/process_pool.py +6 -1
  38. ctools/pools/thread_pool.py +6 -2
  39. ctools/similar.py +3 -0
  40. ctools/stream/ckafka.py +11 -5
  41. ctools/stream/credis.py +29 -21
  42. ctools/stream/mqtt_utils.py +2 -2
  43. ctools/sys_info.py +8 -0
  44. ctools/sys_log.py +3 -0
  45. ctools/util/cftp.py +4 -2
  46. ctools/util/http_util.py +1 -0
  47. ctools/util/image_process.py +1 -1
  48. ctools/util/snow_id.py +3 -2
  49. ctools/web/__init__.py +2 -2
  50. ctools/web/aio_web_server.py +19 -9
  51. ctools/web/api_result.py +3 -2
  52. ctools/web/bottle_web_base.py +15 -2
  53. ctools/web/bottle_webserver.py +14 -8
  54. ctools/web/bottle_websocket.py +4 -0
  55. ctools/web/ctoken.py +5 -1
  56. ctools/web/download_util.py +1 -1
  57. ctools/web/params_util.py +4 -0
  58. ctools/web/upload_util.py +1 -1
  59. {gomyck_tools-1.4.0.dist-info → gomyck_tools-1.4.2.dist-info}/METADATA +2 -1
  60. gomyck_tools-1.4.2.dist-info/RECORD +82 -0
  61. gomyck_tools-1.4.0.dist-info/RECORD +0 -82
  62. {gomyck_tools-1.4.0.dist-info → gomyck_tools-1.4.2.dist-info}/WHEEL +0 -0
  63. {gomyck_tools-1.4.0.dist-info → gomyck_tools-1.4.2.dist-info}/licenses/LICENSE +0 -0
  64. {gomyck_tools-1.4.0.dist-info → gomyck_tools-1.4.2.dist-info}/top_level.txt +0 -0
@@ -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
- def getEngine(db_key: str='default'):
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
- def _create_connection(db_url: str, pool_size: int=5, max_overflow: int=25, connect_args={}, echo: bool=False):
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
- echo=echo,
76
- future=True,
77
- pool_size=pool_size,
78
- max_overflow=max_overflow,
79
- pool_pre_ping=True,
80
- pool_recycle=3600,
81
- connect_args=connect_args)
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
- def query_by_page4_crd_sql(session, sql: str, params: dict, pageInfo) -> []:
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
@@ -3,6 +3,7 @@
3
3
  __author__ = 'haoyang'
4
4
  __date__ = '2024/10/25 09:42'
5
5
 
6
+
6
7
  class DictWrapper(dict):
7
8
 
8
9
  def __getattr__(self, key):
ctools/ex.py CHANGED
@@ -2,6 +2,7 @@ import time
2
2
  import traceback
3
3
  from functools import wraps
4
4
 
5
+
5
6
  # annotation
6
7
  def exception_handler(fail_return, retry_num=0, delay=3, catch_e=Exception, print_exc=False):
7
8
  def decorator(func):
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
- 火星坐标系(GCJ-02)转百度坐标系(BD-09)
12
- 谷歌、高德——>百度
13
- :param lng:火星坐标经度
14
- :param lat:火星坐标纬度
15
- :return:
16
- """
17
- z = math.sqrt(lng * lng + lat * lat) + 0.00002 * math.sin(lat * x_pi)
18
- theta = math.atan2(lat, lng) + 0.000003 * math.cos(lng * x_pi)
19
- bd_lng = z * math.cos(theta) + 0.0065
20
- bd_lat = z * math.sin(theta) + 0.006
21
- return [bd_lng, bd_lat]
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
- 百度坐标系(BD-09)转火星坐标系(GCJ-02)
27
- 百度——>谷歌、高德
28
- :param bd_lat:百度坐标纬度
29
- :param bd_lon:百度坐标经度
30
- :return:转换后的坐标列表形式
31
- """
32
- x = bd_lon - 0.0065
33
- y = bd_lat - 0.006
34
- z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * x_pi)
35
- theta = math.atan2(y, x) - 0.000003 * math.cos(x * x_pi)
36
- gg_lng = z * math.cos(theta)
37
- gg_lat = z * math.sin(theta)
38
- return [gg_lng, gg_lat]
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
- WGS84转GCJ02(火星坐标系)
44
- :param lng:WGS84坐标系的经度
45
- :param lat:WGS84坐标系的纬度
46
- :return:
47
- """
48
- if out_of_china(lng, lat): # 判断是否在国内
49
- return [lng, lat]
50
- dlat = _transformlat(lng - 105.0, lat - 35.0)
51
- dlng = _transformlng(lng - 105.0, lat - 35.0)
52
- radlat = lat / 180.0 * pi
53
- magic = math.sin(radlat)
54
- magic = 1 - ee * magic * magic
55
- sqrtmagic = math.sqrt(magic)
56
- dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
57
- dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
58
- mglat = lat + dlat
59
- mglng = lng + dlng
60
- return [mglng, mglat]
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
- GCJ02(火星坐标系)转GPS84
66
- :param lng:火星坐标系的经度
67
- :param lat:火星坐标系纬度
68
- :return:
69
- """
70
- if out_of_china(lng, lat):
71
- return [lng, lat]
72
- dlat = _transformlat(lng - 105.0, lat - 35.0)
73
- dlng = _transformlng(lng - 105.0, lat - 35.0)
74
- radlat = lat / 180.0 * pi
75
- magic = math.sin(radlat)
76
- magic = 1 - ee * magic * magic
77
- sqrtmagic = math.sqrt(magic)
78
- dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
79
- dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
80
- mglat = lat + dlat
81
- mglng = lng + dlng
82
- return [lng * 2 - mglng, lat * 2 - mglat]
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
- lon, lat = bd09_to_gcj02(bd_lon, bd_lat)
87
- return gcj02_to_wgs84(lon, lat)
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
- lon, lat = wgs84_to_gcj02(lon, lat)
92
- return gcj02_to_bd09(lon, lat)
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
- ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + \
97
- 0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng))
98
- ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *
99
- math.sin(2.0 * lng * pi)) * 2.0 / 3.0
100
- ret += (20.0 * math.sin(lat * pi) + 40.0 *
101
- math.sin(lat / 3.0 * pi)) * 2.0 / 3.0
102
- ret += (160.0 * math.sin(lat / 12.0 * pi) + 320 *
103
- math.sin(lat * pi / 30.0)) * 2.0 / 3.0
104
- return ret
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
- ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + \
109
- 0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng))
110
- ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *
111
- math.sin(2.0 * lng * pi)) * 2.0 / 3.0
112
- ret += (20.0 * math.sin(lng * pi) + 40.0 *
113
- math.sin(lng / 3.0 * pi)) * 2.0 / 3.0
114
- ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 *
115
- math.sin(lng / 30.0 * pi)) * 2.0 / 3.0
116
- return ret
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
- :param lng:
123
- :param lat:
124
- :return:
125
- """
126
- return not (lng > 73.66 and lng < 135.05 and lat > 3.86 and lat < 53.55)
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)
@@ -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
- def __init__(self, points:[], level=THIN_LEVEL.L2, ret_tpl=None, get_lng=None, get_lat=None):
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 = points[-1] # 最后一个点
110
- a, b, c = _get_line_by_point(xy_first, xy_end) # 获取直线方程的 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 = self._sparse_points(points[:split + 1]) # 递归处理左边部分
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
- 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/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)
@@ -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)