sqlshell 0.2.2__py3-none-any.whl → 0.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 sqlshell might be problematic. Click here for more details.
- sqlshell/README.md +5 -1
- sqlshell/__init__.py +35 -5
- sqlshell/create_test_data.py +29 -0
- sqlshell/db/__init__.py +2 -1
- sqlshell/db/database_manager.py +336 -23
- sqlshell/db/export_manager.py +188 -0
- sqlshell/editor_integration.py +127 -0
- sqlshell/execution_handler.py +421 -0
- sqlshell/main.py +784 -143
- sqlshell/query_tab.py +592 -7
- sqlshell/table_list.py +90 -1
- sqlshell/ui/filter_header.py +36 -1
- sqlshell/utils/profile_column.py +2515 -0
- sqlshell/utils/profile_distributions.py +613 -0
- sqlshell/utils/profile_foreign_keys.py +547 -0
- sqlshell/utils/profile_ohe.py +631 -0
- sqlshell-0.3.0.dist-info/METADATA +400 -0
- {sqlshell-0.2.2.dist-info → sqlshell-0.3.0.dist-info}/RECORD +21 -14
- {sqlshell-0.2.2.dist-info → sqlshell-0.3.0.dist-info}/WHEEL +1 -1
- sqlshell-0.2.2.dist-info/METADATA +0 -198
- {sqlshell-0.2.2.dist-info → sqlshell-0.3.0.dist-info}/entry_points.txt +0 -0
- {sqlshell-0.2.2.dist-info → sqlshell-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Export functionality for SQLShell application."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import numpy as np
|
|
6
|
+
from typing import Optional, Tuple, Dict, Any
|
|
7
|
+
|
|
8
|
+
class ExportManager:
|
|
9
|
+
"""Manages data export functionality for SQLShell."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, db_manager):
|
|
12
|
+
"""Initialize the export manager.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
db_manager: The database manager instance to use for table registration
|
|
16
|
+
"""
|
|
17
|
+
self.db_manager = db_manager
|
|
18
|
+
|
|
19
|
+
def export_to_excel(self, df: pd.DataFrame, file_name: str) -> Tuple[str, Dict[str, Any]]:
|
|
20
|
+
"""Export data to Excel format.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
df: The DataFrame to export
|
|
24
|
+
file_name: The target file path
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Tuple containing:
|
|
28
|
+
- The generated table name
|
|
29
|
+
- Dictionary with export metadata
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
# Export to Excel
|
|
33
|
+
df.to_excel(file_name, index=False)
|
|
34
|
+
|
|
35
|
+
# Generate table name from file name
|
|
36
|
+
base_name = os.path.splitext(os.path.basename(file_name))[0]
|
|
37
|
+
table_name = self.db_manager.sanitize_table_name(base_name)
|
|
38
|
+
|
|
39
|
+
# Ensure unique table name
|
|
40
|
+
original_name = table_name
|
|
41
|
+
counter = 1
|
|
42
|
+
while table_name in self.db_manager.loaded_tables:
|
|
43
|
+
table_name = f"{original_name}_{counter}"
|
|
44
|
+
counter += 1
|
|
45
|
+
|
|
46
|
+
# Register the table in the database manager
|
|
47
|
+
self.db_manager.register_dataframe(df, table_name, file_name)
|
|
48
|
+
|
|
49
|
+
# Update tracking
|
|
50
|
+
self.db_manager.loaded_tables[table_name] = file_name
|
|
51
|
+
self.db_manager.table_columns[table_name] = df.columns.tolist()
|
|
52
|
+
|
|
53
|
+
return table_name, {
|
|
54
|
+
'file_path': file_name,
|
|
55
|
+
'columns': df.columns.tolist(),
|
|
56
|
+
'row_count': len(df)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
raise Exception(f"Failed to export to Excel: {str(e)}")
|
|
61
|
+
|
|
62
|
+
def export_to_parquet(self, df: pd.DataFrame, file_name: str) -> Tuple[str, Dict[str, Any]]:
|
|
63
|
+
"""Export data to Parquet format.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
df: The DataFrame to export
|
|
67
|
+
file_name: The target file path
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Tuple containing:
|
|
71
|
+
- The generated table name
|
|
72
|
+
- Dictionary with export metadata
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
# Export to Parquet
|
|
76
|
+
df.to_parquet(file_name, index=False)
|
|
77
|
+
|
|
78
|
+
# Generate table name from file name
|
|
79
|
+
base_name = os.path.splitext(os.path.basename(file_name))[0]
|
|
80
|
+
table_name = self.db_manager.sanitize_table_name(base_name)
|
|
81
|
+
|
|
82
|
+
# Ensure unique table name
|
|
83
|
+
original_name = table_name
|
|
84
|
+
counter = 1
|
|
85
|
+
while table_name in self.db_manager.loaded_tables:
|
|
86
|
+
table_name = f"{original_name}_{counter}"
|
|
87
|
+
counter += 1
|
|
88
|
+
|
|
89
|
+
# Register the table in the database manager
|
|
90
|
+
self.db_manager.register_dataframe(df, table_name, file_name)
|
|
91
|
+
|
|
92
|
+
# Update tracking
|
|
93
|
+
self.db_manager.loaded_tables[table_name] = file_name
|
|
94
|
+
self.db_manager.table_columns[table_name] = df.columns.tolist()
|
|
95
|
+
|
|
96
|
+
return table_name, {
|
|
97
|
+
'file_path': file_name,
|
|
98
|
+
'columns': df.columns.tolist(),
|
|
99
|
+
'row_count': len(df)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
raise Exception(f"Failed to export to Parquet: {str(e)}")
|
|
104
|
+
|
|
105
|
+
def convert_table_to_dataframe(self, table_widget) -> Optional[pd.DataFrame]:
|
|
106
|
+
"""Convert a QTableWidget to a pandas DataFrame with proper data types.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
table_widget: The QTableWidget containing the data
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
DataFrame with properly typed data, or None if conversion fails
|
|
113
|
+
"""
|
|
114
|
+
if not table_widget or table_widget.rowCount() == 0:
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
# Get headers
|
|
118
|
+
headers = [table_widget.horizontalHeaderItem(i).text()
|
|
119
|
+
for i in range(table_widget.columnCount())]
|
|
120
|
+
|
|
121
|
+
# Get data
|
|
122
|
+
data = []
|
|
123
|
+
for row in range(table_widget.rowCount()):
|
|
124
|
+
row_data = []
|
|
125
|
+
for column in range(table_widget.columnCount()):
|
|
126
|
+
item = table_widget.item(row, column)
|
|
127
|
+
row_data.append(item.text() if item else '')
|
|
128
|
+
data.append(row_data)
|
|
129
|
+
|
|
130
|
+
# Create DataFrame from raw string data
|
|
131
|
+
df_raw = pd.DataFrame(data, columns=headers)
|
|
132
|
+
|
|
133
|
+
# Try to use the original dataframe's dtypes if available
|
|
134
|
+
if hasattr(table_widget, 'current_df') and table_widget.current_df is not None:
|
|
135
|
+
original_df = table_widget.current_df
|
|
136
|
+
|
|
137
|
+
# Create a new DataFrame with appropriate types
|
|
138
|
+
df_typed = pd.DataFrame()
|
|
139
|
+
|
|
140
|
+
for col in df_raw.columns:
|
|
141
|
+
if col in original_df.columns:
|
|
142
|
+
# Get the original column type
|
|
143
|
+
orig_type = original_df[col].dtype
|
|
144
|
+
|
|
145
|
+
# Special handling for different data types
|
|
146
|
+
if pd.api.types.is_numeric_dtype(orig_type):
|
|
147
|
+
try:
|
|
148
|
+
numeric_col = pd.to_numeric(
|
|
149
|
+
df_raw[col].str.replace(',', '').replace('NULL', np.nan)
|
|
150
|
+
)
|
|
151
|
+
df_typed[col] = numeric_col
|
|
152
|
+
except:
|
|
153
|
+
df_typed[col] = df_raw[col]
|
|
154
|
+
elif pd.api.types.is_datetime64_dtype(orig_type):
|
|
155
|
+
try:
|
|
156
|
+
df_typed[col] = pd.to_datetime(df_raw[col].replace('NULL', np.nan))
|
|
157
|
+
except:
|
|
158
|
+
df_typed[col] = df_raw[col]
|
|
159
|
+
elif pd.api.types.is_bool_dtype(orig_type):
|
|
160
|
+
try:
|
|
161
|
+
df_typed[col] = df_raw[col].map({'True': True, 'False': False}).replace('NULL', np.nan)
|
|
162
|
+
except:
|
|
163
|
+
df_typed[col] = df_raw[col]
|
|
164
|
+
else:
|
|
165
|
+
df_typed[col] = df_raw[col]
|
|
166
|
+
else:
|
|
167
|
+
df_typed[col] = df_raw[col]
|
|
168
|
+
|
|
169
|
+
return df_typed
|
|
170
|
+
|
|
171
|
+
else:
|
|
172
|
+
# If we don't have the original dataframe, try to infer types
|
|
173
|
+
df_raw.replace('NULL', np.nan, inplace=True)
|
|
174
|
+
|
|
175
|
+
for col in df_raw.columns:
|
|
176
|
+
try:
|
|
177
|
+
df_raw[col] = pd.to_numeric(df_raw[col].str.replace(',', ''))
|
|
178
|
+
except:
|
|
179
|
+
try:
|
|
180
|
+
df_raw[col] = pd.to_datetime(df_raw[col])
|
|
181
|
+
except:
|
|
182
|
+
try:
|
|
183
|
+
if df_raw[col].dropna().isin(['True', 'False']).all():
|
|
184
|
+
df_raw[col] = df_raw[col].map({'True': True, 'False': False})
|
|
185
|
+
except:
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
return df_raw
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integration module to add F5/F9 execution functionality to SQLEditor.
|
|
3
|
+
This module provides a clean way to enhance the editor without modifying the original code.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from PyQt6.QtCore import Qt
|
|
7
|
+
from PyQt6.QtWidgets import QPlainTextEdit
|
|
8
|
+
from .execution_handler import SQLExecutionHandler, ExecutionKeyHandler
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EditorExecutionIntegration:
|
|
12
|
+
"""
|
|
13
|
+
Integration class to add F5/F9 execution functionality to SQLEditor.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, editor: QPlainTextEdit, execute_callback=None):
|
|
17
|
+
"""
|
|
18
|
+
Initialize the integration.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
editor: The SQLEditor instance
|
|
22
|
+
execute_callback: Function to call to execute queries
|
|
23
|
+
"""
|
|
24
|
+
self.editor = editor
|
|
25
|
+
self.execution_handler = SQLExecutionHandler(execute_callback)
|
|
26
|
+
self.key_handler = ExecutionKeyHandler(self.execution_handler)
|
|
27
|
+
|
|
28
|
+
# Store original keyPressEvent to preserve existing functionality
|
|
29
|
+
self.original_key_press_event = editor.keyPressEvent
|
|
30
|
+
|
|
31
|
+
# Replace keyPressEvent with our enhanced version
|
|
32
|
+
editor.keyPressEvent = self.enhanced_key_press_event
|
|
33
|
+
|
|
34
|
+
def set_execute_callback(self, callback):
|
|
35
|
+
"""Set the execution callback function."""
|
|
36
|
+
self.execution_handler.set_execute_callback(callback)
|
|
37
|
+
|
|
38
|
+
def enhanced_key_press_event(self, event):
|
|
39
|
+
"""Enhanced keyPressEvent that handles F5/F9 while preserving original functionality."""
|
|
40
|
+
|
|
41
|
+
# First, try to handle F5/F9 keys
|
|
42
|
+
if self.key_handler.handle_key_press(self.editor, event.key(), event.modifiers()):
|
|
43
|
+
# Key was handled by our execution handler
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
# If not F5/F9, use the original keyPressEvent
|
|
47
|
+
self.original_key_press_event(event)
|
|
48
|
+
|
|
49
|
+
def execute_all_statements(self):
|
|
50
|
+
"""Execute all statements in the editor (F5 functionality)."""
|
|
51
|
+
try:
|
|
52
|
+
return self.execution_handler.execute_from_editor(self.editor, "all")
|
|
53
|
+
except Exception as e:
|
|
54
|
+
print(f"Error executing all statements: {e}")
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def execute_current_statement(self):
|
|
58
|
+
"""Execute the current statement (F9 functionality)."""
|
|
59
|
+
try:
|
|
60
|
+
return self.execution_handler.execute_from_editor(self.editor, "current")
|
|
61
|
+
except Exception as e:
|
|
62
|
+
print(f"Error executing current statement: {e}")
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
def get_current_statement_info(self):
|
|
66
|
+
"""Get information about the current statement at cursor position."""
|
|
67
|
+
text = self.editor.toPlainText()
|
|
68
|
+
cursor = self.editor.textCursor()
|
|
69
|
+
cursor_position = cursor.position()
|
|
70
|
+
|
|
71
|
+
current_stmt = self.execution_handler.get_current_statement(text, cursor_position)
|
|
72
|
+
if current_stmt:
|
|
73
|
+
stmt_text, start_pos, end_pos = current_stmt
|
|
74
|
+
return {
|
|
75
|
+
'text': stmt_text,
|
|
76
|
+
'start': start_pos,
|
|
77
|
+
'end': end_pos,
|
|
78
|
+
'cursor_position': cursor_position
|
|
79
|
+
}
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
def get_all_statements_info(self):
|
|
83
|
+
"""Get information about all statements in the editor."""
|
|
84
|
+
text = self.editor.toPlainText()
|
|
85
|
+
statements = self.execution_handler.parse_sql_statements(text)
|
|
86
|
+
|
|
87
|
+
return [
|
|
88
|
+
{
|
|
89
|
+
'text': stmt_text,
|
|
90
|
+
'start': start_pos,
|
|
91
|
+
'end': end_pos,
|
|
92
|
+
'index': i
|
|
93
|
+
}
|
|
94
|
+
for i, (stmt_text, start_pos, end_pos) in enumerate(statements)
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def integrate_execution_functionality(editor: QPlainTextEdit, execute_callback=None):
|
|
99
|
+
"""
|
|
100
|
+
Convenience function to integrate F5/F9 execution functionality into an editor.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
editor: The SQLEditor instance
|
|
104
|
+
execute_callback: Function to call to execute queries
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
EditorExecutionIntegration instance for further customization
|
|
108
|
+
"""
|
|
109
|
+
integration = EditorExecutionIntegration(editor, execute_callback)
|
|
110
|
+
|
|
111
|
+
# Store the integration instance on the editor for later access
|
|
112
|
+
editor._execution_integration = integration
|
|
113
|
+
|
|
114
|
+
return integration
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def get_execution_integration(editor: QPlainTextEdit):
|
|
118
|
+
"""
|
|
119
|
+
Get the execution integration instance from an editor.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
editor: The SQLEditor instance
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
EditorExecutionIntegration instance or None if not integrated
|
|
126
|
+
"""
|
|
127
|
+
return getattr(editor, '_execution_integration', None)
|