mcp-dbutils 0.22.0__py3-none-any.whl → 0.23.1__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.
- mcp_dbutils/base.py +39 -1
- mcp_dbutils/mysql/handler.py +12 -0
- mcp_dbutils/mysql/server.py +41 -19
- mcp_dbutils/postgres/handler.py +12 -0
- mcp_dbutils/postgres/server.py +28 -15
- mcp_dbutils/sqlite/handler.py +12 -0
- mcp_dbutils/sqlite/server.py +26 -18
- {mcp_dbutils-0.22.0.dist-info → mcp_dbutils-0.23.1.dist-info}/METADATA +3 -2
- {mcp_dbutils-0.22.0.dist-info → mcp_dbutils-0.23.1.dist-info}/RECORD +12 -12
- {mcp_dbutils-0.22.0.dist-info → mcp_dbutils-0.23.1.dist-info}/WHEEL +0 -0
- {mcp_dbutils-0.22.0.dist-info → mcp_dbutils-0.23.1.dist-info}/entry_points.txt +0 -0
- {mcp_dbutils-0.22.0.dist-info → mcp_dbutils-0.23.1.dist-info}/licenses/LICENSE +0 -0
mcp_dbutils/base.py
CHANGED
@@ -474,6 +474,29 @@ class ConnectionServer:
|
|
474
474
|
},
|
475
475
|
"required": ["connection", "sql"],
|
476
476
|
},
|
477
|
+
annotations={
|
478
|
+
"examples": [
|
479
|
+
{
|
480
|
+
"input": {
|
481
|
+
"connection": "example_db",
|
482
|
+
"sql": "SELECT id, name, email FROM users LIMIT 10"
|
483
|
+
},
|
484
|
+
"output": "Results showing first 10 users with their IDs, names, and email addresses"
|
485
|
+
},
|
486
|
+
{
|
487
|
+
"input": {
|
488
|
+
"connection": "example_db",
|
489
|
+
"sql": "SELECT department, COUNT(*) as employee_count FROM employees GROUP BY department ORDER BY employee_count DESC"
|
490
|
+
},
|
491
|
+
"output": "Results showing departments and their employee counts in descending order"
|
492
|
+
}
|
493
|
+
],
|
494
|
+
"usage_tips": [
|
495
|
+
"Always use SELECT statements only - other SQL operations are not permitted",
|
496
|
+
"Use LIMIT to restrict large result sets",
|
497
|
+
"For complex queries, consider using dbutils-explain-query first to understand query execution plan"
|
498
|
+
]
|
499
|
+
}
|
477
500
|
),
|
478
501
|
types.Tool(
|
479
502
|
name="dbutils-list-tables",
|
@@ -488,6 +511,19 @@ class ConnectionServer:
|
|
488
511
|
},
|
489
512
|
"required": ["connection"],
|
490
513
|
},
|
514
|
+
annotations={
|
515
|
+
"examples": [
|
516
|
+
{
|
517
|
+
"input": {"connection": "example_db"},
|
518
|
+
"output": "List of tables in the example_db database with their URIs and descriptions"
|
519
|
+
}
|
520
|
+
],
|
521
|
+
"usage_tips": [
|
522
|
+
"Use this tool first when exploring a new database to understand its structure",
|
523
|
+
"After listing tables, use dbutils-describe-table to get detailed information about specific tables",
|
524
|
+
"Table URIs can be used with other database tools for further operations"
|
525
|
+
]
|
526
|
+
}
|
491
527
|
),
|
492
528
|
types.Tool(
|
493
529
|
name="dbutils-describe-table",
|
@@ -999,5 +1035,7 @@ class ConnectionServer:
|
|
999
1035
|
"""Run server"""
|
1000
1036
|
async with mcp.server.stdio.stdio_server() as streams:
|
1001
1037
|
await self.server.run(
|
1002
|
-
streams[0],
|
1038
|
+
streams[0],
|
1039
|
+
streams[1],
|
1040
|
+
self.server.create_initialization_options()
|
1003
1041
|
)
|
mcp_dbutils/mysql/handler.py
CHANGED
@@ -537,3 +537,15 @@ class MySQLHandler(ConnectionHandler):
|
|
537
537
|
"""Cleanup resources"""
|
538
538
|
# Log final stats before cleanup
|
539
539
|
self.log("info", f"Final MySQL handler stats: {self.stats.to_dict()}")
|
540
|
+
|
541
|
+
# 主动关闭连接
|
542
|
+
if hasattr(self, '_connection') and self._connection:
|
543
|
+
try:
|
544
|
+
self.log("debug", "Closing MySQL connection")
|
545
|
+
self._connection.close()
|
546
|
+
self._connection = None
|
547
|
+
except Exception as e:
|
548
|
+
self.log("warning", f"Error closing MySQL connection: {str(e)}")
|
549
|
+
|
550
|
+
# 清理其他资源
|
551
|
+
self.log("debug", "MySQL handler cleanup complete")
|
mcp_dbutils/mysql/server.py
CHANGED
@@ -27,12 +27,12 @@ class MySQLServer(ConnectionServer):
|
|
27
27
|
conn_params = config.get_connection_params()
|
28
28
|
masked_params = config.get_masked_connection_info()
|
29
29
|
self.log("debug", f"正在连接数据库,参数: {masked_params}")
|
30
|
-
|
30
|
+
|
31
31
|
# 测试连接
|
32
32
|
test_conn = mysql.connector.connect(**conn_params)
|
33
33
|
test_conn.close()
|
34
34
|
self.log("info", "测试连接成功")
|
35
|
-
|
35
|
+
|
36
36
|
# 创建连接池配置
|
37
37
|
pool_config = {
|
38
38
|
'pool_name': 'mypool',
|
@@ -51,7 +51,7 @@ class MySQLServer(ConnectionServer):
|
|
51
51
|
conn = self.pool.get_connection()
|
52
52
|
with conn.cursor(dictionary=True) as cur: # NOSONAR - dictionary参数是正确的,用于返回字典格式的结果
|
53
53
|
cur.execute("""
|
54
|
-
SELECT
|
54
|
+
SELECT
|
55
55
|
table_name,
|
56
56
|
table_comment as description
|
57
57
|
FROM information_schema.tables
|
@@ -81,7 +81,7 @@ class MySQLServer(ConnectionServer):
|
|
81
81
|
with conn.cursor(dictionary=True) as cur: # NOSONAR - dictionary参数是正确的,用于返回字典格式的结果
|
82
82
|
# 获取列信息
|
83
83
|
cur.execute("""
|
84
|
-
SELECT
|
84
|
+
SELECT
|
85
85
|
column_name,
|
86
86
|
data_type,
|
87
87
|
is_nullable,
|
@@ -157,6 +157,9 @@ class MySQLServer(ConnectionServer):
|
|
157
157
|
|
158
158
|
connection = arguments.get("connection")
|
159
159
|
conn = None
|
160
|
+
results = []
|
161
|
+
columns = []
|
162
|
+
|
160
163
|
try:
|
161
164
|
if connection and self.config_path:
|
162
165
|
# 使用指定的数据库连接
|
@@ -177,19 +180,22 @@ class MySQLServer(ConnectionServer):
|
|
177
180
|
cur.execute(sql)
|
178
181
|
results = cur.fetchall()
|
179
182
|
columns = [desc[0] for desc in cur.description]
|
180
|
-
result_text = str({
|
181
|
-
'type': 'mysql',
|
182
|
-
'config_name': connection or 'default',
|
183
|
-
'query_result': {
|
184
|
-
'columns': columns,
|
185
|
-
'rows': results,
|
186
|
-
'row_count': len(results)
|
187
|
-
}
|
188
|
-
})
|
189
|
-
self.log("info", f"查询完成,返回{len(results)}行结果")
|
190
|
-
return [types.TextContent(type="text", text=result_text)]
|
191
183
|
finally:
|
192
184
|
cur.execute("ROLLBACK")
|
185
|
+
|
186
|
+
# 处理结果(在连接操作完成后)
|
187
|
+
result_text = str({
|
188
|
+
'type': 'mysql',
|
189
|
+
'config_name': connection or 'default',
|
190
|
+
'query_result': {
|
191
|
+
'columns': columns,
|
192
|
+
'rows': results,
|
193
|
+
'row_count': len(results)
|
194
|
+
}
|
195
|
+
})
|
196
|
+
self.log("info", f"查询完成,返回{len(results)}行结果")
|
197
|
+
return [types.TextContent(type="text", text=result_text)]
|
198
|
+
|
193
199
|
except Exception as e:
|
194
200
|
error = f"查询执行失败: {str(e)}"
|
195
201
|
error_msg = str({
|
@@ -200,12 +206,28 @@ class MySQLServer(ConnectionServer):
|
|
200
206
|
self.log("error", error_msg)
|
201
207
|
return [types.TextContent(type="text", text=error_msg)]
|
202
208
|
finally:
|
209
|
+
# 确保连接始终被关闭
|
203
210
|
if conn:
|
204
|
-
|
211
|
+
try:
|
212
|
+
conn.close() # 关闭连接(连接池会自动处理)
|
213
|
+
except Exception as e:
|
214
|
+
self.log("warning", f"关闭连接时出错: {str(e)}")
|
205
215
|
|
206
216
|
async def cleanup(self):
|
207
217
|
"""清理资源"""
|
208
218
|
if hasattr(self, 'pool'):
|
209
|
-
self.log("info", "
|
210
|
-
# MySQL连接池没有直接的closeall
|
211
|
-
|
219
|
+
self.log("info", "关闭MySQL连接池")
|
220
|
+
# MySQL连接池没有直接的closeall方法,但我们可以主动关闭连接
|
221
|
+
try:
|
222
|
+
# 尝试获取并关闭所有活动连接
|
223
|
+
for _ in range(5): # 假设最多有5个连接在池中
|
224
|
+
try:
|
225
|
+
conn = self.pool.get_connection()
|
226
|
+
conn.close()
|
227
|
+
except Exception as e:
|
228
|
+
# 如果没有更多连接或出现其他错误,跳出循环
|
229
|
+
self.log("debug", f"连接池清理: {str(e)}")
|
230
|
+
break
|
231
|
+
self.log("info", "MySQL连接池清理完成")
|
232
|
+
except Exception as e:
|
233
|
+
self.log("warning", f"清理MySQL连接池时出错: {str(e)}")
|
mcp_dbutils/postgres/handler.py
CHANGED
@@ -610,3 +610,15 @@ class PostgreSQLHandler(ConnectionHandler):
|
|
610
610
|
"""Cleanup resources"""
|
611
611
|
# Log final stats before cleanup
|
612
612
|
self.log("info", f"Final PostgreSQL handler stats: {self.stats.to_dict()}")
|
613
|
+
|
614
|
+
# 主动关闭连接
|
615
|
+
if hasattr(self, '_connection') and self._connection:
|
616
|
+
try:
|
617
|
+
self.log("debug", "Closing PostgreSQL connection")
|
618
|
+
self._connection.close()
|
619
|
+
self._connection = None
|
620
|
+
except Exception as e:
|
621
|
+
self.log("warning", f"Error closing PostgreSQL connection: {str(e)}")
|
622
|
+
|
623
|
+
# 清理其他资源
|
624
|
+
self.log("debug", "PostgreSQL handler cleanup complete")
|
mcp_dbutils/postgres/server.py
CHANGED
@@ -148,9 +148,14 @@ class PostgreSQLServer(ConnectionServer):
|
|
148
148
|
# 仅允许SELECT语句
|
149
149
|
if not sql.lower().startswith("select"):
|
150
150
|
raise ValueError("仅支持SELECT查询")
|
151
|
+
|
151
152
|
connection = arguments.get("connection")
|
152
153
|
use_pool = True
|
153
154
|
conn = None
|
155
|
+
results = []
|
156
|
+
columns = []
|
157
|
+
formatted_results = []
|
158
|
+
|
154
159
|
try:
|
155
160
|
if connection and self.config_path:
|
156
161
|
# 使用指定的数据库连接
|
@@ -163,6 +168,7 @@ class PostgreSQLServer(ConnectionServer):
|
|
163
168
|
else:
|
164
169
|
# 使用现有连接池
|
165
170
|
conn = self.pool.getconn()
|
171
|
+
|
166
172
|
self.log("info", f"执行查询: {sql}")
|
167
173
|
with conn.cursor() as cur:
|
168
174
|
# 启动只读事务
|
@@ -172,19 +178,22 @@ class PostgreSQLServer(ConnectionServer):
|
|
172
178
|
results = cur.fetchall()
|
173
179
|
columns = [desc[0] for desc in cur.description]
|
174
180
|
formatted_results = [dict(zip(columns, row)) for row in results]
|
175
|
-
result_text = str({
|
176
|
-
'type': 'postgres',
|
177
|
-
'config_name': connection or 'default',
|
178
|
-
'query_result': {
|
179
|
-
'columns': columns,
|
180
|
-
'rows': formatted_results,
|
181
|
-
'row_count': len(results)
|
182
|
-
}
|
183
|
-
})
|
184
|
-
self.log("info", f"查询完成,返回{len(results)}行结果")
|
185
|
-
return [types.TextContent(type="text", text=result_text)]
|
186
181
|
finally:
|
187
182
|
cur.execute("ROLLBACK")
|
183
|
+
|
184
|
+
# 处理结果(在连接操作完成后)
|
185
|
+
result_text = str({
|
186
|
+
'type': 'postgres',
|
187
|
+
'config_name': connection or 'default',
|
188
|
+
'query_result': {
|
189
|
+
'columns': columns,
|
190
|
+
'rows': formatted_results,
|
191
|
+
'row_count': len(results)
|
192
|
+
}
|
193
|
+
})
|
194
|
+
self.log("info", f"查询完成,返回{len(results)}行结果")
|
195
|
+
return [types.TextContent(type="text", text=result_text)]
|
196
|
+
|
188
197
|
except Exception as e:
|
189
198
|
if isinstance(e, psycopg2.Error):
|
190
199
|
error = f"查询执行失败: [Code: {e.pgcode}] {e.pgerror or str(e)}"
|
@@ -198,11 +207,15 @@ class PostgreSQLServer(ConnectionServer):
|
|
198
207
|
self.log("error", error_msg)
|
199
208
|
return [types.TextContent(type="text", text=error_msg)]
|
200
209
|
finally:
|
210
|
+
# 确保连接始终被正确处理
|
201
211
|
if conn:
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
212
|
+
try:
|
213
|
+
if use_pool:
|
214
|
+
self.pool.putconn(conn)
|
215
|
+
else:
|
216
|
+
conn.close()
|
217
|
+
except Exception as e:
|
218
|
+
self.log("warning", f"关闭连接时出错: {str(e)}")
|
206
219
|
async def cleanup(self):
|
207
220
|
"""清理资源"""
|
208
221
|
if hasattr(self, 'pool'):
|
mcp_dbutils/sqlite/handler.py
CHANGED
@@ -475,3 +475,15 @@ class SQLiteHandler(ConnectionHandler):
|
|
475
475
|
"""Cleanup resources"""
|
476
476
|
# Log final stats
|
477
477
|
self.log("info", f"Final SQLite handler stats: {self.stats.to_dict()}")
|
478
|
+
|
479
|
+
# 主动关闭连接
|
480
|
+
if hasattr(self, '_connection') and self._connection:
|
481
|
+
try:
|
482
|
+
self.log("debug", "Closing SQLite connection")
|
483
|
+
self._connection.close()
|
484
|
+
self._connection = None
|
485
|
+
except Exception as e:
|
486
|
+
self.log("warning", f"Error closing SQLite connection: {str(e)}")
|
487
|
+
|
488
|
+
# 清理其他资源
|
489
|
+
self.log("debug", "SQLite handler cleanup complete")
|
mcp_dbutils/sqlite/server.py
CHANGED
@@ -141,8 +141,13 @@ class SQLiteServer(ConnectionServer):
|
|
141
141
|
raise ValueError("仅支持SELECT查询")
|
142
142
|
|
143
143
|
conn = None
|
144
|
+
connection = arguments.get("connection")
|
145
|
+
config_name = connection if isinstance(connection, str) else 'default'
|
146
|
+
results = []
|
147
|
+
columns = []
|
148
|
+
formatted_results = []
|
149
|
+
|
144
150
|
try:
|
145
|
-
connection = arguments.get("connection")
|
146
151
|
if connection and self.config_path:
|
147
152
|
# 使用指定的数据库连接
|
148
153
|
config = SQLiteConfig.from_yaml(self.config_path, connection)
|
@@ -159,28 +164,24 @@ class SQLiteServer(ConnectionServer):
|
|
159
164
|
self.log("info", f"执行查询: {sql}")
|
160
165
|
cursor = conn.execute(sql)
|
161
166
|
results = cursor.fetchall()
|
162
|
-
|
163
167
|
columns = [desc[0] for desc in cursor.description]
|
164
168
|
formatted_results = [dict(zip(columns, row)) for row in results]
|
165
169
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
'
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
return [types.TextContent(type="text", text=result_text)]
|
170
|
+
# 处理结果(在连接操作完成后)
|
171
|
+
result_text = json.dumps({
|
172
|
+
'type': 'sqlite',
|
173
|
+
'config_name': config_name,
|
174
|
+
'query_result': {
|
175
|
+
'columns': columns,
|
176
|
+
'rows': formatted_results,
|
177
|
+
'row_count': len(results)
|
178
|
+
}
|
179
|
+
})
|
180
|
+
|
181
|
+
self.log("info", f"查询完成,返回{len(results)}行结果")
|
182
|
+
return [types.TextContent(type="text", text=result_text)]
|
180
183
|
|
181
184
|
except sqlite3.Error as e:
|
182
|
-
# 使用更通用的方法确定配置名称
|
183
|
-
config_name = connection if isinstance(connection, str) else 'default'
|
184
185
|
error_msg = json.dumps({
|
185
186
|
'type': 'sqlite',
|
186
187
|
'config_name': config_name,
|
@@ -188,6 +189,13 @@ class SQLiteServer(ConnectionServer):
|
|
188
189
|
})
|
189
190
|
self.log("error", error_msg)
|
190
191
|
return [types.TextContent(type="text", text=error_msg)]
|
192
|
+
finally:
|
193
|
+
# 确保连接被正确关闭(如果不是使用with语句)
|
194
|
+
if conn and not isinstance(connection, str):
|
195
|
+
try:
|
196
|
+
conn.close()
|
197
|
+
except Exception as e:
|
198
|
+
self.log("warning", f"关闭连接时出错: {str(e)}")
|
191
199
|
|
192
200
|
async def cleanup(self):
|
193
201
|
"""清理资源"""
|
@@ -1,16 +1,17 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-dbutils
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.23.1
|
4
4
|
Summary: MCP Database Utilities Service
|
5
5
|
Author: Dong Hao
|
6
6
|
License-Expression: MIT
|
7
7
|
License-File: LICENSE
|
8
8
|
Requires-Python: >=3.10
|
9
|
-
Requires-Dist: mcp>=1.
|
9
|
+
Requires-Dist: mcp>=1.7.1
|
10
10
|
Requires-Dist: mysql-connector-python>=8.2.0
|
11
11
|
Requires-Dist: psycopg2-binary>=2.9.10
|
12
12
|
Requires-Dist: python-dotenv>=1.0.1
|
13
13
|
Requires-Dist: pyyaml>=6.0.2
|
14
|
+
Requires-Dist: typer>=0.9.0
|
14
15
|
Provides-Extra: test
|
15
16
|
Requires-Dist: aiosqlite>=0.19.0; extra == 'test'
|
16
17
|
Requires-Dist: docker>=7.0.0; extra == 'test'
|
@@ -1,22 +1,22 @@
|
|
1
1
|
mcp_dbutils/__init__.py,sha256=6LLccQv7je2L4IpY_I3OzSJZcK32VUDJv2IY31y6eYg,1900
|
2
|
-
mcp_dbutils/base.py,sha256=
|
2
|
+
mcp_dbutils/base.py,sha256=411bjwscUoUZWqqwLJBo3HWvE44aWPP6Sus216790Lk,42055
|
3
3
|
mcp_dbutils/config.py,sha256=bmXpOd1fyYfoyUS75I035ChT6t3wP5AyEnJ06e2ZS2o,1848
|
4
4
|
mcp_dbutils/log.py,sha256=mqxi6I_IL-MF1F_pxBtnYZQKOHbGBJ74gsvZHVelr1w,823
|
5
5
|
mcp_dbutils/stats.py,sha256=wMqWPfGnEOg9v5YBtTsARV-1YsFUMM_pKdzitzSU9x4,7137
|
6
6
|
mcp_dbutils/mysql/__init__.py,sha256=gNhoHaxK1qhvMAH5AVl1vfV1rUpcbV9KZWUQb41aaQk,129
|
7
7
|
mcp_dbutils/mysql/config.py,sha256=BTPPFqlhoTp7EBFIeLJZh8x6bCn3q9NivHYz9yZHziw,9820
|
8
|
-
mcp_dbutils/mysql/handler.py,sha256=
|
9
|
-
mcp_dbutils/mysql/server.py,sha256=
|
8
|
+
mcp_dbutils/mysql/handler.py,sha256=CJl6Mcs8kloojtS7i-2yD_-InRSmtU09LfYnHDZDhn0,22783
|
9
|
+
mcp_dbutils/mysql/server.py,sha256=4kWkrZby8fZq-Em65Fobs5KDDPJnrOxbsK1zLK1jzp0,9412
|
10
10
|
mcp_dbutils/postgres/__init__.py,sha256=-2zYuEJEQ2AMvmGhH5Z_umerSvt7S4xOa_XV4wgvGfI,154
|
11
11
|
mcp_dbutils/postgres/config.py,sha256=NyQOVhkXJ1S-JD0w-ePNjTKI1Ja-aZQkDUdHi6U7Vl4,7752
|
12
|
-
mcp_dbutils/postgres/handler.py,sha256=
|
13
|
-
mcp_dbutils/postgres/server.py,sha256=
|
12
|
+
mcp_dbutils/postgres/handler.py,sha256=SnzgXjcO3iuvpQ-dzggNOF0bewAKB5BwL3QS11a5qWw,25183
|
13
|
+
mcp_dbutils/postgres/server.py,sha256=qden51sQu5Ht2ibl2jSDyxg7r_N9qDVhg83Pc-q98yc,8887
|
14
14
|
mcp_dbutils/sqlite/__init__.py,sha256=fK_3-WylCBYpBAzwuopi8hlwoIGJm2TPAlwcPWG46I0,134
|
15
15
|
mcp_dbutils/sqlite/config.py,sha256=j67TJ8mQJ2D886MthSa-zYMtvUUYyyxYLMlNxkYoqZE,4509
|
16
|
-
mcp_dbutils/sqlite/handler.py,sha256=
|
17
|
-
mcp_dbutils/sqlite/server.py,sha256=
|
18
|
-
mcp_dbutils-0.
|
19
|
-
mcp_dbutils-0.
|
20
|
-
mcp_dbutils-0.
|
21
|
-
mcp_dbutils-0.
|
22
|
-
mcp_dbutils-0.
|
16
|
+
mcp_dbutils/sqlite/handler.py,sha256=jH0xi_0PxiIBIVnXAGkfNoTVazTr7ol_P1XgBVDRkrU,19781
|
17
|
+
mcp_dbutils/sqlite/server.py,sha256=EBKNKz_wTvChwg6BZlvZIBA1H5mmE2NiNEMOgu_CMy4,7373
|
18
|
+
mcp_dbutils-0.23.1.dist-info/METADATA,sha256=QXF5IwbB0jUrYbZvoYsLeaENPeEBAlKgrIxVFc4jRos,8441
|
19
|
+
mcp_dbutils-0.23.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
20
|
+
mcp_dbutils-0.23.1.dist-info/entry_points.txt,sha256=XTjt0QmYRgKOJQT6skR9bp1EMUfIrgpHeZJPZ3CJffs,49
|
21
|
+
mcp_dbutils-0.23.1.dist-info/licenses/LICENSE,sha256=1A_CwpWVlbjrKdVEYO77vYfnXlW7oxcilZ8FpA_BzCI,1065
|
22
|
+
mcp_dbutils-0.23.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|