kweaver-dolphin 0.1.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.
- DolphinLanguageSDK/__init__.py +58 -0
- dolphin/__init__.py +62 -0
- dolphin/cli/__init__.py +20 -0
- dolphin/cli/args/__init__.py +9 -0
- dolphin/cli/args/parser.py +567 -0
- dolphin/cli/builtin_agents/__init__.py +22 -0
- dolphin/cli/commands/__init__.py +4 -0
- dolphin/cli/interrupt/__init__.py +8 -0
- dolphin/cli/interrupt/handler.py +205 -0
- dolphin/cli/interrupt/keyboard.py +82 -0
- dolphin/cli/main.py +49 -0
- dolphin/cli/multimodal/__init__.py +34 -0
- dolphin/cli/multimodal/clipboard.py +327 -0
- dolphin/cli/multimodal/handler.py +249 -0
- dolphin/cli/multimodal/image_processor.py +214 -0
- dolphin/cli/multimodal/input_parser.py +149 -0
- dolphin/cli/runner/__init__.py +8 -0
- dolphin/cli/runner/runner.py +989 -0
- dolphin/cli/ui/__init__.py +10 -0
- dolphin/cli/ui/console.py +2795 -0
- dolphin/cli/ui/input.py +340 -0
- dolphin/cli/ui/layout.py +425 -0
- dolphin/cli/ui/stream_renderer.py +302 -0
- dolphin/cli/utils/__init__.py +8 -0
- dolphin/cli/utils/helpers.py +135 -0
- dolphin/cli/utils/version.py +49 -0
- dolphin/core/__init__.py +107 -0
- dolphin/core/agent/__init__.py +10 -0
- dolphin/core/agent/agent_state.py +69 -0
- dolphin/core/agent/base_agent.py +970 -0
- dolphin/core/code_block/__init__.py +0 -0
- dolphin/core/code_block/agent_init_block.py +0 -0
- dolphin/core/code_block/assign_block.py +98 -0
- dolphin/core/code_block/basic_code_block.py +1865 -0
- dolphin/core/code_block/explore_block.py +1327 -0
- dolphin/core/code_block/explore_block_v2.py +712 -0
- dolphin/core/code_block/explore_strategy.py +672 -0
- dolphin/core/code_block/judge_block.py +220 -0
- dolphin/core/code_block/prompt_block.py +32 -0
- dolphin/core/code_block/skill_call_deduplicator.py +291 -0
- dolphin/core/code_block/tool_block.py +129 -0
- dolphin/core/common/__init__.py +17 -0
- dolphin/core/common/constants.py +176 -0
- dolphin/core/common/enums.py +1173 -0
- dolphin/core/common/exceptions.py +133 -0
- dolphin/core/common/multimodal.py +539 -0
- dolphin/core/common/object_type.py +165 -0
- dolphin/core/common/output_format.py +432 -0
- dolphin/core/common/types.py +36 -0
- dolphin/core/config/__init__.py +16 -0
- dolphin/core/config/global_config.py +1289 -0
- dolphin/core/config/ontology_config.py +133 -0
- dolphin/core/context/__init__.py +12 -0
- dolphin/core/context/context.py +1580 -0
- dolphin/core/context/context_manager.py +161 -0
- dolphin/core/context/var_output.py +82 -0
- dolphin/core/context/variable_pool.py +356 -0
- dolphin/core/context_engineer/__init__.py +41 -0
- dolphin/core/context_engineer/config/__init__.py +5 -0
- dolphin/core/context_engineer/config/settings.py +402 -0
- dolphin/core/context_engineer/core/__init__.py +7 -0
- dolphin/core/context_engineer/core/budget_manager.py +327 -0
- dolphin/core/context_engineer/core/context_assembler.py +583 -0
- dolphin/core/context_engineer/core/context_manager.py +637 -0
- dolphin/core/context_engineer/core/tokenizer_service.py +260 -0
- dolphin/core/context_engineer/example/incremental_example.py +267 -0
- dolphin/core/context_engineer/example/traditional_example.py +334 -0
- dolphin/core/context_engineer/services/__init__.py +5 -0
- dolphin/core/context_engineer/services/compressor.py +399 -0
- dolphin/core/context_engineer/utils/__init__.py +6 -0
- dolphin/core/context_engineer/utils/context_utils.py +441 -0
- dolphin/core/context_engineer/utils/message_formatter.py +270 -0
- dolphin/core/context_engineer/utils/token_utils.py +139 -0
- dolphin/core/coroutine/__init__.py +15 -0
- dolphin/core/coroutine/context_snapshot.py +154 -0
- dolphin/core/coroutine/context_snapshot_profile.py +922 -0
- dolphin/core/coroutine/context_snapshot_store.py +268 -0
- dolphin/core/coroutine/execution_frame.py +145 -0
- dolphin/core/coroutine/execution_state_registry.py +161 -0
- dolphin/core/coroutine/resume_handle.py +101 -0
- dolphin/core/coroutine/step_result.py +101 -0
- dolphin/core/executor/__init__.py +18 -0
- dolphin/core/executor/debug_controller.py +630 -0
- dolphin/core/executor/dolphin_executor.py +1063 -0
- dolphin/core/executor/executor.py +624 -0
- dolphin/core/flags/__init__.py +27 -0
- dolphin/core/flags/definitions.py +49 -0
- dolphin/core/flags/manager.py +113 -0
- dolphin/core/hook/__init__.py +95 -0
- dolphin/core/hook/expression_evaluator.py +499 -0
- dolphin/core/hook/hook_dispatcher.py +380 -0
- dolphin/core/hook/hook_types.py +248 -0
- dolphin/core/hook/isolated_variable_pool.py +284 -0
- dolphin/core/interfaces.py +53 -0
- dolphin/core/llm/__init__.py +0 -0
- dolphin/core/llm/llm.py +495 -0
- dolphin/core/llm/llm_call.py +100 -0
- dolphin/core/llm/llm_client.py +1285 -0
- dolphin/core/llm/message_sanitizer.py +120 -0
- dolphin/core/logging/__init__.py +20 -0
- dolphin/core/logging/logger.py +526 -0
- dolphin/core/message/__init__.py +8 -0
- dolphin/core/message/compressor.py +749 -0
- dolphin/core/parser/__init__.py +8 -0
- dolphin/core/parser/parser.py +405 -0
- dolphin/core/runtime/__init__.py +10 -0
- dolphin/core/runtime/runtime_graph.py +926 -0
- dolphin/core/runtime/runtime_instance.py +446 -0
- dolphin/core/skill/__init__.py +14 -0
- dolphin/core/skill/context_retention.py +157 -0
- dolphin/core/skill/skill_function.py +686 -0
- dolphin/core/skill/skill_matcher.py +282 -0
- dolphin/core/skill/skillkit.py +700 -0
- dolphin/core/skill/skillset.py +72 -0
- dolphin/core/trajectory/__init__.py +10 -0
- dolphin/core/trajectory/recorder.py +189 -0
- dolphin/core/trajectory/trajectory.py +522 -0
- dolphin/core/utils/__init__.py +9 -0
- dolphin/core/utils/cache_kv.py +212 -0
- dolphin/core/utils/tools.py +340 -0
- dolphin/lib/__init__.py +93 -0
- dolphin/lib/debug/__init__.py +8 -0
- dolphin/lib/debug/visualizer.py +409 -0
- dolphin/lib/memory/__init__.py +28 -0
- dolphin/lib/memory/async_processor.py +220 -0
- dolphin/lib/memory/llm_calls.py +195 -0
- dolphin/lib/memory/manager.py +78 -0
- dolphin/lib/memory/sandbox.py +46 -0
- dolphin/lib/memory/storage.py +245 -0
- dolphin/lib/memory/utils.py +51 -0
- dolphin/lib/ontology/__init__.py +12 -0
- dolphin/lib/ontology/basic/__init__.py +0 -0
- dolphin/lib/ontology/basic/base.py +102 -0
- dolphin/lib/ontology/basic/concept.py +130 -0
- dolphin/lib/ontology/basic/object.py +11 -0
- dolphin/lib/ontology/basic/relation.py +63 -0
- dolphin/lib/ontology/datasource/__init__.py +27 -0
- dolphin/lib/ontology/datasource/datasource.py +66 -0
- dolphin/lib/ontology/datasource/oracle_datasource.py +338 -0
- dolphin/lib/ontology/datasource/sql.py +845 -0
- dolphin/lib/ontology/mapping.py +177 -0
- dolphin/lib/ontology/ontology.py +733 -0
- dolphin/lib/ontology/ontology_context.py +16 -0
- dolphin/lib/ontology/ontology_manager.py +107 -0
- dolphin/lib/skill_results/__init__.py +31 -0
- dolphin/lib/skill_results/cache_backend.py +559 -0
- dolphin/lib/skill_results/result_processor.py +181 -0
- dolphin/lib/skill_results/result_reference.py +179 -0
- dolphin/lib/skill_results/skillkit_hook.py +324 -0
- dolphin/lib/skill_results/strategies.py +328 -0
- dolphin/lib/skill_results/strategy_registry.py +150 -0
- dolphin/lib/skillkits/__init__.py +44 -0
- dolphin/lib/skillkits/agent_skillkit.py +155 -0
- dolphin/lib/skillkits/cognitive_skillkit.py +82 -0
- dolphin/lib/skillkits/env_skillkit.py +250 -0
- dolphin/lib/skillkits/mcp_adapter.py +616 -0
- dolphin/lib/skillkits/mcp_skillkit.py +771 -0
- dolphin/lib/skillkits/memory_skillkit.py +650 -0
- dolphin/lib/skillkits/noop_skillkit.py +31 -0
- dolphin/lib/skillkits/ontology_skillkit.py +89 -0
- dolphin/lib/skillkits/plan_act_skillkit.py +452 -0
- dolphin/lib/skillkits/resource/__init__.py +52 -0
- dolphin/lib/skillkits/resource/models/__init__.py +6 -0
- dolphin/lib/skillkits/resource/models/skill_config.py +109 -0
- dolphin/lib/skillkits/resource/models/skill_meta.py +127 -0
- dolphin/lib/skillkits/resource/resource_skillkit.py +393 -0
- dolphin/lib/skillkits/resource/skill_cache.py +215 -0
- dolphin/lib/skillkits/resource/skill_loader.py +395 -0
- dolphin/lib/skillkits/resource/skill_validator.py +406 -0
- dolphin/lib/skillkits/resource_skillkit.py +11 -0
- dolphin/lib/skillkits/search_skillkit.py +163 -0
- dolphin/lib/skillkits/sql_skillkit.py +274 -0
- dolphin/lib/skillkits/system_skillkit.py +509 -0
- dolphin/lib/skillkits/vm_skillkit.py +65 -0
- dolphin/lib/utils/__init__.py +9 -0
- dolphin/lib/utils/data_process.py +207 -0
- dolphin/lib/utils/handle_progress.py +178 -0
- dolphin/lib/utils/security.py +139 -0
- dolphin/lib/utils/text_retrieval.py +462 -0
- dolphin/lib/vm/__init__.py +11 -0
- dolphin/lib/vm/env_executor.py +895 -0
- dolphin/lib/vm/python_session_manager.py +453 -0
- dolphin/lib/vm/vm.py +610 -0
- dolphin/sdk/__init__.py +60 -0
- dolphin/sdk/agent/__init__.py +12 -0
- dolphin/sdk/agent/agent_factory.py +236 -0
- dolphin/sdk/agent/dolphin_agent.py +1106 -0
- dolphin/sdk/api/__init__.py +4 -0
- dolphin/sdk/runtime/__init__.py +8 -0
- dolphin/sdk/runtime/env.py +363 -0
- dolphin/sdk/skill/__init__.py +10 -0
- dolphin/sdk/skill/global_skills.py +706 -0
- dolphin/sdk/skill/traditional_toolkit.py +260 -0
- kweaver_dolphin-0.1.0.dist-info/METADATA +521 -0
- kweaver_dolphin-0.1.0.dist-info/RECORD +199 -0
- kweaver_dolphin-0.1.0.dist-info/WHEEL +5 -0
- kweaver_dolphin-0.1.0.dist-info/entry_points.txt +27 -0
- kweaver_dolphin-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
- kweaver_dolphin-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
import re
|
|
3
|
+
import datetime
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import create_engine, inspect
|
|
6
|
+
from sqlalchemy.engine import Engine
|
|
7
|
+
|
|
8
|
+
# Add NullPool import
|
|
9
|
+
from sqlalchemy.pool import NullPool
|
|
10
|
+
|
|
11
|
+
from dolphin.core.common.enums import Messages
|
|
12
|
+
from dolphin.lib.ontology.datasource.datasource import DataSourceType
|
|
13
|
+
from dolphin.lib.ontology.datasource.sql import DataSourceSql
|
|
14
|
+
|
|
15
|
+
from dolphin.core.logging.logger import get_logger
|
|
16
|
+
|
|
17
|
+
logger = get_logger("ontology")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _camelCase(s: str) -> str:
|
|
21
|
+
"""Convert a string separated by underscores or spaces to camel case"""
|
|
22
|
+
s = re.sub(r"[_\-]+", " ", s).title().replace(" ", "")
|
|
23
|
+
return s[0].lower() + s[1:] if s else ""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DataSourceOracle(DataSourceSql):
|
|
27
|
+
"""Oracle data source implementation"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, name: str, config: Dict[str, Any]):
|
|
30
|
+
# Pass the correct type DataSourceType.ORACLE
|
|
31
|
+
super().__init__(name, DataSourceType.ORACLE, config)
|
|
32
|
+
self._type = DataSourceType.ORACLE # Store specific types
|
|
33
|
+
# The default Oracle port is 1521
|
|
34
|
+
self.port = config.get("port", 1521)
|
|
35
|
+
|
|
36
|
+
def connect(self) -> Engine:
|
|
37
|
+
"""Connect to Oracle Database"""
|
|
38
|
+
if self._engine:
|
|
39
|
+
logger.debug(f"Already connected to {self.name} , reconnecting")
|
|
40
|
+
self.close()
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
# Using python-oracledb's thin mode, no local Oracle Client required
|
|
44
|
+
import oracledb # Import only when needed
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
# Explicitly disable thick mode to avoid searching for local Client
|
|
48
|
+
oracledb.defaults.thick_mode = False
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
# Build SQLAlchemy connection URL (oracle+oracledb dialect)
|
|
53
|
+
# Prioritize using service_name,then SID;if neither configured, treat database as service_name
|
|
54
|
+
service_name = (
|
|
55
|
+
self.config.get("service_name") or self.config.get("service") or None
|
|
56
|
+
)
|
|
57
|
+
sid = self.config.get("sid")
|
|
58
|
+
|
|
59
|
+
if service_name:
|
|
60
|
+
connection_url = (
|
|
61
|
+
f"oracle+oracledb://{self.username}:{self.password}@{self.host}:{self.port}"
|
|
62
|
+
f"/?service_name={service_name}"
|
|
63
|
+
)
|
|
64
|
+
elif sid:
|
|
65
|
+
connection_url = (
|
|
66
|
+
f"oracle+oracledb://{self.username}:{self.password}@{self.host}:{self.port}"
|
|
67
|
+
f"/?sid={sid}"
|
|
68
|
+
)
|
|
69
|
+
elif self.database:
|
|
70
|
+
connection_url = (
|
|
71
|
+
f"oracle+oracledb://{self.username}:{self.password}@{self.host}:{self.port}"
|
|
72
|
+
f"/?service_name={self.database}"
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
connection_url = f"oracle+oracledb://{self.username}:{self.password}@{self.host}:{self.port}"
|
|
76
|
+
|
|
77
|
+
# Disable connection pooling using NullPool
|
|
78
|
+
self._engine = create_engine(
|
|
79
|
+
connection_url,
|
|
80
|
+
poolclass=NullPool, # Disable connection pooling
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
self._inspector = inspect(self._engine)
|
|
84
|
+
logger.info(f"Successfully connected to Oracle database: {self.name}")
|
|
85
|
+
return self._engine
|
|
86
|
+
except ImportError:
|
|
87
|
+
logger.error(
|
|
88
|
+
f"connect to Oracle database {self.name} failed: missing 'oracledb' library. Please run 'pip install oracledb'"
|
|
89
|
+
)
|
|
90
|
+
self._engine = None
|
|
91
|
+
raise ConnectionError(f"Missing Oracle driver: {self.name}")
|
|
92
|
+
except Exception as e: # Other unexpected errors
|
|
93
|
+
# If available, try to identify the database error types of oracledb
|
|
94
|
+
try:
|
|
95
|
+
import oracledb as _odb
|
|
96
|
+
|
|
97
|
+
if isinstance(e, _odb.Error):
|
|
98
|
+
logger.error(f"connect to Oracle databasefailed {self.name}: {e}")
|
|
99
|
+
self._engine = None
|
|
100
|
+
raise ConnectionError(f"无法connect to到 Oracle database: {self.name}, {e}")
|
|
101
|
+
except Exception:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
logger.error(f"connect to Oracle unknown error occurred {self.name}: {e}")
|
|
105
|
+
self._engine = None
|
|
106
|
+
raise ConnectionError(f"connect to Oracle unknown error occurred: {self.name}, {e}")
|
|
107
|
+
|
|
108
|
+
def test_connection(self) -> bool:
|
|
109
|
+
"""Test Oracle connection, using DUAL for Oracle compatibility.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
bool: True if the connection is successful, False otherwise.
|
|
113
|
+
"""
|
|
114
|
+
originalConnectionState = self._engine
|
|
115
|
+
connToClose = None
|
|
116
|
+
try:
|
|
117
|
+
if not self._engine:
|
|
118
|
+
connToClose = self.connect()
|
|
119
|
+
if not self._engine:
|
|
120
|
+
logger.warning(f"测试connect to {self.name} failed:无法建立connect to")
|
|
121
|
+
return False
|
|
122
|
+
# Oracle requires FROM DUAL
|
|
123
|
+
self.executeQuery("SELECT 1 FROM DUAL", fetchColumns=False)
|
|
124
|
+
logger.info(f"测试connect to {self.name} successful")
|
|
125
|
+
return True
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logger.error(f"测试connect tofailed {self.name}: {e}")
|
|
128
|
+
return False
|
|
129
|
+
finally:
|
|
130
|
+
# If the connection is temporarily established for testing, close it.
|
|
131
|
+
if connToClose and connToClose == self._engine:
|
|
132
|
+
self.close()
|
|
133
|
+
# Restore the original connection status (if there was already a connection before the test)
|
|
134
|
+
elif originalConnectionState and not self._engine:
|
|
135
|
+
self._engine = originalConnectionState
|
|
136
|
+
|
|
137
|
+
def get_schema(self) -> Dict[str, List[Dict[str, str]]]:
|
|
138
|
+
"""Get Oracle database schema information (table name -> list of column information).
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
dict: A mapping of table name to column info list.
|
|
142
|
+
"""
|
|
143
|
+
if self.schema:
|
|
144
|
+
return self.schema
|
|
145
|
+
|
|
146
|
+
conn = self._engine
|
|
147
|
+
should_close_conn = False
|
|
148
|
+
if not conn:
|
|
149
|
+
conn = self.connect() # Try to connect
|
|
150
|
+
if not conn:
|
|
151
|
+
raise ConnectionError(f"无法Get模式,database未connect to: {self.name}")
|
|
152
|
+
should_close_conn = True
|
|
153
|
+
|
|
154
|
+
schema: Dict[str, List[Dict[str, str]]] = {}
|
|
155
|
+
try:
|
|
156
|
+
# Use self._inspector to get the table name, which is more in line with SQLAlchemy's approach
|
|
157
|
+
if not self._inspector: # Ensure inspector exists
|
|
158
|
+
if not conn: # If conn was not successfully established previously
|
|
159
|
+
conn = self.connect()
|
|
160
|
+
if not conn:
|
|
161
|
+
raise ConnectionError(
|
|
162
|
+
f"无法Get模式,database未connect to: {self.name}"
|
|
163
|
+
)
|
|
164
|
+
self._inspector = inspect(conn)
|
|
165
|
+
|
|
166
|
+
tables = self._inspector.get_table_names()
|
|
167
|
+
|
|
168
|
+
# Get column names and types for each table
|
|
169
|
+
for table_name in tables:
|
|
170
|
+
# Use self._inspector to get column information
|
|
171
|
+
columns_info = self._inspector.get_columns(table_name)
|
|
172
|
+
|
|
173
|
+
current_table_cols = []
|
|
174
|
+
for column_data in columns_info:
|
|
175
|
+
# column_data is a dictionary containing keys such as 'name', 'type', 'nullable', 'default', etc.
|
|
176
|
+
# 'type' is usually a SQLAlchemy type object, which needs to be converted to a string.
|
|
177
|
+
col_name = column_data.get("name")
|
|
178
|
+
col_type_obj = column_data.get("type")
|
|
179
|
+
|
|
180
|
+
if col_name and col_type_obj is not None: # Ensure column names and types exist
|
|
181
|
+
# Convert SQLAlchemy type objects to their string representations
|
|
182
|
+
# For example: VARCHAR2(length=50), NUMBER(precision=10, scale=2), DATE
|
|
183
|
+
col_type_str = str(col_type_obj)
|
|
184
|
+
current_table_cols.append(
|
|
185
|
+
{"name": col_name, "type": col_type_str}
|
|
186
|
+
)
|
|
187
|
+
elif col_name: # Unknown type, but column name exists
|
|
188
|
+
current_table_cols.append(
|
|
189
|
+
{"name": col_name, "type": "UNKNOWN"}
|
|
190
|
+
) # Or record a default value
|
|
191
|
+
logger.warning(
|
|
192
|
+
f"Column '{col_name}' in table '{table_name}' has an unknown type."
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if current_table_cols: # Only add to schema when the table has columns
|
|
196
|
+
schema[table_name] = current_table_cols
|
|
197
|
+
else:
|
|
198
|
+
logger.info(
|
|
199
|
+
f"Table '{table_name}' has no columns or columns could not be retrieved."
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
logger.debug(f"Get到 {self.name} schema: {len(schema)} tables")
|
|
203
|
+
self.schema = schema
|
|
204
|
+
return schema
|
|
205
|
+
except Exception as err: # Catch all database-related errors
|
|
206
|
+
logger.error(f"Get Oracle 模式failed {self.name}: {err}")
|
|
207
|
+
# Ensure that if the connection is temporarily open, it will be closed when an error occurs
|
|
208
|
+
if (
|
|
209
|
+
should_close_conn and conn and conn == self._engine
|
|
210
|
+
): # If this connection was specifically opened for this method
|
|
211
|
+
conn.dispose()
|
|
212
|
+
self._engine = None # Reset engine state
|
|
213
|
+
self._inspector = None # Reset inspector
|
|
214
|
+
raise RuntimeError(f"Get Oracle 模式failed: {err}") from err
|
|
215
|
+
finally:
|
|
216
|
+
if should_close_conn and conn:
|
|
217
|
+
if conn == self._engine: # Only close when this method creates self._engine
|
|
218
|
+
conn.dispose()
|
|
219
|
+
self._engine = None
|
|
220
|
+
self._inspector = None # Also clear the inspector
|
|
221
|
+
elif conn != self._engine: # If it's a temporarily created conn and not self._engine
|
|
222
|
+
conn.dispose()
|
|
223
|
+
|
|
224
|
+
def sampleData(self, conceptName: str, count: int = 10) -> Messages:
|
|
225
|
+
"""Retrieve sample data from an Oracle data source for a specified Concept name.
|
|
226
|
+
|
|
227
|
+
It converts the Concept name back to a possible table name (assuming the naming convention used in the scan method),
|
|
228
|
+
then queries that table to retrieve the specified number of sample rows.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
conceptName (str): The name of the Concept for which to retrieve sample data.
|
|
232
|
+
count (int): The number of sample rows to retrieve, defaults to 10.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Messages: A list of dictionaries, each representing a row of data,
|
|
236
|
+
where keys are column names and values are corresponding data.
|
|
237
|
+
Returns an empty list if the Concept is not found or an error occurs.
|
|
238
|
+
"""
|
|
239
|
+
if count <= 0:
|
|
240
|
+
logger.info(
|
|
241
|
+
f"Sample count is {count}, returning empty list for concept '{conceptName}' in {self.name}."
|
|
242
|
+
)
|
|
243
|
+
return []
|
|
244
|
+
|
|
245
|
+
target_table_name: Optional[str] = None
|
|
246
|
+
actual_column_names: List[str] = []
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
db_schema = self.get_schema() # This might connect if not connected.
|
|
250
|
+
if not db_schema:
|
|
251
|
+
logger.warning(
|
|
252
|
+
f"Could not retrieve schema for {self.name} to find concept '{conceptName}'."
|
|
253
|
+
)
|
|
254
|
+
return []
|
|
255
|
+
|
|
256
|
+
for table_name_from_schema, columns_details in db_schema.items():
|
|
257
|
+
# Ensure table_name_from_schema is a string for _camelCase
|
|
258
|
+
if not isinstance(table_name_from_schema, str):
|
|
259
|
+
logger.warning(
|
|
260
|
+
f"Skipping non-string table name in schema: {table_name_from_schema}"
|
|
261
|
+
)
|
|
262
|
+
continue
|
|
263
|
+
|
|
264
|
+
generated_concept_name = _camelCase(table_name_from_schema).capitalize()
|
|
265
|
+
if generated_concept_name == conceptName:
|
|
266
|
+
target_table_name = table_name_from_schema
|
|
267
|
+
actual_column_names = [
|
|
268
|
+
col_info["name"]
|
|
269
|
+
for col_info in columns_details
|
|
270
|
+
if col_info.get("name")
|
|
271
|
+
]
|
|
272
|
+
if not actual_column_names:
|
|
273
|
+
logger.warning(
|
|
274
|
+
f"Concept '{conceptName}' (Table '{target_table_name}') found in {self.name} but has no columns. Cannot sample data."
|
|
275
|
+
)
|
|
276
|
+
return [] # Cannot select data if no columns
|
|
277
|
+
break # Found the table
|
|
278
|
+
|
|
279
|
+
if not target_table_name:
|
|
280
|
+
logger.warning(
|
|
281
|
+
f"Concept '{conceptName}' not found as a discoverable table in datasource '{self.name}'."
|
|
282
|
+
)
|
|
283
|
+
return []
|
|
284
|
+
|
|
285
|
+
# Oracle uses double quotes to quote identifiers, supporting case sensitivity
|
|
286
|
+
quoted_column_names_str = ", ".join(
|
|
287
|
+
[f'"{col}"' for col in actual_column_names]
|
|
288
|
+
)
|
|
289
|
+
# Oracle limits rows using ROWNUM or FETCH FIRST (Oracle 12c+)
|
|
290
|
+
# ROWNUM method with better compatibility is used here
|
|
291
|
+
sql_query = f'SELECT {quoted_column_names_str} FROM "{target_table_name}" WHERE ROWNUM <= {count}'
|
|
292
|
+
|
|
293
|
+
logger.debug(
|
|
294
|
+
f"Executing sample data query for concept '{conceptName}' on {self.name}: {sql_query}"
|
|
295
|
+
)
|
|
296
|
+
query_result = self.executeQuery(
|
|
297
|
+
sql_query
|
|
298
|
+
) # fetchColumns is True by default
|
|
299
|
+
|
|
300
|
+
result_columns = query_result.get("columns", [])
|
|
301
|
+
result_data_rows = query_result.get("data", [])
|
|
302
|
+
|
|
303
|
+
if not result_columns and result_data_rows:
|
|
304
|
+
logger.warning(
|
|
305
|
+
f"Query for concept '{conceptName}' in {self.name} returned data but no column names. This might indicate an issue with executeQuery or the underlying table structure."
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
formatted_samples: Messages = []
|
|
309
|
+
for row_tuple in result_data_rows:
|
|
310
|
+
processed_row = []
|
|
311
|
+
for item in row_tuple:
|
|
312
|
+
if isinstance(
|
|
313
|
+
item, (datetime.datetime, datetime.date, datetime.time)
|
|
314
|
+
):
|
|
315
|
+
processed_row.append(item.isoformat())
|
|
316
|
+
else:
|
|
317
|
+
processed_row.append(item)
|
|
318
|
+
|
|
319
|
+
if len(processed_row) == len(result_columns):
|
|
320
|
+
formatted_samples.append(dict(zip(result_columns, processed_row)))
|
|
321
|
+
else:
|
|
322
|
+
logger.warning(
|
|
323
|
+
f"Row data length mismatch for concept '{conceptName}' in {self.name}. "
|
|
324
|
+
f"Expected {len(result_columns)} columns based on query result, got {len(processed_row)}. Row: {processed_row}"
|
|
325
|
+
)
|
|
326
|
+
return formatted_samples
|
|
327
|
+
|
|
328
|
+
except ConnectionError as ce:
|
|
329
|
+
logger.error(
|
|
330
|
+
f"Connection error while fetching sample data for concept '{conceptName}' from {self.name}: {ce}"
|
|
331
|
+
)
|
|
332
|
+
return []
|
|
333
|
+
except Exception as e:
|
|
334
|
+
logger.error(
|
|
335
|
+
f"Error fetching sample data for concept '{conceptName}' (table: {target_table_name or 'unknown'}) from {self.name}: {e}",
|
|
336
|
+
exc_info=True,
|
|
337
|
+
)
|
|
338
|
+
return []
|