MindsDB 25.5.4.2__py3-none-any.whl → 25.6.3.0__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.

Potentially problematic release.


This version of MindsDB might be problematic. Click here for more details.

Files changed (76) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/api/a2a/agent.py +50 -26
  3. mindsdb/api/a2a/common/server/server.py +32 -26
  4. mindsdb/api/a2a/task_manager.py +68 -6
  5. mindsdb/api/executor/command_executor.py +69 -14
  6. mindsdb/api/executor/datahub/datanodes/integration_datanode.py +49 -65
  7. mindsdb/api/executor/datahub/datanodes/mindsdb_tables.py +91 -84
  8. mindsdb/api/executor/datahub/datanodes/project_datanode.py +29 -48
  9. mindsdb/api/executor/datahub/datanodes/system_tables.py +35 -61
  10. mindsdb/api/executor/planner/plan_join.py +67 -77
  11. mindsdb/api/executor/planner/query_planner.py +176 -155
  12. mindsdb/api/executor/planner/steps.py +37 -12
  13. mindsdb/api/executor/sql_query/result_set.py +45 -64
  14. mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +14 -18
  15. mindsdb/api/executor/sql_query/steps/fetch_dataframe_partition.py +17 -18
  16. mindsdb/api/executor/sql_query/steps/insert_step.py +13 -33
  17. mindsdb/api/executor/sql_query/steps/subselect_step.py +43 -35
  18. mindsdb/api/executor/utilities/sql.py +42 -48
  19. mindsdb/api/http/namespaces/config.py +1 -1
  20. mindsdb/api/http/namespaces/file.py +14 -23
  21. mindsdb/api/http/namespaces/knowledge_bases.py +132 -154
  22. mindsdb/api/mysql/mysql_proxy/data_types/mysql_datum.py +12 -28
  23. mindsdb/api/mysql/mysql_proxy/data_types/mysql_packets/binary_resultset_row_package.py +59 -50
  24. mindsdb/api/mysql/mysql_proxy/data_types/mysql_packets/resultset_row_package.py +9 -8
  25. mindsdb/api/mysql/mysql_proxy/libs/constants/mysql.py +449 -461
  26. mindsdb/api/mysql/mysql_proxy/utilities/dump.py +87 -36
  27. mindsdb/integrations/handlers/bigquery_handler/bigquery_handler.py +219 -28
  28. mindsdb/integrations/handlers/file_handler/file_handler.py +15 -9
  29. mindsdb/integrations/handlers/file_handler/tests/test_file_handler.py +43 -24
  30. mindsdb/integrations/handlers/litellm_handler/litellm_handler.py +10 -3
  31. mindsdb/integrations/handlers/llama_index_handler/requirements.txt +1 -1
  32. mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +29 -33
  33. mindsdb/integrations/handlers/openai_handler/openai_handler.py +277 -356
  34. mindsdb/integrations/handlers/oracle_handler/oracle_handler.py +74 -51
  35. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +305 -98
  36. mindsdb/integrations/handlers/salesforce_handler/salesforce_handler.py +145 -40
  37. mindsdb/integrations/handlers/salesforce_handler/salesforce_tables.py +136 -6
  38. mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +352 -83
  39. mindsdb/integrations/libs/api_handler.py +279 -57
  40. mindsdb/integrations/libs/base.py +185 -30
  41. mindsdb/integrations/utilities/files/file_reader.py +99 -73
  42. mindsdb/integrations/utilities/handler_utils.py +23 -8
  43. mindsdb/integrations/utilities/sql_utils.py +35 -40
  44. mindsdb/interfaces/agents/agents_controller.py +226 -196
  45. mindsdb/interfaces/agents/constants.py +8 -1
  46. mindsdb/interfaces/agents/langchain_agent.py +42 -11
  47. mindsdb/interfaces/agents/mcp_client_agent.py +29 -21
  48. mindsdb/interfaces/agents/mindsdb_database_agent.py +23 -18
  49. mindsdb/interfaces/data_catalog/__init__.py +0 -0
  50. mindsdb/interfaces/data_catalog/base_data_catalog.py +54 -0
  51. mindsdb/interfaces/data_catalog/data_catalog_loader.py +375 -0
  52. mindsdb/interfaces/data_catalog/data_catalog_reader.py +38 -0
  53. mindsdb/interfaces/database/database.py +81 -57
  54. mindsdb/interfaces/database/integrations.py +222 -234
  55. mindsdb/interfaces/database/log.py +72 -104
  56. mindsdb/interfaces/database/projects.py +156 -193
  57. mindsdb/interfaces/file/file_controller.py +21 -65
  58. mindsdb/interfaces/knowledge_base/controller.py +66 -25
  59. mindsdb/interfaces/knowledge_base/evaluate.py +516 -0
  60. mindsdb/interfaces/knowledge_base/llm_client.py +75 -0
  61. mindsdb/interfaces/skills/custom/text2sql/mindsdb_kb_tools.py +83 -43
  62. mindsdb/interfaces/skills/skills_controller.py +31 -36
  63. mindsdb/interfaces/skills/sql_agent.py +113 -86
  64. mindsdb/interfaces/storage/db.py +242 -82
  65. mindsdb/migrations/versions/2025-05-28_a44643042fe8_added_data_catalog_tables.py +118 -0
  66. mindsdb/migrations/versions/2025-06-09_608e376c19a7_updated_data_catalog_data_types.py +58 -0
  67. mindsdb/utilities/config.py +13 -2
  68. mindsdb/utilities/log.py +35 -26
  69. mindsdb/utilities/ml_task_queue/task.py +19 -22
  70. mindsdb/utilities/render/sqlalchemy_render.py +129 -181
  71. mindsdb/utilities/starters.py +40 -0
  72. {mindsdb-25.5.4.2.dist-info → mindsdb-25.6.3.0.dist-info}/METADATA +257 -257
  73. {mindsdb-25.5.4.2.dist-info → mindsdb-25.6.3.0.dist-info}/RECORD +76 -68
  74. {mindsdb-25.5.4.2.dist-info → mindsdb-25.6.3.0.dist-info}/WHEEL +0 -0
  75. {mindsdb-25.5.4.2.dist-info → mindsdb-25.6.3.0.dist-info}/licenses/LICENSE +0 -0
  76. {mindsdb-25.5.4.2.dist-info → mindsdb-25.6.3.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- from typing import Any, List
1
+ from typing import Any, List, Optional
2
2
  import ast as py_ast
3
3
 
4
4
  import pandas as pd
@@ -6,26 +6,29 @@ from mindsdb_sql_parser.ast import ASTNode, Select, Insert, Update, Delete, Star
6
6
  from mindsdb_sql_parser.ast.select.identifier import Identifier
7
7
 
8
8
  from mindsdb.integrations.utilities.sql_utils import (
9
- extract_comparison_conditions, filter_dataframe, sort_dataframe,
10
- FilterCondition, FilterOperator, SortColumn
9
+ extract_comparison_conditions,
10
+ filter_dataframe,
11
+ sort_dataframe,
12
+ FilterCondition,
13
+ FilterOperator,
14
+ SortColumn,
11
15
  )
12
16
  from mindsdb.integrations.libs.base import BaseHandler
13
17
  from mindsdb.integrations.libs.api_handler_exceptions import TableAlreadyExists, TableNotFound
14
18
 
15
- from mindsdb.integrations.libs.response import (
16
- HandlerResponse as Response,
17
- RESPONSE_TYPE
18
- )
19
+ from mindsdb.integrations.libs.response import HandlerResponse as Response, RESPONSE_TYPE
20
+ from mindsdb.utilities import log
19
21
 
20
22
 
21
- class FuncParser:
23
+ logger = log.getLogger("mindsdb")
22
24
 
23
- def from_string(self, query_string):
24
25
 
25
- body = py_ast.parse(query_string.strip(), mode='eval').body
26
+ class FuncParser:
27
+ def from_string(self, query_string):
28
+ body = py_ast.parse(query_string.strip(), mode="eval").body
26
29
 
27
30
  if not isinstance(body, py_ast.Call):
28
- raise RuntimeError(f'Api function not found {query_string}')
31
+ raise RuntimeError(f"Api function not found {query_string}")
29
32
 
30
33
  fnc_name = body.func.id
31
34
 
@@ -39,7 +42,6 @@ class FuncParser:
39
42
  return fnc_name, params
40
43
 
41
44
  def process(self, node):
42
-
43
45
  if isinstance(node, py_ast.List):
44
46
  elements = []
45
47
  for node2 in node.elts:
@@ -47,7 +49,6 @@ class FuncParser:
47
49
  return elements
48
50
 
49
51
  if isinstance(node, py_ast.Dict):
50
-
51
52
  keys = []
52
53
  for node2 in node.keys:
53
54
  if isinstance(node2, py_ast.Constant):
@@ -55,7 +56,7 @@ class FuncParser:
55
56
  elif isinstance(node2, py_ast.Str): # py37
56
57
  value = node2.s
57
58
  else:
58
- raise NotImplementedError(f'Unknown dict key {node2}')
59
+ raise NotImplementedError(f"Unknown dict key {node2}")
59
60
 
60
61
  keys.append(value)
61
62
 
@@ -68,11 +69,11 @@ class FuncParser:
68
69
  if isinstance(node, py_ast.Name):
69
70
  # special attributes
70
71
  name = node.id
71
- if name == 'true':
72
+ if name == "true":
72
73
  return True
73
- elif name == 'false':
74
+ elif name == "false":
74
75
  return False
75
- elif name == 'null':
76
+ elif name == "null":
76
77
  return None
77
78
 
78
79
  if isinstance(node, py_ast.Constant):
@@ -92,11 +93,10 @@ class FuncParser:
92
93
  value = self.process(node.operand)
93
94
  return -value
94
95
 
95
- raise NotImplementedError(f'Unknown node {node}')
96
+ raise NotImplementedError(f"Unknown node {node}")
96
97
 
97
98
 
98
99
  class APITable:
99
-
100
100
  def __init__(self, handler):
101
101
  self.handler = handler
102
102
 
@@ -154,7 +154,6 @@ class APITable:
154
154
 
155
155
 
156
156
  class APIResource(APITable):
157
-
158
157
  def __init__(self, *args, table_name=None, **kwargs):
159
158
  self.table_name = table_name
160
159
  super().__init__(*args, **kwargs)
@@ -179,26 +178,18 @@ class APIResource(APITable):
179
178
  if query.order_by and len(query.order_by) > 0:
180
179
  sort = []
181
180
  for an_order in query.order_by:
182
- sort.append(SortColumn(an_order.field.parts[-1],
183
- an_order.direction.upper() != 'DESC'))
181
+ sort.append(SortColumn(an_order.field.parts[-1], an_order.direction.upper() != "DESC"))
184
182
 
185
183
  targets = []
186
184
  for col in query.targets:
187
185
  if isinstance(col, Identifier):
188
186
  targets.append(col.parts[-1])
189
187
 
190
- kwargs = {
191
- 'conditions': conditions,
192
- 'limit': limit,
193
- 'sort': sort,
194
- 'targets': targets
195
- }
188
+ kwargs = {"conditions": conditions, "limit": limit, "sort": sort, "targets": targets}
196
189
  if self.table_name is not None:
197
- kwargs['table_name'] = self.table_name
190
+ kwargs["table_name"] = self.table_name
198
191
 
199
- result = self.list(
200
- **kwargs
201
- )
192
+ result = self.list(**kwargs)
202
193
 
203
194
  filters = []
204
195
  for cond in conditions:
@@ -216,17 +207,18 @@ class APIResource(APITable):
216
207
  result = sort_dataframe(result, sort_columns)
217
208
 
218
209
  if limit is not None and len(result) > limit:
219
- result = result[:int(limit)]
210
+ result = result[: int(limit)]
220
211
 
221
212
  return result
222
213
 
223
- def list(self,
224
- conditions: List[FilterCondition] = None,
225
- limit: int = None,
226
- sort: List[SortColumn] = None,
227
- targets: List[str] = None,
228
- **kwargs
229
- ):
214
+ def list(
215
+ self,
216
+ conditions: List[FilterCondition] = None,
217
+ limit: int = None,
218
+ sort: List[SortColumn] = None,
219
+ targets: List[str] = None,
220
+ **kwargs,
221
+ ):
230
222
  """
231
223
  List items based on specified conditions, limits, sorting, and targets.
232
224
 
@@ -254,13 +246,10 @@ class APIResource(APITable):
254
246
 
255
247
  columns = [col.name for col in query.columns]
256
248
 
257
- data = [
258
- dict(zip(columns, a_row))
259
- for a_row in query.values
260
- ]
249
+ data = [dict(zip(columns, a_row)) for a_row in query.values]
261
250
  kwargs = {}
262
251
  if self.table_name is not None:
263
- kwargs['table_name'] = self.table_name
252
+ kwargs["table_name"] = self.table_name
264
253
 
265
254
  self.add(data, **kwargs)
266
255
 
@@ -332,10 +321,105 @@ class APIResource(APITable):
332
321
  raise NotImplementedError()
333
322
 
334
323
  def _extract_conditions(self, where: ASTNode) -> List[FilterCondition]:
335
- return [
336
- FilterCondition(i[1], FilterOperator(i[0].upper()), i[2])
337
- for i in extract_comparison_conditions(where)
338
- ]
324
+ return [FilterCondition(i[1], FilterOperator(i[0].upper()), i[2]) for i in extract_comparison_conditions(where)]
325
+
326
+
327
+ class MetaAPIResource(APIResource):
328
+ # TODO: Add a meta_table_info() method in case metadata cannot be retrieved as expected below?
329
+
330
+ def meta_get_tables(self, table_name: str, **kwargs) -> dict:
331
+ """
332
+ Retrieves table metadata for the API resource.
333
+
334
+ Args:
335
+ table_name (str): The name given to the table that represents the API resource. This is required because the name for the APIResource is given by the handler.
336
+ kwargs: Additional keyword arguments that may be used by the specific API resource implementation.
337
+
338
+ Returns:
339
+ Dict: The dictionary should contain the following fields:
340
+ - TABLE_NAME (str): Name of the table.
341
+ - TABLE_TYPE (str): Type of the table, e.g. 'BASE TABLE', 'VIEW', etc. (optional).
342
+ - TABLE_SCHEMA (str): Schema of the table (optional).
343
+ - TABLE_DESCRIPTION (str): Description of the table (optional).
344
+ - ROW_COUNT (int): Estimated number of rows in the table (optional).
345
+ """
346
+ pass
347
+
348
+ def meta_get_columns(self, table_name: str, **kwargs) -> List[dict]:
349
+ """
350
+ Retrieves column metadata for the API resource.
351
+
352
+ Args:
353
+ table_name (str): The name given to the table that represents the API resource. This is required because the name for the APIResource is given by the handler.
354
+ kwargs: Additional keyword arguments that may be used by the specific API resource implementation.
355
+
356
+ Returns:
357
+ List[dict]: The list should contain dictionaries with the following fields:
358
+ - TABLE_NAME (str): Name of the table.
359
+ - COLUMN_NAME (str): Name of the column.
360
+ - DATA_TYPE (str): Data type of the column, e.g. 'VARCHAR', 'INT', etc.
361
+ - COLUMN_DESCRIPTION (str): Description of the column (optional).
362
+ - IS_NULLABLE (bool): Whether the column can contain NULL values (optional).
363
+ - COLUMN_DEFAULT (str): Default value of the column (optional).
364
+ """
365
+ pass
366
+
367
+ def meta_get_column_statistics(self, table_name: str, **kwargs) -> List[dict]:
368
+ """
369
+ Retrieves column statistics for the API resource.
370
+
371
+ Args:
372
+ table_name (str): The name given to the table that represents the API resource. This is required because the name for the APIResource is given by the handler.
373
+ kwargs: Additional keyword arguments that may be used by the specific API resource implementation.
374
+
375
+ Returns:
376
+ List[dict]: The list should contain dictionaries with the following fields:
377
+ - TABLE_NAME (str): Name of the table.
378
+ - COLUMN_NAME (str): Name of the column.
379
+ - MOST_COMMON_VALUES (List[str]): Most common values in the column (optional).
380
+ - MOST_COMMON_FREQUENCIES (List[str]): Frequencies of the most common values in the column (optional).
381
+ - NULL_PERCENTAGE: Percentage of NULL values in the column (optional).
382
+ - MINIMUM_VALUE (str): Minimum value in the column (optional).
383
+ - MAXIMUM_VALUE (str): Maximum value in the column (optional).
384
+ - DISTINCT_VALUES_COUNT (int): Count of distinct values in the column (optional).
385
+ """
386
+ pass
387
+
388
+ def meta_get_primary_keys(self, table_name: str, **kwargs) -> List[dict]:
389
+ """
390
+ Retrieves primary key metadata for the API resource.
391
+
392
+ Args:
393
+ table_name (str): The name given to the table that represents the API resource. This is required because the name for the APIResource is given by the handler.
394
+ kwargs: Additional keyword arguments that may be used by the specific API resource implementation.
395
+
396
+ Returns:
397
+ List[dict]: The list should contain dictionaries with the following fields:
398
+ - TABLE_NAME (str): Name of the table.
399
+ - COLUMN_NAME (str): Name of the column that is part of the primary key.
400
+ - ORDINAL_POSITION (int): Position of the column in the primary key (optional).
401
+ - CONSTRAINT_NAME (str): Name of the primary key constraint (optional).
402
+ """
403
+ pass
404
+
405
+ def meta_get_foreign_keys(self, table_name: str, all_tables: List[str], **kwargs) -> List[dict]:
406
+ """
407
+ Retrieves foreign key metadata for the API resource.
408
+
409
+ Args:
410
+ table_name (str): The name given to the table that represents the API resource. This is required because the name for the APIResource is given by the handler.
411
+ all_tables (List[str]): A list of all table names in the API resource. This is used to identify relationships between tables.
412
+ kwargs: Additional keyword arguments that may be used by the specific API resource implementation.
413
+
414
+ Returns:
415
+ List[dict]: The list should contain dictionaries with the following fields:
416
+ - PARENT_TABLE_NAME (str): Name of the parent table.
417
+ - PARENT_COLUMN_NAME (str): Name of the parent column that is part of the foreign key.
418
+ - CHILD_TABLE_NAME (str): Name of the child table.
419
+ - CHILD_COLUMN_NAME (str): Name of the child column that is part of the foreign key.
420
+ - CONSTRAINT_NAME (str): Name of the foreign key constraint (optional).
421
+ """
422
+ pass
339
423
 
340
424
 
341
425
  class APIHandler(BaseHandler):
@@ -368,14 +452,16 @@ class APIHandler(BaseHandler):
368
452
  """
369
453
  name = name.parts[-1]
370
454
  if name not in self._tables:
371
- raise TableNotFound(f'Table not found: {name}')
455
+ raise TableNotFound(f"Table not found: {name}")
372
456
  return self._tables[name]
373
457
 
374
458
  def query(self, query: ASTNode):
375
-
376
459
  if isinstance(query, Select):
460
+ # If the list method exists, it should be overridden in the child class.
461
+ # The APIResource class could be used as a base class by overriding the select method, but not the list method.
377
462
  table = self._get_table(query.from_table)
378
- if not hasattr(table, 'list'):
463
+ list_method = getattr(table, "list", None)
464
+ if not list_method or (list_method and list_method.__func__ is APIResource.list):
379
465
  # for back compatibility, targets wasn't passed in previous version
380
466
  query.targets = [Star()]
381
467
  result = self._get_table(query.from_table).select(query)
@@ -406,8 +492,8 @@ class APIHandler(BaseHandler):
406
492
 
407
493
  result = self._get_table(Identifier(table_name)).get_columns()
408
494
 
409
- df = pd.DataFrame(result, columns=['Field'])
410
- df['Type'] = 'str'
495
+ df = pd.DataFrame(result, columns=["Field"])
496
+ df["Type"] = "str"
411
497
 
412
498
  return Response(RESPONSE_TYPE.TABLE, df)
413
499
 
@@ -419,14 +505,150 @@ class APIHandler(BaseHandler):
419
505
  """
420
506
  result = list(self._tables.keys())
421
507
 
422
- df = pd.DataFrame(result, columns=['table_name'])
423
- df['table_type'] = 'BASE TABLE'
508
+ df = pd.DataFrame(result, columns=["table_name"])
509
+ df["table_type"] = "BASE TABLE"
424
510
 
425
511
  return Response(RESPONSE_TYPE.TABLE, df)
426
512
 
427
513
 
428
- class APIChatHandler(APIHandler):
514
+ class MetaAPIHandler(APIHandler):
515
+ """
516
+ Base class for handlers associated to the applications APIs (e.g. twitter, slack, discord etc.)
517
+
518
+ This class is used when the handler is also needed to store information in the data catalog.
519
+ """
520
+
521
+ def meta_get_handler_info(self, **kwargs) -> str:
522
+ """
523
+ Retrieves information about the design and implementation of the API handler.
524
+ This should include, but not be limited to, the following:
525
+ - The type of SQL queries and operations that the handler supports.
526
+ - etc.
527
+
528
+ Args:
529
+ kwargs: Additional keyword arguments that may be used in generating the handler information.
530
+
531
+ Returns:
532
+ str: A string containing information about the API handler's design and implementation.
533
+ """
534
+ pass
535
+
536
+ def meta_get_tables(self, table_names: Optional[List[str]] = None, **kwargs) -> Response:
537
+ """
538
+ Retrieves metadata for the specified tables (or all tables if no list is provided).
539
+
540
+ Args:
541
+ table_names (List): A list of table names for which to retrieve metadata.
542
+ kwargs: Additional keyword arguments that may be used by the specific API resource implementation.
429
543
 
544
+ Returns:
545
+ Response: A response object containing the table metadata.
546
+ """
547
+ df = pd.DataFrame()
548
+ for table_name, table_class in self._tables.items():
549
+ if table_names is None or table_name in table_names:
550
+ try:
551
+ if hasattr(table_class, "meta_get_tables"):
552
+ table_metadata = table_class.meta_get_tables(table_name, **kwargs)
553
+ df = pd.concat([df, pd.DataFrame([table_metadata])], ignore_index=True)
554
+ except Exception as e:
555
+ logger.error(f"Error retrieving metadata for table {table_name}: {e}")
556
+
557
+ return Response(RESPONSE_TYPE.TABLE, df)
558
+
559
+ def meta_get_columns(self, table_names: Optional[List[str]] = None, **kwargs) -> Response:
560
+ """
561
+ Retrieves column metadata for the specified tables (or all tables if no list is provided).
562
+
563
+ Args:
564
+ table_names (List): A list of table names for which to retrieve column metadata.
565
+
566
+ Returns:
567
+ Response: A response object containing the column metadata.
568
+ """
569
+ df = pd.DataFrame()
570
+ for table_name, table_class in self._tables.items():
571
+ if table_names is None or table_name in table_names:
572
+ try:
573
+ if hasattr(table_class, "meta_get_columns"):
574
+ column_metadata = table_class.meta_get_columns(table_name, **kwargs)
575
+ df = pd.concat([df, pd.DataFrame(column_metadata)], ignore_index=True)
576
+ except Exception as e:
577
+ logger.error(f"Error retrieving column metadata for table {table_name}: {e}")
578
+
579
+ return Response(RESPONSE_TYPE.TABLE, df)
580
+
581
+ def meta_get_column_statistics(self, table_names: Optional[List[str]] = None, **kwargs) -> Response:
582
+ """
583
+ Retrieves column statistics for the specified tables (or all tables if no list is provided).
584
+
585
+ Args:
586
+ table_names (List): A list of table names for which to retrieve column statistics.
587
+
588
+ Returns:
589
+ Response: A response object containing the column statistics.
590
+ """
591
+ df = pd.DataFrame()
592
+ for table_name, table_class in self._tables.items():
593
+ if table_names is None or table_name in table_names:
594
+ try:
595
+ if hasattr(table_class, "meta_get_column_statistics"):
596
+ column_statistics = table_class.meta_get_column_statistics(table_name, **kwargs)
597
+ df = pd.concat([df, pd.DataFrame(column_statistics)], ignore_index=True)
598
+ except Exception as e:
599
+ logger.error(f"Error retrieving column statistics for table {table_name}: {e}")
600
+
601
+ return Response(RESPONSE_TYPE.TABLE, df)
602
+
603
+ def meta_get_primary_keys(self, table_names: Optional[List[str]] = None, **kwargs) -> Response:
604
+ """
605
+ Retrieves primary key metadata for the specified tables (or all tables if no list is provided).
606
+
607
+ Args:
608
+ table_names (List): A list of table names for which to retrieve primary key metadata.
609
+
610
+ Returns:
611
+ Response: A response object containing the primary key metadata.
612
+ """
613
+ df = pd.DataFrame()
614
+ for table_name, table_class in self._tables.items():
615
+ if table_names is None or table_name in table_names:
616
+ try:
617
+ if hasattr(table_class, "meta_get_primary_keys"):
618
+ primary_key_metadata = table_class.meta_get_primary_keys(table_name, **kwargs)
619
+ df = pd.concat([df, pd.DataFrame(primary_key_metadata)], ignore_index=True)
620
+ except Exception as e:
621
+ logger.error(f"Error retrieving primary keys for table {table_name}: {e}")
622
+
623
+ return Response(RESPONSE_TYPE.TABLE, df)
624
+
625
+ def meta_get_foreign_keys(self, table_names: Optional[List[str]] = None, **kwargs) -> Response:
626
+ """
627
+ Retrieves foreign key metadata for the specified tables (or all tables if no list is provided).
628
+
629
+ Args:
630
+ table_names (List): A list of table names for which to retrieve foreign key metadata.
631
+
632
+ Returns:
633
+ Response: A response object containing the foreign key metadata.
634
+ """
635
+ df = pd.DataFrame()
636
+ all_tables = list(self._tables.keys())
637
+ for table_name, table_class in self._tables.items():
638
+ if table_names is None or table_name in table_names:
639
+ try:
640
+ if hasattr(table_class, "meta_get_foreign_keys"):
641
+ foreign_key_metadata = table_class.meta_get_foreign_keys(
642
+ table_name, all_tables=table_names if table_names else all_tables, **kwargs
643
+ )
644
+ df = pd.concat([df, pd.DataFrame(foreign_key_metadata)], ignore_index=True)
645
+ except Exception as e:
646
+ logger.error(f"Error retrieving foreign keys for table {table_name}: {e}")
647
+
648
+ return Response(RESPONSE_TYPE.TABLE, df)
649
+
650
+
651
+ class APIChatHandler(APIHandler):
430
652
  def get_chat_config(self):
431
653
  """Return configuration to connect to chatbot
432
654