agenthink 0.1.18__tar.gz → 0.1.20__tar.gz
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.
- {agenthink-0.1.18 → agenthink-0.1.20}/PKG-INFO +1 -1
- {agenthink-0.1.18 → agenthink-0.1.20}/pyproject.toml +1 -1
- {agenthink-0.1.18 → agenthink-0.1.20}/src/agenthink/connection.py +242 -25
- {agenthink-0.1.18 → agenthink-0.1.20}/src/agenthink.egg-info/PKG-INFO +1 -1
- {agenthink-0.1.18 → agenthink-0.1.20}/README.md +0 -0
- {agenthink-0.1.18 → agenthink-0.1.20}/setup.cfg +0 -0
- {agenthink-0.1.18 → agenthink-0.1.20}/src/agenthink/__init__.py +0 -0
- {agenthink-0.1.18 → agenthink-0.1.20}/src/agenthink/models.py +0 -0
- {agenthink-0.1.18 → agenthink-0.1.20}/src/agenthink/utils.py +0 -0
- {agenthink-0.1.18 → agenthink-0.1.20}/src/agenthink.egg-info/SOURCES.txt +0 -0
- {agenthink-0.1.18 → agenthink-0.1.20}/src/agenthink.egg-info/dependency_links.txt +0 -0
- {agenthink-0.1.18 → agenthink-0.1.20}/src/agenthink.egg-info/requires.txt +0 -0
- {agenthink-0.1.18 → agenthink-0.1.20}/src/agenthink.egg-info/top_level.txt +0 -0
|
@@ -20,15 +20,6 @@ import logging
|
|
|
20
20
|
import re
|
|
21
21
|
dotenv.load_dotenv()
|
|
22
22
|
|
|
23
|
-
# Create and configure logger
|
|
24
|
-
# logging.basicConfig(
|
|
25
|
-
# filename="datastore_library.log",
|
|
26
|
-
# format='%(asctime)s %(levelname)s:%(name)s:%(message)s',
|
|
27
|
-
# filemode='w'
|
|
28
|
-
# )
|
|
29
|
-
# logger = logging.getLogger(__name__)
|
|
30
|
-
# logger.setLevel(logging.DEBUG)
|
|
31
|
-
|
|
32
23
|
logger = logging.getLogger(__name__)
|
|
33
24
|
logger.addHandler(logging.NullHandler())
|
|
34
25
|
|
|
@@ -223,14 +214,34 @@ class DBConnector:
|
|
|
223
214
|
return f"Database: {db_name}, Connection Object: {conn}"
|
|
224
215
|
|
|
225
216
|
def execute_query(self, db_name: str, query: str):
|
|
217
|
+
|
|
226
218
|
"""
|
|
227
|
-
Execute a query on
|
|
219
|
+
Execute a read-only SQL query on a MySQL or MSSQL database.
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
db_name : str
|
|
224
|
+
Name of the database connection.
|
|
225
|
+
query : str
|
|
226
|
+
Read-only SQL query to execute (SELECT / WITH / SHOW / DESCRIBE / EXPLAIN).
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
Any or None
|
|
231
|
+
Query results if successful, otherwise None.
|
|
232
|
+
|
|
233
|
+
Notes
|
|
234
|
+
-----
|
|
235
|
+
- Only read-only queries are allowed and enforced by prefix checks.
|
|
236
|
+
- Write or mutating queries are blocked.
|
|
237
|
+
- Errors are logged and cursors are safely closed.
|
|
238
|
+
- Behavior may differ slightly between MySQL and MSSQL drivers.
|
|
228
239
|
"""
|
|
240
|
+
|
|
229
241
|
if db_name not in self.connection_object_dict:
|
|
230
242
|
logger.error("No connection found for database: %s", db_name)
|
|
231
243
|
return None
|
|
232
244
|
|
|
233
|
-
|
|
234
245
|
database_dict = self.connection_object_dict[db_name]
|
|
235
246
|
database_type = database_dict.get("database_type")
|
|
236
247
|
database_connection = database_dict.get("connection_object")
|
|
@@ -252,19 +263,18 @@ class DBConnector:
|
|
|
252
263
|
return None
|
|
253
264
|
|
|
254
265
|
if database_type == "mysql":
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
# cursor.close()
|
|
266
|
+
try:
|
|
267
|
+
cursor = database_connection.cursor()
|
|
268
|
+
cursor.execute(query)
|
|
269
|
+
results = cursor.fetchall()
|
|
270
|
+
logger.info("Executed query on MySQL database '%s': %s", db_name, query)
|
|
271
|
+
return f"{results}"
|
|
272
|
+
except Exception as e:
|
|
273
|
+
logger.exception("Failed to execute query on MySQL database '%s': %s", db_name, e)
|
|
274
|
+
return None
|
|
275
|
+
finally:
|
|
276
|
+
if cursor:
|
|
277
|
+
cursor.close()
|
|
268
278
|
elif database_type == "mssql":
|
|
269
279
|
try:
|
|
270
280
|
cursor = database_connection.cursor()
|
|
@@ -283,6 +293,211 @@ class DBConnector:
|
|
|
283
293
|
else:
|
|
284
294
|
logger.error("Unsupported database type '%s' for database '%s'", database_type, db_name)
|
|
285
295
|
return None
|
|
296
|
+
|
|
297
|
+
def display_tables(self,db_name:str):
|
|
298
|
+
"""
|
|
299
|
+
Retrieve a list of user tables from a MySQL or MSSQL database.
|
|
300
|
+
|
|
301
|
+
Parameters
|
|
302
|
+
----------
|
|
303
|
+
db_name : str
|
|
304
|
+
Name of the database connection.
|
|
305
|
+
|
|
306
|
+
Returns
|
|
307
|
+
-------
|
|
308
|
+
list[str] or None
|
|
309
|
+
List of table names if successful, otherwise None.
|
|
310
|
+
|
|
311
|
+
Notes
|
|
312
|
+
-----
|
|
313
|
+
- Uses `SHOW TABLES` for MySQL.
|
|
314
|
+
- Uses `INFORMATION_SCHEMA.TABLES` for MSSQL.
|
|
315
|
+
- Errors are logged and not raised.
|
|
316
|
+
"""
|
|
317
|
+
database_dict = self.connection_object_dict[db_name]
|
|
318
|
+
database_type = database_dict.get("database_type")
|
|
319
|
+
database_connection = database_dict.get("connection_object")
|
|
320
|
+
if database_type == "mysql":
|
|
321
|
+
try:
|
|
322
|
+
cursor = database_connection.cursor()
|
|
323
|
+
cursor.execute("SHOW TABLES")
|
|
324
|
+
tables = cursor.fetchall()
|
|
325
|
+
table_list = [table[0] for table in tables]
|
|
326
|
+
return table_list
|
|
327
|
+
except Exception as e:
|
|
328
|
+
logger.exception("Failed to retrieve tables from MySQL database '%s': %s", db_name, e)
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
if database_type == "mssql":
|
|
332
|
+
try:
|
|
333
|
+
cursor = database_connection.cursor()
|
|
334
|
+
cursor.execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")
|
|
335
|
+
tables = cursor.fetchall()
|
|
336
|
+
table_list = [table[0] for table in tables]
|
|
337
|
+
return table_list
|
|
338
|
+
except Exception as e:
|
|
339
|
+
logger.exception("Failed to retrieve tables from MSSQL database '%s': %s", db_name, e)
|
|
340
|
+
return None
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def insert_data(self,db_name:str,table_name:str,data:dict):
|
|
344
|
+
"""
|
|
345
|
+
Insert a single row into a MySQL or MSSQL table.
|
|
346
|
+
|
|
347
|
+
Parameters
|
|
348
|
+
----------
|
|
349
|
+
db_name : str
|
|
350
|
+
Name of the database connection.
|
|
351
|
+
table_name : str
|
|
352
|
+
Target table name (must be trusted).
|
|
353
|
+
data : dict
|
|
354
|
+
Column-to-value mapping for the row to insert.
|
|
355
|
+
|
|
356
|
+
Notes
|
|
357
|
+
-----
|
|
358
|
+
- Uses `%s` placeholders for MySQL and `?` for MSSQL.
|
|
359
|
+
- Commits the transaction on success and logs the result.
|
|
360
|
+
- Table and column names are not parameterized and must be validated.
|
|
361
|
+
"""
|
|
362
|
+
database_dict = self.connection_object_dict[db_name]
|
|
363
|
+
database_type = database_dict.get("database_type")
|
|
364
|
+
database_connection = database_dict.get("connection_object")
|
|
365
|
+
columns = ', '.join(data.keys())
|
|
366
|
+
placeholders = ', '.join(['%s'] * len(data))
|
|
367
|
+
values = tuple(data.values())
|
|
368
|
+
if database_type == "mysql":
|
|
369
|
+
try:
|
|
370
|
+
cursor = database_connection.cursor()
|
|
371
|
+
sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
|
|
372
|
+
cursor.execute(sql, values)
|
|
373
|
+
database_connection.commit()
|
|
374
|
+
cursor.close()
|
|
375
|
+
logger.debug("Data inserted into MySQL database '%s', table '%s'", db_name, table_name)
|
|
376
|
+
except Exception as e:
|
|
377
|
+
logger.exception("Failed to insert data into MySQL database '%s', table '%s': %s", db_name, table_name, e)
|
|
378
|
+
if database_type == "mssql":
|
|
379
|
+
try:
|
|
380
|
+
cursor = database_connection.cursor()
|
|
381
|
+
columns_sql = ", ".join(f"[{c}]" for c in data.keys())
|
|
382
|
+
placeholders = ", ".join("?" for _ in data)
|
|
383
|
+
values = tuple(data.values())
|
|
384
|
+
|
|
385
|
+
sql = f"INSERT INTO [{table_name}] ({columns_sql}) VALUES ({placeholders})"
|
|
386
|
+
|
|
387
|
+
cursor.execute(sql, values)
|
|
388
|
+
database_connection.commit()
|
|
389
|
+
|
|
390
|
+
logger.info(
|
|
391
|
+
"Data inserted into MSSQL database '%s', table '%s'",
|
|
392
|
+
db_name,
|
|
393
|
+
table_name
|
|
394
|
+
)
|
|
395
|
+
cursor.close()
|
|
396
|
+
except Exception as e:
|
|
397
|
+
logger.exception("Insert failed for %s database '%s', table '%s'", database_type, db_name, table_name)
|
|
398
|
+
|
|
399
|
+
def get_data(self, db_name: str, table_name: str, num_rows: int = 5):
|
|
400
|
+
"""
|
|
401
|
+
Retrieve a limited number of rows from a table for display.
|
|
402
|
+
|
|
403
|
+
Parameters
|
|
404
|
+
----------
|
|
405
|
+
db_name : str
|
|
406
|
+
Name of the database connection.
|
|
407
|
+
table_name : str
|
|
408
|
+
Target table name (must be trusted).
|
|
409
|
+
num_rows : int, optional
|
|
410
|
+
Number of rows to retrieve (default is 5).
|
|
411
|
+
|
|
412
|
+
Returns
|
|
413
|
+
-------
|
|
414
|
+
list[tuple] or None
|
|
415
|
+
Retrieved rows if successful, otherwise None.
|
|
416
|
+
"""
|
|
417
|
+
db = self.connection_object_dict[db_name]
|
|
418
|
+
db_type = db["database_type"]
|
|
419
|
+
conn = db["connection_object"]
|
|
420
|
+
|
|
421
|
+
cursor = conn.cursor()
|
|
422
|
+
try:
|
|
423
|
+
if db_type == "mysql":
|
|
424
|
+
sql = f"SELECT * FROM {table_name} LIMIT %s"
|
|
425
|
+
cursor.execute(sql, (num_rows,))
|
|
426
|
+
elif db_type == "mssql":
|
|
427
|
+
sql = f"SELECT TOP ({num_rows}) * FROM [{table_name}]"
|
|
428
|
+
cursor.execute(sql)
|
|
429
|
+
else:
|
|
430
|
+
return None
|
|
431
|
+
|
|
432
|
+
return cursor.fetchall()
|
|
433
|
+
except Exception:
|
|
434
|
+
logger.exception(
|
|
435
|
+
"Failed to retrieve data from %s database '%s', table '%s'",
|
|
436
|
+
db_type, db_name, table_name
|
|
437
|
+
)
|
|
438
|
+
return None
|
|
439
|
+
finally:
|
|
440
|
+
cursor.close()
|
|
441
|
+
|
|
442
|
+
def get_schema(self, db_name: str, table_name: str):
|
|
443
|
+
"""
|
|
444
|
+
Retrieve column metadata for a table from a database.
|
|
445
|
+
|
|
446
|
+
Parameters
|
|
447
|
+
----------
|
|
448
|
+
db_name : str
|
|
449
|
+
Name of the database connection.
|
|
450
|
+
table_name : str
|
|
451
|
+
Target table name (must be trusted).
|
|
452
|
+
|
|
453
|
+
Returns
|
|
454
|
+
-------
|
|
455
|
+
list[tuple] or None
|
|
456
|
+
List of (column_name, data_type, is_nullable) ordered by column position,
|
|
457
|
+
or None if the schema cannot be retrieved.
|
|
458
|
+
|
|
459
|
+
Notes
|
|
460
|
+
-----
|
|
461
|
+
- Uses INFORMATION_SCHEMA for portability.
|
|
462
|
+
- Supports MySQL and MSSQL.
|
|
463
|
+
- Errors are logged and not raised.
|
|
464
|
+
"""
|
|
465
|
+
|
|
466
|
+
db = self.connection_object_dict[db_name]
|
|
467
|
+
db_type = db["database_type"]
|
|
468
|
+
conn = db["connection_object"]
|
|
469
|
+
|
|
470
|
+
cursor = conn.cursor()
|
|
471
|
+
try:
|
|
472
|
+
if db_type == "mysql":
|
|
473
|
+
sql = """
|
|
474
|
+
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE
|
|
475
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
476
|
+
WHERE TABLE_SCHEMA = DATABASE()
|
|
477
|
+
AND TABLE_NAME = %s
|
|
478
|
+
ORDER BY ORDINAL_POSITION
|
|
479
|
+
"""
|
|
480
|
+
cursor.execute(sql, (table_name,))
|
|
481
|
+
result = cursor.fetchall()
|
|
482
|
+
if db_type == "mssql":
|
|
483
|
+
sql = """
|
|
484
|
+
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE
|
|
485
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
486
|
+
WHERE TABLE_NAME = ?
|
|
487
|
+
ORDER BY ORDINAL_POSITION
|
|
488
|
+
"""
|
|
489
|
+
cursor.execute(sql, (table_name,))
|
|
490
|
+
result = cursor.fetchall()
|
|
491
|
+
except Exception:
|
|
492
|
+
logger.exception(
|
|
493
|
+
"Failed to retrieve schema from %s database '%s', table '%s'",
|
|
494
|
+
db_type, db_name, table_name
|
|
495
|
+
)
|
|
496
|
+
return None
|
|
497
|
+
finally:
|
|
498
|
+
cursor.close()
|
|
499
|
+
return result
|
|
500
|
+
|
|
286
501
|
|
|
287
502
|
def _debugging_function(self):
|
|
288
503
|
prefix = f"{self._workflow_id}/{self._user_id}/"
|
|
@@ -300,4 +515,6 @@ class DBConnector:
|
|
|
300
515
|
Number of datastores: {self.no_of_datastores}\n
|
|
301
516
|
Current connection_object_dict: {self.connection_object_dict}\n
|
|
302
517
|
"""
|
|
303
|
-
return output_message
|
|
518
|
+
return output_message
|
|
519
|
+
|
|
520
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|