Skip to content
This repository was archived by the owner on Dec 1, 2025. It is now read-only.

Commit 3b3d078

Browse files
committed
Merge branch 'schemabased'
2 parents 704672e + ef8a621 commit 3b3d078

16 files changed

Lines changed: 264 additions & 358 deletions

src/data_neuron/api/claude_api.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ def call_claude_vision_api_with_pagination(query: str, image_path: str, include_
7878
api_key = get_api_key()
7979
headers = get_headers(api_key)
8080

81-
print("cluade...vision")
8281
mime_type, image_data = convert_to_base64(image_path)
8382

8483
full_response = ""

src/data_neuron/context_init_cmd/main.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import os
2-
import click
32
from .table_operations import get_table_list, choose_tables, generate_yaml_for_table
43
from .yaml_generator import generate_definitions_and_relationships
54
from ..utils.print import print_header, print_info, print_success, print_warning, print_prompt
@@ -9,40 +8,48 @@
98
@handle_database_errors
109
def init_context():
1110
print_header("Setting up the context(semantic layer) for your database..")
12-
1311
print_warning(
1412
"This will create a folder context in the current directory. And override if there is an existing file")
15-
1613
print_prompt("You can edit it anytime...")
17-
1814
print_warning("Please choose a set of 10 or lesser tables..\n")
19-
2015
print_info("🗄️ Fetching tables from the database")
16+
2117
db_type, all_tables = get_table_list()
18+
2219
chosen_tables = choose_tables(all_tables)
2320

2421
os.makedirs('context/tables', exist_ok=True)
2522

26-
for table in chosen_tables:
27-
yaml_content = generate_yaml_for_table(table)
28-
with open(f'context/tables/{table}.yaml', 'w') as f:
23+
for table_info in chosen_tables:
24+
if isinstance(table_info, dict):
25+
schema = table_info['schema']
26+
table = table_info['table']
27+
else:
28+
schema = 'main' # default schema
29+
table = table_info
30+
31+
yaml_content = generate_yaml_for_table(schema, table)
32+
with open(f'context/tables/{schema}___{table}.yaml', 'w') as f:
2933
f.write(yaml_content)
3034
print("\n")
31-
print_success(f"Generated YAML for table: {table}")
35+
print_success(f"Generated YAML for table: {schema}.{table}")
3236

3337
print_info("Generating definitions and relationships...")
34-
definitions_yaml, relationships_yaml = generate_definitions_and_relationships(
35-
chosen_tables, db_type)
38+
try:
39+
definitions_yaml, relationships_yaml = generate_definitions_and_relationships(
40+
chosen_tables, db_type)
3641

37-
with open('context/definitions.yaml', 'w') as f:
38-
f.write(definitions_yaml)
39-
print_success("Generated definitions.yaml")
42+
with open('context/definitions.yaml', 'w') as f:
43+
f.write(definitions_yaml)
44+
print_success("Generated definitions.yaml")
4045

41-
with open('context/relationships.yaml', 'w') as f:
42-
f.write(relationships_yaml)
43-
print_success("Generated relationships.yaml")
46+
with open('context/relationships.yaml', 'w') as f:
47+
f.write(relationships_yaml)
48+
print_success("Generated relationships.yaml")
4449

45-
print_success("Initialization complete!")
50+
print_success("Initialization complete!")
51+
except Exception as e:
52+
print_warning(f"An error occurred: {str(e)}")
4653

4754

4855
if __name__ == '__main__':

src/data_neuron/context_init_cmd/table_operations.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
21
import click
32
from ..db_operations.factory import DatabaseFactory
43

54

65
def get_table_list():
76
db = DatabaseFactory.get_database()
8-
res = db.get_table_list()
9-
return db.db_type, res
7+
return db.db_type, db.get_table_list()
108

119

1210
def choose_tables(all_tables):
@@ -15,8 +13,8 @@ def choose_tables(all_tables):
1513
batch = all_tables[i:i+10]
1614
click.echo(
1715
"Choose tables (enter numbers separated by commas, or 'all' for all, 'skip' for next batch, 'done' to finish):")
18-
for idx, table in enumerate(batch, start=1):
19-
click.echo(f"{idx}. {table}")
16+
for idx, table_info in enumerate(batch, start=1):
17+
click.echo(f"{idx}. {table_info['schema']}.{table_info['table']}")
2018
choice = click.prompt("Your choice").lower()
2119
if choice == 'all':
2220
chosen_tables.extend(batch)
@@ -31,9 +29,9 @@ def choose_tables(all_tables):
3129
return chosen_tables
3230

3331

34-
def generate_yaml_for_table(table_name):
32+
def generate_yaml_for_table(schema, table):
3533
db = DatabaseFactory.get_database()
36-
table_info = db.get_table_info(table_name)
34+
table_info = db.get_table_info(schema, table)
3735

3836
from ..prompts.yaml_generation_prompt import table_yaml_prompt
3937
from ..api.main import stream_neuron_api

src/data_neuron/context_init_cmd/yaml_generator.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,22 @@
33

44

55
def generate_definitions_and_relationships(tables, db_type):
6-
prompt = definitions_relationships_prompt(tables, db_type)
6+
# Check if tables is a list of dictionaries or a list of strings
7+
if tables and isinstance(tables[0], dict):
8+
table_names = [
9+
f"{table['schema']}.{table['table']}" for table in tables]
10+
elif tables and isinstance(tables[0], str):
11+
table_names = tables
12+
else:
13+
raise ValueError("Unexpected table format")
14+
15+
prompt = definitions_relationships_prompt(table_names, db_type)
716
system_prompt = "You are a helpful assistant that generates YAML content for database definitions and relationships."
817

918
yaml_content = ""
1019
for chunk in stream_neuron_api(prompt, instruction_prompt=system_prompt):
1120
yaml_content += chunk
1221

22+
# Split the YAML content into definitions and relationships
1323
definitions_yaml, relationships_yaml = yaml_content.split('---')
1424
return definitions_yaml.strip(), relationships_yaml.strip()

src/data_neuron/context_loader.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,32 @@ def load_context():
1717
tables_path = os.path.join('context', 'tables')
1818
for filename in os.listdir(tables_path):
1919
if filename.endswith('.yaml'):
20-
with open(os.path.join(tables_path, filename), 'r') as f:
21-
table_data = yaml.safe_load(f)
22-
context['tables'][table_data['table_name']] = table_data
20+
file_path = os.path.join(tables_path, filename)
21+
if os.path.getsize(file_path) > 0: # Check if file is not empty
22+
with open(file_path, 'r') as f:
23+
table_data = yaml.safe_load(f)
24+
if table_data and isinstance(table_data, dict):
25+
full_name = table_data.get('full_name')
26+
if full_name:
27+
context['tables'][full_name] = table_data
28+
else:
29+
print(
30+
f"Warning: 'full_name' not found in {filename}. Skipping this table.")
31+
else:
32+
print(
33+
f"Warning: Invalid or empty YAML content in {filename}. Skipping this table.")
2334

2435
# Load relationships
25-
with open(os.path.join('context', 'relationships.yaml'), 'r') as f:
26-
context['relationships'] = yaml.safe_load(f)
36+
relationships_path = os.path.join('context', 'relationships.yaml')
37+
if os.path.exists(relationships_path):
38+
with open(relationships_path, 'r') as f:
39+
context['relationships'] = yaml.safe_load(f)
2740

2841
# Load global definitions
29-
with open(os.path.join('context', 'definitions.yaml'), 'r') as f:
30-
context['global_definitions'] = yaml.safe_load(f)
42+
definitions_path = os.path.join('context', 'definitions.yaml')
43+
if os.path.exists(definitions_path):
44+
with open(definitions_path, 'r') as f:
45+
context['global_definitions'] = yaml.safe_load(f)
3146

3247
# Load database configuration
3348
if not os.path.exists(CONFIG_PATH):
@@ -41,7 +56,7 @@ def load_context():
4156
if not db_config:
4257
raise ConfigurationError(
4358
"No database configuration found in the YAML file.")
44-
# careful this loads into context,
59+
# IMPORTANT not to send db_config itself
4560
context['database'] = db_config.get('name')
4661
except yaml.YAMLError as e:
4762
raise ConfigurationError(f"Error parsing YAML configuration: {str(e)}")

src/data_neuron/db_operations/base.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,13 @@ def get_table_list(self):
1111
pass
1212

1313
@abstractmethod
14-
def get_table_info(self, table_name):
14+
def get_table_info(self, schema, table_name):
1515
pass
1616

1717
@abstractmethod
1818
def execute_query(self, query: str) -> str:
1919
pass
2020

21-
@abstractmethod
22-
def get_schema_info(self) -> str:
23-
pass
24-
25-
@abstractmethod
2621
def execute_query_with_column_names(self, query: str) -> Tuple[List[Tuple], List[str]]:
2722
pass
2823

src/data_neuron/db_operations/clickhouse.py

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .base import DatabaseOperations
22
from .exceptions import ConnectionError, OperationError
3-
from typing import List, Tuple
3+
from typing import List, Tuple, Dict, Any
44

55

66
class ClickHouseOperations(DatabaseOperations):
@@ -27,26 +27,29 @@ def _get_connection(self):
2727
raise ConnectionError(
2828
f"Failed to connect to ClickHouse database: {str(e)}")
2929

30-
def get_table_list(self) -> List[str]:
30+
def get_table_list(self) -> List[Dict[str, str]]:
3131
try:
3232
client = self._get_connection()
3333
result = client.query("SHOW TABLES")
34-
return [row[0] for row in result.result_rows]
34+
return [{"schema": self.conn_params.get('database', 'default'), "table": table[0]} for table in result.result_rows]
3535
except Exception as e:
3636
raise OperationError(f"Failed to get table list: {str(e)}")
3737

38-
def get_table_info(self, table_name: str) -> dict:
38+
def get_table_info(self, schema: str, table: str) -> Dict[str, Any]:
3939
try:
4040
client = self._get_connection()
41-
result = client.query(f"DESCRIBE {table_name}")
41+
result = client.query(f"DESCRIBE {schema}.{table}")
4242
return {
43-
'table_name': table_name,
43+
'schema': schema,
44+
'table_name': table,
45+
'full_name': f"{schema}.{table}",
4446
'columns': [
4547
{
4648
'name': col[0],
4749
'type': col[1],
4850
'nullable': col[5] == 'YES',
49-
'primary_key': col[6] == 'true'
51+
# ClickHouse uses 'key' to indicate primary key columns
52+
'primary_key': col[2] == 'key'
5053
} for col in result.result_rows
5154
]
5255
}
@@ -61,24 +64,10 @@ def execute_query_with_column_names(self, query: str) -> Tuple[List[Tuple], List
6164
except Exception as e:
6265
raise OperationError(f"Failed to execute query: {str(e)}")
6366

64-
def execute_query(self, query: str) -> str:
67+
def execute_query(self, query: str) -> List[Tuple]:
6568
try:
6669
client = self._get_connection()
6770
result = client.query(query)
6871
return result.result_rows
6972
except Exception as e:
7073
raise OperationError(f"Failed to execute query: {str(e)}")
71-
72-
def get_schema_info(self) -> str:
73-
try:
74-
client = self._get_connection()
75-
tables = self.get_table_list()
76-
schema_info = []
77-
for table in tables:
78-
schema_info.append(f"\nTable: {table}")
79-
columns = client.query(f"DESCRIBE {table}")
80-
for column in columns.result_rows:
81-
schema_info.append(f" {column[0]} ({column[1]})")
82-
return "\n".join(schema_info)
83-
except Exception as e:
84-
raise OperationError(f"Failed to get schema info: {str(e)}")

src/data_neuron/db_operations/database_helpers.py

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,6 @@ def quote_identifier(self, identifier: str) -> str:
1414
else: # postgres and sqlite
1515
return f'"{identifier}"'
1616

17-
def get_fully_qualified_table_name(self, schema_name: str, table_name: str) -> str:
18-
quoted_table = self.quote_identifier(table_name)
19-
if self.database == 'postgres':
20-
if schema_name and schema_name != 'public':
21-
return f'{self.quote_identifier(schema_name)}.{quoted_table}'
22-
elif self.database == 'mssql':
23-
if schema_name and schema_name != 'dbo':
24-
return f'{self.quote_identifier(schema_name)}.{quoted_table}'
25-
elif self.database == 'mysql':
26-
if schema_name:
27-
return f'{self.quote_identifier(schema_name)}.{quoted_table}'
28-
return quoted_table
29-
3017
def get_pattern_match_clause(self, column: str, value: str) -> str:
3118
if self.database == 'mssql':
3219
return f"LOWER({column}) LIKE '%' + LOWER('{value}') + '%'"
@@ -37,10 +24,7 @@ def execute_query(self, query: str) -> List[tuple]:
3724
return self.db.execute_query(query)
3825

3926

40-
def top_few_records(db_helper: DatabaseHelper, column_name: str, schema_name: str, table_name: str, potential_value: str = None, limit: int = 10) -> str:
41-
quoted_table = db_helper.get_fully_qualified_table_name(
42-
schema_name, table_name)
43-
27+
def top_few_records(db_helper: DatabaseHelper, column_name: str, table_name: str, potential_value: str = None, limit: int = 10) -> str:
4428
# Handle the asterisk separately
4529
if column_name == '*':
4630
select_clause = '*'
@@ -55,20 +39,20 @@ def top_few_records(db_helper: DatabaseHelper, column_name: str, schema_name: st
5539
if column_name == '*':
5640
# When column is '*', we can't use it in the WHERE clause
5741
# Instead, we'll search across all columns
58-
where_clause = f"WHERE EXISTS (SELECT 1 FROM {quoted_table} FOR JSON PATH) WHERE JSON_VALUE(BulkColumn, '$.*') LIKE '%{potential_value}%'"
42+
where_clause = f"WHERE EXISTS (SELECT 1 FROM {table_name} FOR JSON PATH) WHERE JSON_VALUE(BulkColumn, '$.*') LIKE '%{potential_value}%'"
5943
else:
6044
where_clause = f"WHERE {db_helper.get_pattern_match_clause(quoted_column, potential_value)}"
6145

6246
if db_helper.database == 'mssql':
6347
query = f"""
6448
SELECT {distinct_clause} TOP {limit} {select_clause}
65-
FROM {quoted_table}
49+
FROM {table_name}
6650
{where_clause}
6751
"""
6852
else: # postgres, mysql, and sqlite
6953
query = f"""
7054
SELECT {distinct_clause}{select_clause}
71-
FROM {quoted_table}
55+
FROM {table_name}
7256
{where_clause}
7357
LIMIT {limit}
7458
"""

0 commit comments

Comments
 (0)