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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agenthink
3
- Version: 0.1.18
3
+ Version: 0.1.20
4
4
  Summary: A unified agent framework for connecting workflows, databases, and agents
5
5
  Author: Ritobroto
6
6
  Requires-Python: >=3.10
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agenthink"
3
- version = "0.1.18"
3
+ version = "0.1.20"
4
4
  description = "A unified agent framework for connecting workflows, databases, and agents"
5
5
  authors = [
6
6
  { name = "Ritobroto" }
@@ -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 the specified database connection.
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
- # try:
256
- cursor = database_connection.cursor()
257
- cursor.execute(query)
258
- results = cursor.fetchall()
259
- logger.info("Executed query on MySQL database '%s': %s", db_name, query)
260
- # return f"{results}"
261
- return f"{cursor}, {results}"
262
- # except Exception as e:
263
- # logger.exception("Failed to execute query on MySQL database '%s': %s", db_name, e)
264
- # return None
265
- # finally:
266
- # if cursor:
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
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agenthink
3
- Version: 0.1.18
3
+ Version: 0.1.20
4
4
  Summary: A unified agent framework for connecting workflows, databases, and agents
5
5
  Author: Ritobroto
6
6
  Requires-Python: >=3.10
File without changes
File without changes