sqlshell 0.4.4__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.
Files changed (54) hide show
  1. sqlshell/__init__.py +84 -0
  2. sqlshell/__main__.py +4926 -0
  3. sqlshell/ai_autocomplete.py +392 -0
  4. sqlshell/ai_settings_dialog.py +337 -0
  5. sqlshell/context_suggester.py +768 -0
  6. sqlshell/create_test_data.py +152 -0
  7. sqlshell/data/create_test_data.py +137 -0
  8. sqlshell/db/__init__.py +6 -0
  9. sqlshell/db/database_manager.py +1318 -0
  10. sqlshell/db/export_manager.py +188 -0
  11. sqlshell/editor.py +1166 -0
  12. sqlshell/editor_integration.py +127 -0
  13. sqlshell/execution_handler.py +421 -0
  14. sqlshell/menus.py +262 -0
  15. sqlshell/notification_manager.py +370 -0
  16. sqlshell/query_tab.py +904 -0
  17. sqlshell/resources/__init__.py +1 -0
  18. sqlshell/resources/icon.png +0 -0
  19. sqlshell/resources/logo_large.png +0 -0
  20. sqlshell/resources/logo_medium.png +0 -0
  21. sqlshell/resources/logo_small.png +0 -0
  22. sqlshell/resources/splash_screen.gif +0 -0
  23. sqlshell/space_invaders.py +501 -0
  24. sqlshell/splash_screen.py +405 -0
  25. sqlshell/sqlshell/__init__.py +5 -0
  26. sqlshell/sqlshell/create_test_data.py +118 -0
  27. sqlshell/sqlshell/create_test_databases.py +96 -0
  28. sqlshell/sqlshell_demo.png +0 -0
  29. sqlshell/styles.py +257 -0
  30. sqlshell/suggester_integration.py +330 -0
  31. sqlshell/syntax_highlighter.py +124 -0
  32. sqlshell/table_list.py +996 -0
  33. sqlshell/ui/__init__.py +6 -0
  34. sqlshell/ui/bar_chart_delegate.py +49 -0
  35. sqlshell/ui/filter_header.py +469 -0
  36. sqlshell/utils/__init__.py +16 -0
  37. sqlshell/utils/profile_cn2.py +1661 -0
  38. sqlshell/utils/profile_column.py +2635 -0
  39. sqlshell/utils/profile_distributions.py +616 -0
  40. sqlshell/utils/profile_entropy.py +347 -0
  41. sqlshell/utils/profile_foreign_keys.py +779 -0
  42. sqlshell/utils/profile_keys.py +2834 -0
  43. sqlshell/utils/profile_ohe.py +934 -0
  44. sqlshell/utils/profile_ohe_advanced.py +754 -0
  45. sqlshell/utils/profile_ohe_comparison.py +237 -0
  46. sqlshell/utils/profile_prediction.py +926 -0
  47. sqlshell/utils/profile_similarity.py +876 -0
  48. sqlshell/utils/search_in_df.py +90 -0
  49. sqlshell/widgets.py +400 -0
  50. sqlshell-0.4.4.dist-info/METADATA +441 -0
  51. sqlshell-0.4.4.dist-info/RECORD +54 -0
  52. sqlshell-0.4.4.dist-info/WHEEL +5 -0
  53. sqlshell-0.4.4.dist-info/entry_points.txt +2 -0
  54. sqlshell-0.4.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,337 @@
1
+ """
2
+ AI Settings Dialog for SQLShell.
3
+
4
+ This module provides a dialog for configuring OpenAI API settings
5
+ for the AI autocomplete feature.
6
+ """
7
+
8
+ from PyQt6.QtWidgets import (
9
+ QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
10
+ QPushButton, QCheckBox, QComboBox, QGroupBox, QMessageBox,
11
+ QFormLayout, QFrame
12
+ )
13
+ from PyQt6.QtCore import Qt
14
+ from PyQt6.QtGui import QFont
15
+
16
+ from sqlshell.ai_autocomplete import get_ai_autocomplete_manager
17
+
18
+
19
+ class AISettingsDialog(QDialog):
20
+ """Dialog for configuring AI autocomplete settings."""
21
+
22
+ def __init__(self, parent=None):
23
+ super().__init__(parent)
24
+ self.ai_manager = get_ai_autocomplete_manager()
25
+ self.setWindowTitle("AI Autocomplete Settings")
26
+ self.setModal(True)
27
+ self.setMinimumWidth(500)
28
+ self.setup_ui()
29
+ self.load_settings()
30
+
31
+ def setup_ui(self):
32
+ """Set up the dialog UI."""
33
+ layout = QVBoxLayout(self)
34
+ layout.setSpacing(16)
35
+
36
+ # Header
37
+ header = QLabel("🤖 AI-Powered SQL Autocomplete")
38
+ header_font = QFont()
39
+ header_font.setPointSize(14)
40
+ header_font.setBold(True)
41
+ header.setFont(header_font)
42
+ layout.addWidget(header)
43
+
44
+ # Description
45
+ desc = QLabel(
46
+ "Enable intelligent SQL suggestions powered by OpenAI. "
47
+ "This feature uses GPT models to provide context-aware completions "
48
+ "based on your database schema and query context."
49
+ )
50
+ desc.setWordWrap(True)
51
+ desc.setStyleSheet("color: #666; margin-bottom: 8px;")
52
+ layout.addWidget(desc)
53
+
54
+ # Enable/Disable checkbox
55
+ self.enabled_checkbox = QCheckBox("Enable AI Autocomplete")
56
+ self.enabled_checkbox.setStyleSheet("font-weight: bold; font-size: 13px;")
57
+ layout.addWidget(self.enabled_checkbox)
58
+
59
+ # API Key group
60
+ api_group = QGroupBox("OpenAI API Configuration")
61
+ api_layout = QFormLayout(api_group)
62
+ api_layout.setSpacing(12)
63
+
64
+ # API Key input
65
+ api_key_layout = QHBoxLayout()
66
+ self.api_key_input = QLineEdit()
67
+ self.api_key_input.setEchoMode(QLineEdit.EchoMode.Password)
68
+ self.api_key_input.setPlaceholderText("sk-...")
69
+ self.api_key_input.setMinimumWidth(300)
70
+ api_key_layout.addWidget(self.api_key_input)
71
+
72
+ self.show_key_btn = QPushButton("👁")
73
+ self.show_key_btn.setFixedWidth(40)
74
+ self.show_key_btn.setCheckable(True)
75
+ self.show_key_btn.toggled.connect(self.toggle_key_visibility)
76
+ self.show_key_btn.setToolTip("Show/Hide API Key")
77
+ api_key_layout.addWidget(self.show_key_btn)
78
+
79
+ api_layout.addRow("API Key:", api_key_layout)
80
+
81
+ # API key help text
82
+ help_label = QLabel(
83
+ '<a href="https://platform.openai.com/api-keys">Get your API key from OpenAI</a>'
84
+ )
85
+ help_label.setOpenExternalLinks(True)
86
+ help_label.setStyleSheet("color: #1890ff; font-size: 11px;")
87
+ api_layout.addRow("", help_label)
88
+
89
+ # Model selection
90
+ self.model_combo = QComboBox()
91
+ self.model_combo.addItems([
92
+ "gpt-4o-mini",
93
+ "gpt-4o",
94
+ "gpt-4-turbo",
95
+ "gpt-3.5-turbo"
96
+ ])
97
+ self.model_combo.setToolTip(
98
+ "gpt-4o-mini: Fast and cost-effective (recommended)\n"
99
+ "gpt-4o: Most capable, higher cost\n"
100
+ "gpt-4-turbo: Good balance of capability and speed\n"
101
+ "gpt-3.5-turbo: Fastest, lowest cost"
102
+ )
103
+ api_layout.addRow("Model:", self.model_combo)
104
+
105
+ layout.addWidget(api_group)
106
+
107
+ # Status indicator
108
+ self.status_frame = QFrame()
109
+ self.status_frame.setStyleSheet("""
110
+ QFrame {
111
+ background-color: #f0f0f0;
112
+ border-radius: 4px;
113
+ padding: 8px;
114
+ }
115
+ """)
116
+ status_layout = QHBoxLayout(self.status_frame)
117
+ status_layout.setContentsMargins(12, 8, 12, 8)
118
+
119
+ self.status_icon = QLabel("⚪")
120
+ self.status_icon.setFixedWidth(24)
121
+ status_layout.addWidget(self.status_icon)
122
+
123
+ self.status_label = QLabel("Not configured")
124
+ status_layout.addWidget(self.status_label)
125
+ status_layout.addStretch()
126
+
127
+ self.test_btn = QPushButton("Test Connection")
128
+ self.test_btn.clicked.connect(self.test_connection)
129
+ status_layout.addWidget(self.test_btn)
130
+
131
+ layout.addWidget(self.status_frame)
132
+
133
+ # Info about usage
134
+ info_frame = QFrame()
135
+ info_frame.setStyleSheet("""
136
+ QFrame {
137
+ background-color: #e6f7ff;
138
+ border: 1px solid #91d5ff;
139
+ border-radius: 4px;
140
+ padding: 8px;
141
+ }
142
+ """)
143
+ info_layout = QVBoxLayout(info_frame)
144
+ info_layout.setContentsMargins(12, 8, 12, 8)
145
+
146
+ info_label = QLabel(
147
+ "💡 <b>How it works:</b><br>"
148
+ "• AI suggestions appear as ghost text while you type<br>"
149
+ "• Press <b>Tab</b> to accept a suggestion<br>"
150
+ "• The AI uses your table schema for context-aware completions<br>"
151
+ "• API calls are rate-limited and cached for efficiency"
152
+ )
153
+ info_label.setWordWrap(True)
154
+ info_label.setStyleSheet("color: #0050b3;")
155
+ info_layout.addWidget(info_label)
156
+
157
+ layout.addWidget(info_frame)
158
+
159
+ # Buttons
160
+ button_layout = QHBoxLayout()
161
+ button_layout.addStretch()
162
+
163
+ cancel_btn = QPushButton("Cancel")
164
+ cancel_btn.clicked.connect(self.reject)
165
+ button_layout.addWidget(cancel_btn)
166
+
167
+ save_btn = QPushButton("Save")
168
+ save_btn.setDefault(True)
169
+ save_btn.clicked.connect(self.save_settings)
170
+ save_btn.setStyleSheet("""
171
+ QPushButton {
172
+ background-color: #1890ff;
173
+ color: white;
174
+ border: none;
175
+ padding: 8px 24px;
176
+ border-radius: 4px;
177
+ font-weight: bold;
178
+ }
179
+ QPushButton:hover {
180
+ background-color: #40a9ff;
181
+ }
182
+ """)
183
+ button_layout.addWidget(save_btn)
184
+
185
+ layout.addLayout(button_layout)
186
+
187
+ def toggle_key_visibility(self, visible: bool):
188
+ """Toggle API key visibility."""
189
+ if visible:
190
+ self.api_key_input.setEchoMode(QLineEdit.EchoMode.Normal)
191
+ self.show_key_btn.setText("🔒")
192
+ else:
193
+ self.api_key_input.setEchoMode(QLineEdit.EchoMode.Password)
194
+ self.show_key_btn.setText("👁")
195
+
196
+ def load_settings(self):
197
+ """Load current settings into the dialog."""
198
+ # Load enabled state
199
+ self.enabled_checkbox.setChecked(self.ai_manager.is_enabled())
200
+
201
+ # Load API key (raw for editing)
202
+ raw_key = self.ai_manager.get_raw_api_key()
203
+ if raw_key:
204
+ self.api_key_input.setText(raw_key)
205
+
206
+ # Load model
207
+ current_model = self.ai_manager.get_model()
208
+ index = self.model_combo.findText(current_model)
209
+ if index >= 0:
210
+ self.model_combo.setCurrentIndex(index)
211
+
212
+ # Update status
213
+ self.update_status()
214
+
215
+ def update_status(self):
216
+ """Update the status indicator."""
217
+ if self.ai_manager.is_available:
218
+ self.status_icon.setText("🟢")
219
+ self.status_label.setText("Ready - AI autocomplete is active")
220
+ self.status_frame.setStyleSheet("""
221
+ QFrame {
222
+ background-color: #f6ffed;
223
+ border: 1px solid #b7eb8f;
224
+ border-radius: 4px;
225
+ }
226
+ """)
227
+ elif self.ai_manager.is_configured:
228
+ self.status_icon.setText("🟡")
229
+ self.status_label.setText("Configured but not validated")
230
+ self.status_frame.setStyleSheet("""
231
+ QFrame {
232
+ background-color: #fffbe6;
233
+ border: 1px solid #ffe58f;
234
+ border-radius: 4px;
235
+ }
236
+ """)
237
+ else:
238
+ self.status_icon.setText("⚪")
239
+ self.status_label.setText("Not configured - enter your API key")
240
+ self.status_frame.setStyleSheet("""
241
+ QFrame {
242
+ background-color: #f0f0f0;
243
+ border-radius: 4px;
244
+ }
245
+ """)
246
+
247
+ def test_connection(self):
248
+ """Test the OpenAI API connection."""
249
+ api_key = self.api_key_input.text().strip()
250
+
251
+ if not api_key:
252
+ QMessageBox.warning(self, "No API Key", "Please enter an API key first.")
253
+ return
254
+
255
+ # Temporarily set the key for testing
256
+ old_key = self.ai_manager.get_raw_api_key()
257
+ self.ai_manager.set_api_key(api_key)
258
+
259
+ try:
260
+ from openai import OpenAI
261
+ client = OpenAI(api_key=api_key)
262
+
263
+ # Make a minimal API call to test
264
+ response = client.chat.completions.create(
265
+ model="gpt-4o-mini",
266
+ messages=[{"role": "user", "content": "Say 'OK'"}],
267
+ max_tokens=5
268
+ )
269
+
270
+ if response.choices:
271
+ QMessageBox.information(
272
+ self,
273
+ "Connection Successful",
274
+ "✅ Successfully connected to OpenAI API!\n\n"
275
+ "AI autocomplete is ready to use."
276
+ )
277
+ self.update_status()
278
+ else:
279
+ raise Exception("No response received")
280
+
281
+ except ImportError:
282
+ QMessageBox.critical(
283
+ self,
284
+ "Missing Dependency",
285
+ "The OpenAI library is not installed.\n\n"
286
+ "Please run: pip install openai"
287
+ )
288
+ # Restore old key
289
+ if old_key:
290
+ self.ai_manager.set_api_key(old_key)
291
+ except Exception as e:
292
+ error_msg = str(e)
293
+ if "invalid_api_key" in error_msg.lower() or "incorrect api key" in error_msg.lower():
294
+ QMessageBox.critical(
295
+ self,
296
+ "Invalid API Key",
297
+ "❌ The API key is invalid.\n\n"
298
+ "Please check your API key and try again."
299
+ )
300
+ elif "rate" in error_msg.lower():
301
+ QMessageBox.warning(
302
+ self,
303
+ "Rate Limited",
304
+ "⚠️ Rate limited by OpenAI.\n\n"
305
+ "The key appears valid but you've hit the rate limit. "
306
+ "Please wait a moment and try again."
307
+ )
308
+ else:
309
+ QMessageBox.critical(
310
+ self,
311
+ "Connection Failed",
312
+ f"❌ Failed to connect to OpenAI:\n\n{error_msg}"
313
+ )
314
+ # Restore old key on failure
315
+ if old_key != api_key:
316
+ self.ai_manager.set_api_key(old_key)
317
+
318
+ def save_settings(self):
319
+ """Save the settings and close the dialog."""
320
+ # Save API key
321
+ api_key = self.api_key_input.text().strip()
322
+ self.ai_manager.set_api_key(api_key)
323
+
324
+ # Save enabled state
325
+ self.ai_manager.set_enabled(self.enabled_checkbox.isChecked())
326
+
327
+ # Save model selection
328
+ self.ai_manager.set_model(self.model_combo.currentText())
329
+
330
+ self.accept()
331
+
332
+
333
+ def show_ai_settings_dialog(parent=None):
334
+ """Show the AI settings dialog."""
335
+ dialog = AISettingsDialog(parent)
336
+ return dialog.exec()
337
+