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,6 @@
1
+ """UI components for SQLShell application."""
2
+
3
+ from sqlshell.ui.filter_header import FilterHeader
4
+ from sqlshell.ui.bar_chart_delegate import BarChartDelegate
5
+
6
+ __all__ = ['FilterHeader', 'BarChartDelegate']
@@ -0,0 +1,49 @@
1
+ from PyQt6.QtWidgets import QStyledItemDelegate
2
+ from PyQt6.QtCore import Qt, QRect
3
+ from PyQt6.QtGui import QColor
4
+
5
+ class BarChartDelegate(QStyledItemDelegate):
6
+ def __init__(self, parent=None):
7
+ super().__init__(parent)
8
+ self.min_val = 0
9
+ self.max_val = 1
10
+ self.bar_color = QColor("#3498DB")
11
+
12
+ def set_range(self, min_val, max_val):
13
+ self.min_val = min_val
14
+ self.max_val = max_val
15
+
16
+ def paint(self, painter, option, index):
17
+ # Draw the default background
18
+ super().paint(painter, option, index)
19
+
20
+ try:
21
+ text = index.data()
22
+ value = float(text.replace(',', ''))
23
+
24
+ # Calculate normalized value
25
+ range_val = self.max_val - self.min_val if self.max_val != self.min_val else 1
26
+ normalized = (value - self.min_val) / range_val
27
+
28
+ # Define bar dimensions
29
+ bar_height = 16
30
+ max_bar_width = 100
31
+ bar_width = max(5, int(max_bar_width * normalized))
32
+
33
+ # Calculate positions
34
+ text_width = option.fontMetrics.horizontalAdvance(text) + 10
35
+ bar_x = option.rect.left() + text_width + 10
36
+ bar_y = option.rect.center().y() - bar_height // 2
37
+
38
+ # Draw the bar
39
+ bar_rect = QRect(bar_x, bar_y, bar_width, bar_height)
40
+ painter.fillRect(bar_rect, self.bar_color)
41
+
42
+ # Draw the text
43
+ text_rect = QRect(option.rect.left() + 4, option.rect.top(),
44
+ text_width, option.rect.height())
45
+ painter.drawText(text_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, text)
46
+
47
+ except (ValueError, AttributeError):
48
+ # If not a number, just draw the text
49
+ super().paint(painter, option, index)
@@ -0,0 +1,469 @@
1
+ from PyQt6.QtWidgets import (QHeaderView, QMenu, QCheckBox, QWidgetAction,
2
+ QWidget, QVBoxLayout, QLineEdit, QHBoxLayout, QPushButton, QTableWidget, QMessageBox)
3
+ from PyQt6.QtCore import Qt, QRect, QPoint
4
+ from PyQt6.QtGui import QColor, QFont, QPolygon, QPainterPath, QBrush
5
+
6
+ class FilterHeader(QHeaderView):
7
+ def __init__(self, parent=None):
8
+ super().__init__(Qt.Orientation.Horizontal, parent)
9
+ self.filter_buttons = []
10
+ self.active_filters = {} # Track active filters for each column
11
+ self.columns_with_bars = set() # Track which columns show bar charts
12
+ self.bar_delegates = {} # Store delegates for columns with bars
13
+ self.setSectionsClickable(True)
14
+ self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
15
+ self.customContextMenuRequested.connect(self.show_header_context_menu)
16
+ self.main_window = None # Store reference to main window
17
+ self.filter_icon_color = QColor("#3498DB") # Bright blue color for filter icon
18
+
19
+ def toggle_bar_chart(self, column_index):
20
+ """Toggle bar chart visualization for a column"""
21
+ table = self.parent()
22
+ if not table:
23
+ return
24
+
25
+ if column_index in self.columns_with_bars:
26
+ # Remove bars
27
+ self.columns_with_bars.remove(column_index)
28
+ if column_index in self.bar_delegates:
29
+ table.setItemDelegateForColumn(column_index, None)
30
+ del self.bar_delegates[column_index]
31
+ else:
32
+ # Add bars
33
+ self.columns_with_bars.add(column_index)
34
+
35
+ # Get all values for normalization
36
+ values = []
37
+ for row in range(table.rowCount()):
38
+ item = table.item(row, column_index)
39
+ if item:
40
+ try:
41
+ value = float(item.text().replace(',', ''))
42
+ values.append(value)
43
+ except ValueError:
44
+ continue
45
+
46
+ if not values:
47
+ return
48
+
49
+ # Calculate min and max for normalization
50
+ min_val = min(values)
51
+ max_val = max(values)
52
+
53
+ # Import BarChartDelegate here to avoid circular imports
54
+ from sqlshell.ui.bar_chart_delegate import BarChartDelegate
55
+
56
+ # Create and set up delegate
57
+ delegate = BarChartDelegate(table)
58
+ delegate.set_range(min_val, max_val)
59
+ self.bar_delegates[column_index] = delegate
60
+ table.setItemDelegateForColumn(column_index, delegate)
61
+
62
+ # Update the view
63
+ table.viewport().update()
64
+
65
+ def show_header_context_menu(self, pos):
66
+ """Show context menu for header section"""
67
+ logical_index = self.logicalIndexAt(pos)
68
+ if logical_index < 0:
69
+ return
70
+
71
+ # Create context menu
72
+ context_menu = QMenu(self)
73
+ context_menu.setStyleSheet("""
74
+ QMenu {
75
+ background-color: white;
76
+ border: 1px solid #BDC3C7;
77
+ padding: 5px;
78
+ }
79
+ QMenu::item {
80
+ padding: 5px 20px;
81
+ }
82
+ QMenu::item:selected {
83
+ background-color: #3498DB;
84
+ color: white;
85
+ }
86
+ """)
87
+
88
+ # Add sort actions
89
+ sort_asc_action = context_menu.addAction("Sort Ascending")
90
+ sort_desc_action = context_menu.addAction("Sort Descending")
91
+ context_menu.addSeparator()
92
+
93
+ # Add count rows action
94
+ count_rows_action = context_menu.addAction("Count Rows")
95
+
96
+ # Add explain column action
97
+ explain_action = context_menu.addAction("Explain Column")
98
+
99
+ # Add encode text action
100
+ encode_action = context_menu.addAction("Encode Text")
101
+
102
+ # Add predict action
103
+ predict_action = context_menu.addAction("Predict Column")
104
+
105
+ # Add CN2 rule discovery action
106
+ discover_rules_action = context_menu.addAction("Discover Classification Rules (CN2)")
107
+
108
+ # Add load model action
109
+ load_model_action = context_menu.addAction("Load Model & Predict")
110
+
111
+ context_menu.addSeparator()
112
+ filter_action = context_menu.addAction("Filter...")
113
+
114
+ # Add bar chart action if column is numeric
115
+ table = self.parent()
116
+ if table and table.rowCount() > 0:
117
+ try:
118
+ # Check if column contains numeric values
119
+ sample_value = table.item(0, logical_index).text()
120
+ float(sample_value.replace(',', '')) # Try converting to float
121
+
122
+ context_menu.addSeparator()
123
+ toggle_bar_action = context_menu.addAction(
124
+ "Remove Bar Chart" if logical_index in self.columns_with_bars
125
+ else "Add Bar Chart"
126
+ )
127
+ except (ValueError, AttributeError):
128
+ toggle_bar_action = None
129
+ else:
130
+ toggle_bar_action = None
131
+
132
+ # Show menu and get selected action
133
+ action = context_menu.exec(self.mapToGlobal(pos))
134
+
135
+ if not action:
136
+ return
137
+
138
+ table = self.parent()
139
+ if not table:
140
+ return
141
+
142
+ if action == sort_asc_action:
143
+ table.sortItems(logical_index, Qt.SortOrder.AscendingOrder)
144
+ elif action == sort_desc_action:
145
+ table.sortItems(logical_index, Qt.SortOrder.DescendingOrder)
146
+ elif action == filter_action:
147
+ self.show_filter_menu(logical_index)
148
+ elif action == toggle_bar_action:
149
+ self.toggle_bar_chart(logical_index)
150
+ elif action == explain_action:
151
+ # Call the explain_column method on the main window
152
+ if self.main_window and hasattr(self.main_window, "explain_column"):
153
+ # Get the column name from the table (if it has a current dataframe)
154
+ current_tab = self.main_window.get_current_tab()
155
+ if current_tab and hasattr(current_tab, "current_df") and current_tab.current_df is not None:
156
+ if logical_index < len(current_tab.current_df.columns):
157
+ column_name = current_tab.current_df.columns[logical_index]
158
+ self.main_window.explain_column(column_name)
159
+ elif action == encode_action:
160
+ # Call the encode_text method on the main window
161
+ if self.main_window and hasattr(self.main_window, "encode_text"):
162
+ # Get the column name from the table (if it has a current dataframe)
163
+ current_tab = self.main_window.get_current_tab()
164
+ if current_tab and hasattr(current_tab, "current_df") and current_tab.current_df is not None:
165
+ if logical_index < len(current_tab.current_df.columns):
166
+ column_name = current_tab.current_df.columns[logical_index]
167
+ self.main_window.encode_text(column_name)
168
+ elif action == predict_action:
169
+ # Call the predict_column method on the main window
170
+ if self.main_window and hasattr(self.main_window, "predict_column"):
171
+ # Get the column name from the table (if it has a current dataframe)
172
+ current_tab = self.main_window.get_current_tab()
173
+ if current_tab and hasattr(current_tab, "current_df") and current_tab.current_df is not None:
174
+ if logical_index < len(current_tab.current_df.columns):
175
+ column_name = current_tab.current_df.columns[logical_index]
176
+ self.main_window.predict_column(column_name)
177
+ elif action == discover_rules_action:
178
+ # Call the discover_classification_rules method on the main window
179
+ if self.main_window and hasattr(self.main_window, "discover_classification_rules"):
180
+ # Get the column name from the table (if it has a current dataframe)
181
+ current_tab = self.main_window.get_current_tab()
182
+ if current_tab and hasattr(current_tab, "current_df") and current_tab.current_df is not None:
183
+ if logical_index < len(current_tab.current_df.columns):
184
+ column_name = current_tab.current_df.columns[logical_index]
185
+ self.main_window.discover_classification_rules(column_name)
186
+ elif action == load_model_action:
187
+ # Call the load_and_apply_model method on the main window
188
+ if self.main_window and hasattr(self.main_window, "load_and_apply_model"):
189
+ self.main_window.load_and_apply_model()
190
+ elif action == count_rows_action:
191
+ # Get the current tab and show row count
192
+ current_tab = self.main_window.get_current_tab()
193
+ if current_tab and hasattr(current_tab, "current_df") and current_tab.current_df is not None:
194
+ row_count = len(current_tab.current_df)
195
+ QMessageBox.information(self, "Row Count", f"Total rows: {row_count:,}")
196
+
197
+ def set_main_window(self, window):
198
+ """Set the reference to the main window"""
199
+ self.main_window = window
200
+
201
+ def paintSection(self, painter, rect, logical_index):
202
+ """Override paint section to add filter indicator"""
203
+ super().paintSection(painter, rect, logical_index)
204
+
205
+ if logical_index in self.active_filters:
206
+ # Draw background highlight for filtered columns
207
+ highlight_color = QColor(52, 152, 219, 30) # Light blue background
208
+ painter.fillRect(rect, highlight_color)
209
+
210
+ # Make icon larger and more visible
211
+ icon_size = min(rect.height() - 8, 24) # Larger icon, but not too large
212
+ margin = 6
213
+ icon_rect = QRect(
214
+ rect.right() - icon_size - margin,
215
+ rect.top() + (rect.height() - icon_size) // 2,
216
+ icon_size,
217
+ icon_size
218
+ )
219
+
220
+ # Draw filter icon with improved visibility
221
+ painter.save()
222
+
223
+ # Set up the pen for better visibility
224
+ pen = painter.pen()
225
+ pen.setWidth(3) # Thicker lines
226
+ pen.setColor(self.filter_icon_color)
227
+ painter.setPen(pen)
228
+
229
+ # Calculate points for larger funnel shape
230
+ points = [
231
+ QPoint(icon_rect.left(), icon_rect.top()),
232
+ QPoint(icon_rect.right(), icon_rect.top()),
233
+ QPoint(icon_rect.center().x() + icon_size//3, icon_rect.center().y()),
234
+ QPoint(icon_rect.center().x() + icon_size//3, icon_rect.bottom()),
235
+ QPoint(icon_rect.center().x() - icon_size//3, icon_rect.bottom()),
236
+ QPoint(icon_rect.center().x() - icon_size//3, icon_rect.center().y()),
237
+ QPoint(icon_rect.left(), icon_rect.top())
238
+ ]
239
+
240
+ # Create and fill path
241
+ path = QPainterPath()
242
+ path.moveTo(float(points[0].x()), float(points[0].y()))
243
+ for point in points[1:]:
244
+ path.lineTo(float(point.x()), float(point.y()))
245
+
246
+ # Fill with semi-transparent blue
247
+ painter.fillPath(path, QBrush(QColor(52, 152, 219, 120))) # More opaque fill
248
+
249
+ # Draw outline
250
+ painter.drawPolyline(QPolygon(points))
251
+
252
+ # If multiple values are filtered, add a number
253
+ if len(self.active_filters[logical_index]) > 1:
254
+ # Draw number with better visibility
255
+ number_rect = QRect(icon_rect.left(), icon_rect.top(),
256
+ icon_rect.width(), icon_rect.height())
257
+ painter.setFont(QFont("Arial", icon_size//2, QFont.Weight.Bold))
258
+
259
+ # Draw text shadow for better contrast
260
+ painter.setPen(QColor("white"))
261
+ painter.drawText(number_rect.adjusted(1, 1, 1, 1),
262
+ Qt.AlignmentFlag.AlignCenter,
263
+ str(len(self.active_filters[logical_index])))
264
+
265
+ # Draw main text
266
+ painter.setPen(self.filter_icon_color)
267
+ painter.drawText(number_rect, Qt.AlignmentFlag.AlignCenter,
268
+ str(len(self.active_filters[logical_index])))
269
+
270
+ painter.restore()
271
+
272
+ # Draw a more visible indicator at the bottom of the header section
273
+ painter.save()
274
+ indicator_height = 3 # Thicker indicator line
275
+ indicator_rect = QRect(rect.left(), rect.bottom() - indicator_height,
276
+ rect.width(), indicator_height)
277
+ painter.fillRect(indicator_rect, self.filter_icon_color)
278
+ painter.restore()
279
+
280
+ def show_filter_menu(self, logical_index):
281
+ if not self.parent() or not isinstance(self.parent(), QTableWidget):
282
+ return
283
+
284
+ table = self.parent()
285
+ unique_values = set()
286
+
287
+ # Collect unique values from the column
288
+ for row in range(table.rowCount()):
289
+ item = table.item(row, logical_index)
290
+ if item and not table.isRowHidden(row):
291
+ unique_values.add(item.text())
292
+
293
+ # Create and show the filter menu
294
+ menu = QMenu(self)
295
+ menu.setStyleSheet("""
296
+ QMenu {
297
+ background-color: white;
298
+ border: 1px solid #BDC3C7;
299
+ padding: 5px;
300
+ }
301
+ QMenu::item {
302
+ padding: 5px 20px;
303
+ }
304
+ QMenu::item:selected {
305
+ background-color: #3498DB;
306
+ color: white;
307
+ }
308
+ QCheckBox {
309
+ padding: 5px;
310
+ }
311
+ QScrollArea {
312
+ border: none;
313
+ }
314
+ """)
315
+
316
+ # Add search box at the top
317
+ search_widget = QWidget(menu)
318
+ search_layout = QVBoxLayout(search_widget)
319
+ search_edit = QLineEdit(search_widget)
320
+ search_edit.setPlaceholderText("Search values...")
321
+ search_layout.addWidget(search_edit)
322
+
323
+ # Add action for search widget
324
+ search_action = QWidgetAction(menu)
325
+ search_action.setDefaultWidget(search_widget)
326
+ menu.addAction(search_action)
327
+ menu.addSeparator()
328
+
329
+ # Add "Select All" checkbox
330
+ select_all = QCheckBox("Select All", menu)
331
+ select_all.setChecked(True)
332
+ select_all_action = QWidgetAction(menu)
333
+ select_all_action.setDefaultWidget(select_all)
334
+ menu.addAction(select_all_action)
335
+ menu.addSeparator()
336
+
337
+ # Create scrollable area for checkboxes
338
+ scroll_widget = QWidget(menu)
339
+ scroll_layout = QVBoxLayout(scroll_widget)
340
+ scroll_layout.setSpacing(2)
341
+ scroll_layout.setContentsMargins(0, 0, 0, 0)
342
+
343
+ # Add checkboxes for unique values
344
+ value_checkboxes = {}
345
+ for value in sorted(unique_values):
346
+ checkbox = QCheckBox(str(value), scroll_widget)
347
+ # Set checked state based on active filters
348
+ checkbox.setChecked(logical_index not in self.active_filters or
349
+ value in self.active_filters[logical_index])
350
+ value_checkboxes[value] = checkbox
351
+ scroll_layout.addWidget(checkbox)
352
+
353
+ # Add scrollable area to menu
354
+ scroll_action = QWidgetAction(menu)
355
+ scroll_action.setDefaultWidget(scroll_widget)
356
+ menu.addAction(scroll_action)
357
+
358
+ # Connect search box to filter checkboxes
359
+ def filter_checkboxes(text):
360
+ for value, checkbox in value_checkboxes.items():
361
+ checkbox.setVisible(text.lower() in str(value).lower())
362
+
363
+ search_edit.textChanged.connect(filter_checkboxes)
364
+
365
+ # Connect select all to other checkboxes
366
+ def toggle_all(state):
367
+ for checkbox in value_checkboxes.values():
368
+ if not checkbox.isHidden(): # Only toggle visible checkboxes
369
+ checkbox.setChecked(state)
370
+
371
+ select_all.stateChanged.connect(toggle_all)
372
+
373
+ # Add Apply and Clear buttons
374
+ menu.addSeparator()
375
+ apply_button = QPushButton("Apply Filter", menu)
376
+ apply_button.setStyleSheet("""
377
+ QPushButton {
378
+ background-color: #2ECC71;
379
+ color: white;
380
+ border: none;
381
+ padding: 5px 15px;
382
+ border-radius: 3px;
383
+ }
384
+ QPushButton:hover {
385
+ background-color: #27AE60;
386
+ }
387
+ """)
388
+
389
+ clear_button = QPushButton("Clear Filter", menu)
390
+ clear_button.setStyleSheet("""
391
+ QPushButton {
392
+ background-color: #E74C3C;
393
+ color: white;
394
+ border: none;
395
+ padding: 5px 15px;
396
+ border-radius: 3px;
397
+ }
398
+ QPushButton:hover {
399
+ background-color: #C0392B;
400
+ }
401
+ """)
402
+
403
+ button_widget = QWidget(menu)
404
+ button_layout = QHBoxLayout(button_widget)
405
+ button_layout.addWidget(apply_button)
406
+ button_layout.addWidget(clear_button)
407
+
408
+ button_action = QWidgetAction(menu)
409
+ button_action.setDefaultWidget(button_widget)
410
+ menu.addAction(button_action)
411
+
412
+ def apply_filter():
413
+ # Get selected values
414
+ selected_values = {value for value, checkbox in value_checkboxes.items()
415
+ if checkbox.isChecked()}
416
+
417
+ if len(selected_values) < len(unique_values):
418
+ # Store active filter only if not all values are selected
419
+ self.active_filters[logical_index] = selected_values
420
+ else:
421
+ # Remove filter if all values are selected
422
+ self.active_filters.pop(logical_index, None)
423
+
424
+ # Apply all active filters
425
+ self.apply_all_filters(table)
426
+
427
+ menu.close()
428
+ self.updateSection(logical_index) # Redraw section to show/hide filter icon
429
+
430
+ def clear_filter():
431
+ # Remove filter for this column
432
+ if logical_index in self.active_filters:
433
+ del self.active_filters[logical_index]
434
+
435
+ # Apply remaining filters
436
+ self.apply_all_filters(table)
437
+
438
+ menu.close()
439
+ self.updateSection(logical_index) # Redraw section to hide filter icon
440
+
441
+ apply_button.clicked.connect(apply_filter)
442
+ clear_button.clicked.connect(clear_filter)
443
+
444
+ # Show menu under the header section
445
+ header_pos = self.mapToGlobal(self.geometry().bottomLeft())
446
+ header_pos.setX(header_pos.x() + self.sectionPosition(logical_index))
447
+ menu.exec(header_pos)
448
+
449
+ def apply_all_filters(self, table):
450
+ """Apply all active filters to the table"""
451
+ # Show all rows first
452
+ for row in range(table.rowCount()):
453
+ table.setRowHidden(row, False)
454
+
455
+ # Apply each active filter
456
+ for col_idx, allowed_values in self.active_filters.items():
457
+ for row in range(table.rowCount()):
458
+ item = table.item(row, col_idx)
459
+ if item and not table.isRowHidden(row):
460
+ table.setRowHidden(row, item.text() not in allowed_values)
461
+
462
+ # Update status bar with visible row count
463
+ if self.main_window:
464
+ visible_rows = sum(1 for row in range(table.rowCount())
465
+ if not table.isRowHidden(row))
466
+ total_filters = len(self.active_filters)
467
+ filter_text = f" ({total_filters} filter{'s' if total_filters != 1 else ''} active)" if total_filters > 0 else ""
468
+ self.main_window.statusBar().showMessage(
469
+ f"Showing {visible_rows:,} rows{filter_text}")
@@ -0,0 +1,16 @@
1
+ """
2
+ Utility functions for SQLShell
3
+ """
4
+
5
+ # Import profile_entropy for convenient access
6
+ from sqlshell.utils.profile_entropy import profile, visualize_profile, EntropyProfiler
7
+
8
+ # Import CN2 rule induction for convenient access
9
+ from sqlshell.utils.profile_cn2 import (
10
+ CN2Classifier,
11
+ Condition,
12
+ Rule,
13
+ fit_cn2,
14
+ visualize_cn2_rules,
15
+ CN2RulesVisualization
16
+ )