sqlshell 0.1.6__py3-none-any.whl → 0.1.9__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/__init__.py +4 -2
- sqlshell/create_test_data.py +50 -0
- sqlshell/data/create_test_data.py +137 -0
- sqlshell/db/__init__.py +5 -0
- sqlshell/db/database_manager.py +691 -0
- sqlshell/editor.py +856 -0
- sqlshell/main.py +1904 -961
- sqlshell/query_tab.py +172 -0
- sqlshell/resources/__init__.py +1 -0
- sqlshell/resources/create_icon.py +131 -0
- sqlshell/resources/create_splash.py +96 -0
- sqlshell/resources/icon.png +0 -0
- sqlshell/resources/logo_large.png +0 -0
- sqlshell/resources/logo_medium.png +0 -0
- sqlshell/resources/logo_small.png +0 -0
- sqlshell/resources/splash_screen.gif +0 -0
- sqlshell/setup.py +1 -1
- sqlshell/splash_screen.py +405 -0
- sqlshell/sqlshell/create_test_data.py +4 -23
- sqlshell/sqlshell_demo.png +0 -0
- sqlshell/syntax_highlighter.py +123 -0
- sqlshell/ui/__init__.py +6 -0
- sqlshell/ui/bar_chart_delegate.py +49 -0
- sqlshell/ui/filter_header.py +403 -0
- sqlshell-0.1.9.dist-info/METADATA +122 -0
- sqlshell-0.1.9.dist-info/RECORD +31 -0
- {sqlshell-0.1.6.dist-info → sqlshell-0.1.9.dist-info}/WHEEL +1 -1
- sqlshell-0.1.6.dist-info/METADATA +0 -92
- sqlshell-0.1.6.dist-info/RECORD +0 -11
- {sqlshell-0.1.6.dist-info → sqlshell-0.1.9.dist-info}/entry_points.txt +0 -0
- {sqlshell-0.1.6.dist-info → sqlshell-0.1.9.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
from PyQt6.QtWidgets import (QHeaderView, QMenu, QCheckBox, QWidgetAction,
|
|
2
|
+
QWidget, QVBoxLayout, QLineEdit, QHBoxLayout, QPushButton, QTableWidget)
|
|
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
|
+
filter_action = context_menu.addAction("Filter...")
|
|
93
|
+
|
|
94
|
+
# Add bar chart action if column is numeric
|
|
95
|
+
table = self.parent()
|
|
96
|
+
if table and table.rowCount() > 0:
|
|
97
|
+
try:
|
|
98
|
+
# Check if column contains numeric values
|
|
99
|
+
sample_value = table.item(0, logical_index).text()
|
|
100
|
+
float(sample_value.replace(',', '')) # Try converting to float
|
|
101
|
+
|
|
102
|
+
context_menu.addSeparator()
|
|
103
|
+
toggle_bar_action = context_menu.addAction(
|
|
104
|
+
"Remove Bar Chart" if logical_index in self.columns_with_bars
|
|
105
|
+
else "Add Bar Chart"
|
|
106
|
+
)
|
|
107
|
+
except (ValueError, AttributeError):
|
|
108
|
+
toggle_bar_action = None
|
|
109
|
+
else:
|
|
110
|
+
toggle_bar_action = None
|
|
111
|
+
|
|
112
|
+
# Show menu and get selected action
|
|
113
|
+
action = context_menu.exec(self.mapToGlobal(pos))
|
|
114
|
+
|
|
115
|
+
if not action:
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
table = self.parent()
|
|
119
|
+
if not table:
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
if action == sort_asc_action:
|
|
123
|
+
table.sortItems(logical_index, Qt.SortOrder.AscendingOrder)
|
|
124
|
+
elif action == sort_desc_action:
|
|
125
|
+
table.sortItems(logical_index, Qt.SortOrder.DescendingOrder)
|
|
126
|
+
elif action == filter_action:
|
|
127
|
+
self.show_filter_menu(logical_index)
|
|
128
|
+
elif action == toggle_bar_action:
|
|
129
|
+
self.toggle_bar_chart(logical_index)
|
|
130
|
+
|
|
131
|
+
def set_main_window(self, window):
|
|
132
|
+
"""Set the reference to the main window"""
|
|
133
|
+
self.main_window = window
|
|
134
|
+
|
|
135
|
+
def paintSection(self, painter, rect, logical_index):
|
|
136
|
+
"""Override paint section to add filter indicator"""
|
|
137
|
+
super().paintSection(painter, rect, logical_index)
|
|
138
|
+
|
|
139
|
+
if logical_index in self.active_filters:
|
|
140
|
+
# Draw background highlight for filtered columns
|
|
141
|
+
highlight_color = QColor(52, 152, 219, 30) # Light blue background
|
|
142
|
+
painter.fillRect(rect, highlight_color)
|
|
143
|
+
|
|
144
|
+
# Make icon larger and more visible
|
|
145
|
+
icon_size = min(rect.height() - 8, 24) # Larger icon, but not too large
|
|
146
|
+
margin = 6
|
|
147
|
+
icon_rect = QRect(
|
|
148
|
+
rect.right() - icon_size - margin,
|
|
149
|
+
rect.top() + (rect.height() - icon_size) // 2,
|
|
150
|
+
icon_size,
|
|
151
|
+
icon_size
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Draw filter icon with improved visibility
|
|
155
|
+
painter.save()
|
|
156
|
+
|
|
157
|
+
# Set up the pen for better visibility
|
|
158
|
+
pen = painter.pen()
|
|
159
|
+
pen.setWidth(3) # Thicker lines
|
|
160
|
+
pen.setColor(self.filter_icon_color)
|
|
161
|
+
painter.setPen(pen)
|
|
162
|
+
|
|
163
|
+
# Calculate points for larger funnel shape
|
|
164
|
+
points = [
|
|
165
|
+
QPoint(icon_rect.left(), icon_rect.top()),
|
|
166
|
+
QPoint(icon_rect.right(), icon_rect.top()),
|
|
167
|
+
QPoint(icon_rect.center().x() + icon_size//3, icon_rect.center().y()),
|
|
168
|
+
QPoint(icon_rect.center().x() + icon_size//3, icon_rect.bottom()),
|
|
169
|
+
QPoint(icon_rect.center().x() - icon_size//3, icon_rect.bottom()),
|
|
170
|
+
QPoint(icon_rect.center().x() - icon_size//3, icon_rect.center().y()),
|
|
171
|
+
QPoint(icon_rect.left(), icon_rect.top())
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
# Create and fill path
|
|
175
|
+
path = QPainterPath()
|
|
176
|
+
path.moveTo(float(points[0].x()), float(points[0].y()))
|
|
177
|
+
for point in points[1:]:
|
|
178
|
+
path.lineTo(float(point.x()), float(point.y()))
|
|
179
|
+
|
|
180
|
+
# Fill with semi-transparent blue
|
|
181
|
+
painter.fillPath(path, QBrush(QColor(52, 152, 219, 120))) # More opaque fill
|
|
182
|
+
|
|
183
|
+
# Draw outline
|
|
184
|
+
painter.drawPolyline(QPolygon(points))
|
|
185
|
+
|
|
186
|
+
# If multiple values are filtered, add a number
|
|
187
|
+
if len(self.active_filters[logical_index]) > 1:
|
|
188
|
+
# Draw number with better visibility
|
|
189
|
+
number_rect = QRect(icon_rect.left(), icon_rect.top(),
|
|
190
|
+
icon_rect.width(), icon_rect.height())
|
|
191
|
+
painter.setFont(QFont("Arial", icon_size//2, QFont.Weight.Bold))
|
|
192
|
+
|
|
193
|
+
# Draw text shadow for better contrast
|
|
194
|
+
painter.setPen(QColor("white"))
|
|
195
|
+
painter.drawText(number_rect.adjusted(1, 1, 1, 1),
|
|
196
|
+
Qt.AlignmentFlag.AlignCenter,
|
|
197
|
+
str(len(self.active_filters[logical_index])))
|
|
198
|
+
|
|
199
|
+
# Draw main text
|
|
200
|
+
painter.setPen(self.filter_icon_color)
|
|
201
|
+
painter.drawText(number_rect, Qt.AlignmentFlag.AlignCenter,
|
|
202
|
+
str(len(self.active_filters[logical_index])))
|
|
203
|
+
|
|
204
|
+
painter.restore()
|
|
205
|
+
|
|
206
|
+
# Draw a more visible indicator at the bottom of the header section
|
|
207
|
+
painter.save()
|
|
208
|
+
indicator_height = 3 # Thicker indicator line
|
|
209
|
+
indicator_rect = QRect(rect.left(), rect.bottom() - indicator_height,
|
|
210
|
+
rect.width(), indicator_height)
|
|
211
|
+
painter.fillRect(indicator_rect, self.filter_icon_color)
|
|
212
|
+
painter.restore()
|
|
213
|
+
|
|
214
|
+
def show_filter_menu(self, logical_index):
|
|
215
|
+
if not self.parent() or not isinstance(self.parent(), QTableWidget):
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
table = self.parent()
|
|
219
|
+
unique_values = set()
|
|
220
|
+
|
|
221
|
+
# Collect unique values from the column
|
|
222
|
+
for row in range(table.rowCount()):
|
|
223
|
+
item = table.item(row, logical_index)
|
|
224
|
+
if item and not table.isRowHidden(row):
|
|
225
|
+
unique_values.add(item.text())
|
|
226
|
+
|
|
227
|
+
# Create and show the filter menu
|
|
228
|
+
menu = QMenu(self)
|
|
229
|
+
menu.setStyleSheet("""
|
|
230
|
+
QMenu {
|
|
231
|
+
background-color: white;
|
|
232
|
+
border: 1px solid #BDC3C7;
|
|
233
|
+
padding: 5px;
|
|
234
|
+
}
|
|
235
|
+
QMenu::item {
|
|
236
|
+
padding: 5px 20px;
|
|
237
|
+
}
|
|
238
|
+
QMenu::item:selected {
|
|
239
|
+
background-color: #3498DB;
|
|
240
|
+
color: white;
|
|
241
|
+
}
|
|
242
|
+
QCheckBox {
|
|
243
|
+
padding: 5px;
|
|
244
|
+
}
|
|
245
|
+
QScrollArea {
|
|
246
|
+
border: none;
|
|
247
|
+
}
|
|
248
|
+
""")
|
|
249
|
+
|
|
250
|
+
# Add search box at the top
|
|
251
|
+
search_widget = QWidget(menu)
|
|
252
|
+
search_layout = QVBoxLayout(search_widget)
|
|
253
|
+
search_edit = QLineEdit(search_widget)
|
|
254
|
+
search_edit.setPlaceholderText("Search values...")
|
|
255
|
+
search_layout.addWidget(search_edit)
|
|
256
|
+
|
|
257
|
+
# Add action for search widget
|
|
258
|
+
search_action = QWidgetAction(menu)
|
|
259
|
+
search_action.setDefaultWidget(search_widget)
|
|
260
|
+
menu.addAction(search_action)
|
|
261
|
+
menu.addSeparator()
|
|
262
|
+
|
|
263
|
+
# Add "Select All" checkbox
|
|
264
|
+
select_all = QCheckBox("Select All", menu)
|
|
265
|
+
select_all.setChecked(True)
|
|
266
|
+
select_all_action = QWidgetAction(menu)
|
|
267
|
+
select_all_action.setDefaultWidget(select_all)
|
|
268
|
+
menu.addAction(select_all_action)
|
|
269
|
+
menu.addSeparator()
|
|
270
|
+
|
|
271
|
+
# Create scrollable area for checkboxes
|
|
272
|
+
scroll_widget = QWidget(menu)
|
|
273
|
+
scroll_layout = QVBoxLayout(scroll_widget)
|
|
274
|
+
scroll_layout.setSpacing(2)
|
|
275
|
+
scroll_layout.setContentsMargins(0, 0, 0, 0)
|
|
276
|
+
|
|
277
|
+
# Add checkboxes for unique values
|
|
278
|
+
value_checkboxes = {}
|
|
279
|
+
for value in sorted(unique_values):
|
|
280
|
+
checkbox = QCheckBox(str(value), scroll_widget)
|
|
281
|
+
# Set checked state based on active filters
|
|
282
|
+
checkbox.setChecked(logical_index not in self.active_filters or
|
|
283
|
+
value in self.active_filters[logical_index])
|
|
284
|
+
value_checkboxes[value] = checkbox
|
|
285
|
+
scroll_layout.addWidget(checkbox)
|
|
286
|
+
|
|
287
|
+
# Add scrollable area to menu
|
|
288
|
+
scroll_action = QWidgetAction(menu)
|
|
289
|
+
scroll_action.setDefaultWidget(scroll_widget)
|
|
290
|
+
menu.addAction(scroll_action)
|
|
291
|
+
|
|
292
|
+
# Connect search box to filter checkboxes
|
|
293
|
+
def filter_checkboxes(text):
|
|
294
|
+
for value, checkbox in value_checkboxes.items():
|
|
295
|
+
checkbox.setVisible(text.lower() in str(value).lower())
|
|
296
|
+
|
|
297
|
+
search_edit.textChanged.connect(filter_checkboxes)
|
|
298
|
+
|
|
299
|
+
# Connect select all to other checkboxes
|
|
300
|
+
def toggle_all(state):
|
|
301
|
+
for checkbox in value_checkboxes.values():
|
|
302
|
+
if not checkbox.isHidden(): # Only toggle visible checkboxes
|
|
303
|
+
checkbox.setChecked(state)
|
|
304
|
+
|
|
305
|
+
select_all.stateChanged.connect(toggle_all)
|
|
306
|
+
|
|
307
|
+
# Add Apply and Clear buttons
|
|
308
|
+
menu.addSeparator()
|
|
309
|
+
apply_button = QPushButton("Apply Filter", menu)
|
|
310
|
+
apply_button.setStyleSheet("""
|
|
311
|
+
QPushButton {
|
|
312
|
+
background-color: #2ECC71;
|
|
313
|
+
color: white;
|
|
314
|
+
border: none;
|
|
315
|
+
padding: 5px 15px;
|
|
316
|
+
border-radius: 3px;
|
|
317
|
+
}
|
|
318
|
+
QPushButton:hover {
|
|
319
|
+
background-color: #27AE60;
|
|
320
|
+
}
|
|
321
|
+
""")
|
|
322
|
+
|
|
323
|
+
clear_button = QPushButton("Clear Filter", menu)
|
|
324
|
+
clear_button.setStyleSheet("""
|
|
325
|
+
QPushButton {
|
|
326
|
+
background-color: #E74C3C;
|
|
327
|
+
color: white;
|
|
328
|
+
border: none;
|
|
329
|
+
padding: 5px 15px;
|
|
330
|
+
border-radius: 3px;
|
|
331
|
+
}
|
|
332
|
+
QPushButton:hover {
|
|
333
|
+
background-color: #C0392B;
|
|
334
|
+
}
|
|
335
|
+
""")
|
|
336
|
+
|
|
337
|
+
button_widget = QWidget(menu)
|
|
338
|
+
button_layout = QHBoxLayout(button_widget)
|
|
339
|
+
button_layout.addWidget(apply_button)
|
|
340
|
+
button_layout.addWidget(clear_button)
|
|
341
|
+
|
|
342
|
+
button_action = QWidgetAction(menu)
|
|
343
|
+
button_action.setDefaultWidget(button_widget)
|
|
344
|
+
menu.addAction(button_action)
|
|
345
|
+
|
|
346
|
+
def apply_filter():
|
|
347
|
+
# Get selected values
|
|
348
|
+
selected_values = {value for value, checkbox in value_checkboxes.items()
|
|
349
|
+
if checkbox.isChecked()}
|
|
350
|
+
|
|
351
|
+
if len(selected_values) < len(unique_values):
|
|
352
|
+
# Store active filter only if not all values are selected
|
|
353
|
+
self.active_filters[logical_index] = selected_values
|
|
354
|
+
else:
|
|
355
|
+
# Remove filter if all values are selected
|
|
356
|
+
self.active_filters.pop(logical_index, None)
|
|
357
|
+
|
|
358
|
+
# Apply all active filters
|
|
359
|
+
self.apply_all_filters(table)
|
|
360
|
+
|
|
361
|
+
menu.close()
|
|
362
|
+
self.updateSection(logical_index) # Redraw section to show/hide filter icon
|
|
363
|
+
|
|
364
|
+
def clear_filter():
|
|
365
|
+
# Remove filter for this column
|
|
366
|
+
if logical_index in self.active_filters:
|
|
367
|
+
del self.active_filters[logical_index]
|
|
368
|
+
|
|
369
|
+
# Apply remaining filters
|
|
370
|
+
self.apply_all_filters(table)
|
|
371
|
+
|
|
372
|
+
menu.close()
|
|
373
|
+
self.updateSection(logical_index) # Redraw section to hide filter icon
|
|
374
|
+
|
|
375
|
+
apply_button.clicked.connect(apply_filter)
|
|
376
|
+
clear_button.clicked.connect(clear_filter)
|
|
377
|
+
|
|
378
|
+
# Show menu under the header section
|
|
379
|
+
header_pos = self.mapToGlobal(self.geometry().bottomLeft())
|
|
380
|
+
header_pos.setX(header_pos.x() + self.sectionPosition(logical_index))
|
|
381
|
+
menu.exec(header_pos)
|
|
382
|
+
|
|
383
|
+
def apply_all_filters(self, table):
|
|
384
|
+
"""Apply all active filters to the table"""
|
|
385
|
+
# Show all rows first
|
|
386
|
+
for row in range(table.rowCount()):
|
|
387
|
+
table.setRowHidden(row, False)
|
|
388
|
+
|
|
389
|
+
# Apply each active filter
|
|
390
|
+
for col_idx, allowed_values in self.active_filters.items():
|
|
391
|
+
for row in range(table.rowCount()):
|
|
392
|
+
item = table.item(row, col_idx)
|
|
393
|
+
if item and not table.isRowHidden(row):
|
|
394
|
+
table.setRowHidden(row, item.text() not in allowed_values)
|
|
395
|
+
|
|
396
|
+
# Update status bar with visible row count
|
|
397
|
+
if self.main_window:
|
|
398
|
+
visible_rows = sum(1 for row in range(table.rowCount())
|
|
399
|
+
if not table.isRowHidden(row))
|
|
400
|
+
total_filters = len(self.active_filters)
|
|
401
|
+
filter_text = f" ({total_filters} filter{'s' if total_filters != 1 else ''} active)" if total_filters > 0 else ""
|
|
402
|
+
self.main_window.statusBar().showMessage(
|
|
403
|
+
f"Showing {visible_rows:,} rows{filter_text}")
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlshell
|
|
3
|
+
Version: 0.1.9
|
|
4
|
+
Summary: A powerful SQL shell with GUI interface for data analysis
|
|
5
|
+
Home-page: https://github.com/yourusername/sqlshell
|
|
6
|
+
Author: SQLShell Team
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/oyvinrog/SQLShell
|
|
9
|
+
Keywords: sql,data analysis,gui,duckdb
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: pandas>=2.0.0
|
|
20
|
+
Requires-Dist: numpy>=1.24.0
|
|
21
|
+
Requires-Dist: PyQt6>=6.4.0
|
|
22
|
+
Requires-Dist: duckdb>=0.9.0
|
|
23
|
+
Requires-Dist: openpyxl>=3.1.0
|
|
24
|
+
Requires-Dist: pyarrow>=14.0.1
|
|
25
|
+
Requires-Dist: fastparquet>=2023.10.1
|
|
26
|
+
Requires-Dist: xlrd>=2.0.1
|
|
27
|
+
Dynamic: home-page
|
|
28
|
+
Dynamic: requires-python
|
|
29
|
+
|
|
30
|
+
# SQLShell
|
|
31
|
+
|
|
32
|
+
<div align="center">
|
|
33
|
+
|
|
34
|
+
<img src="sqlshell_logo.png" alt="SQLShell Logo" width="256" height="256">
|
|
35
|
+
|
|
36
|
+
**A modern SQL REPL interface for seamless querying of Excel, Parquet, and SQLite databases**
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
## 🚀 Key Features
|
|
43
|
+
|
|
44
|
+
- **Interactive SQL Interface** - Rich syntax highlighting for enhanced query writing
|
|
45
|
+
- **DuckDB Integration** - Built-in support for local DuckDB database (pool.db)
|
|
46
|
+
- **Multi-Format Support** - Import and query Excel (.xlsx, .xls), CSV, and Parquet files effortlessly
|
|
47
|
+
- **Modern UI** - Clean, tabular results display with intuitive controls
|
|
48
|
+
- **Productivity Tools** - Streamlined workflow with keyboard shortcuts (e.g., Ctrl+Enter for query execution)
|
|
49
|
+
- **Professional Design** - Human-readable interface with optimized graphics
|
|
50
|
+
|
|
51
|
+
## 📦 Installation
|
|
52
|
+
|
|
53
|
+
### Linux Setup with Virtual Environment
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Create and activate virtual environment
|
|
57
|
+
python3 -m venv ~/.venv/sqlshell
|
|
58
|
+
source ~/.venv/sqlshell/bin/activate
|
|
59
|
+
|
|
60
|
+
# Install SQLShell
|
|
61
|
+
pip install sqlshell
|
|
62
|
+
|
|
63
|
+
# Configure shell alias
|
|
64
|
+
echo 'alias sqls="~/.venv/sqlshell/bin/sqls"' >> ~/.bashrc # or ~/.zshrc for Zsh
|
|
65
|
+
source ~/.bashrc # or source ~/.zshrc
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Windows Quick Start
|
|
69
|
+
SQLShell is immediately available via the `sqls` command after installation:
|
|
70
|
+
```bash
|
|
71
|
+
pip install sqlshell
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 🎯 Getting Started
|
|
75
|
+
|
|
76
|
+
1. **Launch the Application**
|
|
77
|
+
```bash
|
|
78
|
+
sqls
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
2. **Database Connection**
|
|
82
|
+
- SQLShell automatically connects to a local DuckDB database named 'pool.db'
|
|
83
|
+
|
|
84
|
+
3. **Working with Data Files**
|
|
85
|
+
- Click "Load Files" to select your Excel, CSV, or Parquet files
|
|
86
|
+
- File contents are loaded as queryable SQL tables
|
|
87
|
+
- Query using standard SQL syntax
|
|
88
|
+
|
|
89
|
+
4. **Query Execution**
|
|
90
|
+
- Enter SQL in the editor
|
|
91
|
+
- Execute using Ctrl+Enter or the "Execute" button
|
|
92
|
+
- View results in the structured output panel
|
|
93
|
+
|
|
94
|
+
## 📝 Query Examples
|
|
95
|
+
|
|
96
|
+
### Basic Join Operation
|
|
97
|
+
```sql
|
|
98
|
+
SELECT *
|
|
99
|
+
FROM sample_sales_data cd
|
|
100
|
+
INNER JOIN product_catalog pc ON pc.productid = cd.productid
|
|
101
|
+
LIMIT 3;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Multi-Statement Queries
|
|
105
|
+
```sql
|
|
106
|
+
-- Create a temporary view
|
|
107
|
+
CREATE OR REPLACE TEMPORARY VIEW test_v AS
|
|
108
|
+
SELECT *
|
|
109
|
+
FROM sample_sales_data cd
|
|
110
|
+
INNER JOIN product_catalog pc ON pc.productid = cd.productid;
|
|
111
|
+
|
|
112
|
+
-- Query the view
|
|
113
|
+
SELECT DISTINCT productid
|
|
114
|
+
FROM test_v;
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 💡 Pro Tips
|
|
118
|
+
|
|
119
|
+
- Use temporary views for complex query organization
|
|
120
|
+
- Leverage keyboard shortcuts for efficient workflow
|
|
121
|
+
- Explore the multi-format support for various data sources
|
|
122
|
+
- Create multiple tabs for parallel query development
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
sqlshell/__init__.py,sha256=GAZ3g4YsExb-aFyN0a77whBxRRk4XMGJYakvpeKbxdg,164
|
|
2
|
+
sqlshell/create_test_data.py,sha256=AzrEsKlZsR1H1Xlp7IKOzWQ2N6FsVISJbc6CerI8IJ0,1954
|
|
3
|
+
sqlshell/editor.py,sha256=LSUu1woAXXRNx-iquYY0_tjybOdaor-wSZfoHTEz5jk,36768
|
|
4
|
+
sqlshell/main.py,sha256=n6d5lYE0bfeWHB-9pbMpAm3GPz0t225EMqW1CYAqLp0,103613
|
|
5
|
+
sqlshell/query_tab.py,sha256=cvuaqPdwdhPo1O5jRTyyxYoCOJx0RpFClwBb7pv-Ivw,7095
|
|
6
|
+
sqlshell/setup.py,sha256=kaBlE81C38Sp7M76BinoBqbZ_XgbXc1fFFAvU6kZxVQ,1263
|
|
7
|
+
sqlshell/splash_screen.py,sha256=K0Ku_nXJWmWSnVEh2OttIthRZcnUoY_tmjIAWIWLm7Y,17604
|
|
8
|
+
sqlshell/sqlshell_demo.png,sha256=dPp9J1FVqQVfrh-gekosuha2Jw2p2--wxbOmt2kr7fg,133550
|
|
9
|
+
sqlshell/syntax_highlighter.py,sha256=mPwsD8N4XzAUx0IgwlelyfjUhe0xmH0Ug3UI9hTcHz0,5861
|
|
10
|
+
sqlshell/data/create_test_data.py,sha256=sUTcf50V8-bVwYV2VNTLK65c-iHiU4wb99By67I10zM,5404
|
|
11
|
+
sqlshell/db/__init__.py,sha256=AJGRkywFCnJliwfOBvtE_ISXjdESkRea7lBFM5KjuTU,152
|
|
12
|
+
sqlshell/db/database_manager.py,sha256=a3Uq8Lqxamvk4mikaZ3MNCY5Ft_Vv8v33cdToNzBLNk,29548
|
|
13
|
+
sqlshell/resources/__init__.py,sha256=VLTJ_5pUHhctRiV8UZDvG-jnsjgT6JQvW-ZPzIJqBIY,44
|
|
14
|
+
sqlshell/resources/create_icon.py,sha256=O7idVEKwmSXxLUsbeRn6zcYVQLPSdJi98nGamTgXiM4,4905
|
|
15
|
+
sqlshell/resources/create_splash.py,sha256=t1KK43Y0pHKGcdRkbnZgV6_y1c1C0THHQl5_fmpC2gQ,3347
|
|
16
|
+
sqlshell/resources/icon.png,sha256=l7MI1PWCED1XzsRgUjPR3A9pmbZ253tghd5s_0lJBMs,3173
|
|
17
|
+
sqlshell/resources/logo_large.png,sha256=pjLs6kXCy8gW8Iiq1gb6ZLnJEQ7_2GxtoJ_HDZ0_ERQ,31080
|
|
18
|
+
sqlshell/resources/logo_medium.png,sha256=brEV-YLgKS4RSxdZBgrwq_MvMA9zpsYvvvWgchUdjK4,14087
|
|
19
|
+
sqlshell/resources/logo_small.png,sha256=X4oQwj1k4OTbY785hWUIR12f1yAduV-USNzEu7aqkHs,6677
|
|
20
|
+
sqlshell/resources/splash_screen.gif,sha256=H24DQBdK1EsqQTWZkgInjM5ouOzY4cMesUoza10auNg,3070484
|
|
21
|
+
sqlshell/sqlshell/__init__.py,sha256=6Wp5nabfTzH5rkC-2jYo_ZjCuw8utmj21Jpy8vBuliI,100
|
|
22
|
+
sqlshell/sqlshell/create_test_data.py,sha256=TFXgWeK1l3l_c4Dg38yS8Df4sBUfOZcBucXngtpThvk,4624
|
|
23
|
+
sqlshell/sqlshell/create_test_databases.py,sha256=oqryFJJahqLFsAjBFM4r9Fe1ea7djDcRpT9U_aBf7PU,3573
|
|
24
|
+
sqlshell/ui/__init__.py,sha256=2CsTDAvRZJ99gkjs3-rdwkxyGVAKXX6ueOhPdP1VXQc,206
|
|
25
|
+
sqlshell/ui/bar_chart_delegate.py,sha256=tbtIt2ZqPIcYWNJzpONpYa0CYURkLdjkg23TI7TmOKY,1881
|
|
26
|
+
sqlshell/ui/filter_header.py,sha256=c4Mg1J1yTUfrnT9C-xDWHhcauRsgU3WNfvVInv1J814,16074
|
|
27
|
+
sqlshell-0.1.9.dist-info/METADATA,sha256=xiGeZnTbFrzPbG0wtCLgxQvqFZlya9gaxRs4yc9qDhg,3518
|
|
28
|
+
sqlshell-0.1.9.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
29
|
+
sqlshell-0.1.9.dist-info/entry_points.txt,sha256=Kd0fOvyOW7UiTgTVY7abVOmDIH2Y2nawGTp5kVadac4,44
|
|
30
|
+
sqlshell-0.1.9.dist-info/top_level.txt,sha256=ahwsMFhvAqI97ZkT2xvHL5iZCO1p13mNiUOFkdSFwms,9
|
|
31
|
+
sqlshell-0.1.9.dist-info/RECORD,,
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.2
|
|
2
|
-
Name: sqlshell
|
|
3
|
-
Version: 0.1.6
|
|
4
|
-
Summary: A powerful SQL shell with GUI interface for data analysis
|
|
5
|
-
Home-page: https://github.com/yourusername/sqlshell
|
|
6
|
-
Author: SQLShell Team
|
|
7
|
-
License: MIT
|
|
8
|
-
Project-URL: Homepage, https://github.com/oyvinrog/SQLShell
|
|
9
|
-
Keywords: sql,data analysis,gui,duckdb
|
|
10
|
-
Classifier: Development Status :: 3 - Alpha
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
-
Requires-Python: >=3.8
|
|
18
|
-
Description-Content-Type: text/markdown
|
|
19
|
-
Requires-Dist: pandas>=2.0.0
|
|
20
|
-
Requires-Dist: numpy>=1.24.0
|
|
21
|
-
Requires-Dist: PyQt6>=6.4.0
|
|
22
|
-
Requires-Dist: duckdb>=0.9.0
|
|
23
|
-
Requires-Dist: openpyxl>=3.1.0
|
|
24
|
-
Requires-Dist: pyarrow>=14.0.1
|
|
25
|
-
Requires-Dist: fastparquet>=2023.10.1
|
|
26
|
-
Requires-Dist: xlrd>=2.0.1
|
|
27
|
-
Dynamic: home-page
|
|
28
|
-
Dynamic: requires-python
|
|
29
|
-
|
|
30
|
-
# SQL Shell
|
|
31
|
-
|
|
32
|
-
A GUI application that provides a SQL REPL interface for querying Excel and parquet files (more to come!)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-

|
|
36
|
-
|
|
37
|
-
## Features
|
|
38
|
-
|
|
39
|
-
- SQL query interface with syntax highlighting
|
|
40
|
-
- Support for querying local DuckDB database (pool.db)
|
|
41
|
-
- Import and query Excel files (.xlsx, .xls) and CSV files
|
|
42
|
-
- Results displayed in a clear, tabular format
|
|
43
|
-
- Keyboard shortcuts (Ctrl+Enter to execute queries)
|
|
44
|
-
|
|
45
|
-
## Installation
|
|
46
|
-
|
|
47
|
-
1. Make sure you have Python 3.8 or newer installed
|
|
48
|
-
2. Install the required dependencies:
|
|
49
|
-
```bash
|
|
50
|
-
pip install -r requirements.txt
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
You can also do:
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
pip install sqlshell
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## Usage
|
|
60
|
-
|
|
61
|
-
1. Run the application:
|
|
62
|
-
```bash
|
|
63
|
-
python sqls.py
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
2. The application will automatically connect to a local DuckDB database named 'pool.db'
|
|
67
|
-
|
|
68
|
-
3. To query Excel files:
|
|
69
|
-
- Click the "Browse Excel" button
|
|
70
|
-
- Select your Excel file
|
|
71
|
-
- The file will be loaded as a table named 'imported_data'
|
|
72
|
-
- Query the data using SQL commands (e.g., `SELECT * FROM imported_data`)
|
|
73
|
-
|
|
74
|
-
4. Enter SQL queries in the top text area
|
|
75
|
-
- Press Ctrl+Enter or click "Execute" to run the query
|
|
76
|
-
- Results will be displayed in the bottom panel
|
|
77
|
-
|
|
78
|
-
## Example Queries
|
|
79
|
-
|
|
80
|
-
```sql
|
|
81
|
-
select * from sample_sales_data cd inner join product_catalog pc on pc.productid = cd.productid limit 3
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
you can also do multiple statements, i.e:
|
|
85
|
-
|
|
86
|
-
```sql
|
|
87
|
-
create or replace temporary view test_v as
|
|
88
|
-
select * from sample_sales_data cd
|
|
89
|
-
inner join product_catalog pc on pc.productid = cd.productid;
|
|
90
|
-
|
|
91
|
-
select distinct productid from test_v ;
|
|
92
|
-
```
|
sqlshell-0.1.6.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
sqlshell/__init__.py,sha256=OyPOfZIsEkW8eSHd0zAf6BT31-WwzDpONFBbeXV7kDc,129
|
|
2
|
-
sqlshell/main.py,sha256=DuCJqWVxcRlOnPjV1h7LwC49yXZSRFcBdMsgldFG4Fc,60536
|
|
3
|
-
sqlshell/setup.py,sha256=bAIXTpgAHhBRmPdT13Klzq16cjd4w4NOYSbyV_rxjlQ,1245
|
|
4
|
-
sqlshell/sqlshell/__init__.py,sha256=6Wp5nabfTzH5rkC-2jYo_ZjCuw8utmj21Jpy8vBuliI,100
|
|
5
|
-
sqlshell/sqlshell/create_test_data.py,sha256=sUTcf50V8-bVwYV2VNTLK65c-iHiU4wb99By67I10zM,5404
|
|
6
|
-
sqlshell/sqlshell/create_test_databases.py,sha256=oqryFJJahqLFsAjBFM4r9Fe1ea7djDcRpT9U_aBf7PU,3573
|
|
7
|
-
sqlshell-0.1.6.dist-info/METADATA,sha256=l0FXXswJr5NlZtD4YzzD345VAeYer8Mve8uhmcbana0,2559
|
|
8
|
-
sqlshell-0.1.6.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
9
|
-
sqlshell-0.1.6.dist-info/entry_points.txt,sha256=Kd0fOvyOW7UiTgTVY7abVOmDIH2Y2nawGTp5kVadac4,44
|
|
10
|
-
sqlshell-0.1.6.dist-info/top_level.txt,sha256=ahwsMFhvAqI97ZkT2xvHL5iZCO1p13mNiUOFkdSFwms,9
|
|
11
|
-
sqlshell-0.1.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|