sqlshell 0.1.0__tar.gz

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-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 SQLShell Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include sqlshell *.py
4
+ recursive-include sqlshell *.db
5
+ recursive-include sqlshell/test_data *
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.2
2
+ Name: sqlshell
3
+ Version: 0.1.0
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/yourusername/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
+ License-File: LICENSE
20
+ Requires-Dist: pandas>=2.0.0
21
+ Requires-Dist: numpy>=1.24.0
22
+ Requires-Dist: PyQt6>=6.4.0
23
+ Requires-Dist: duckdb>=0.9.0
24
+ Requires-Dist: openpyxl>=3.1.0
25
+ Requires-Dist: pyarrow>=14.0.1
26
+ Requires-Dist: fastparquet>=2023.10.1
27
+ Requires-Dist: xlrd>=2.0.1
28
+ Dynamic: home-page
29
+ Dynamic: requires-python
30
+
31
+ # SQLShell
32
+
33
+ A powerful SQL shell with GUI interface for data analysis. SQLShell provides an intuitive interface for working with various data formats (CSV, Excel, Parquet) using SQL queries powered by DuckDB.
34
+
35
+ ## Features
36
+
37
+ - Load and analyze data from CSV, Excel (.xlsx, .xls), and Parquet files
38
+ - Interactive GUI with syntax highlighting
39
+ - Real-time query results
40
+ - Table preview functionality
41
+ - Built-in test data generation
42
+ - Support for multiple concurrent table views
43
+
44
+ ## Installation
45
+
46
+ You can install SQLShell using pip:
47
+
48
+ ```bash
49
+ pip install sqlshell
50
+ ```
51
+
52
+ For development installation:
53
+
54
+ ```bash
55
+ git clone https://github.com/yourusername/sqlshell.git
56
+ cd sqlshell
57
+ pip install -e .
58
+ ```
59
+
60
+ ## Usage
61
+
62
+ After installation, you can start SQLShell from anywhere in your terminal by running:
63
+
64
+ ```bash
65
+ sqls
66
+ ```
67
+
68
+ This will open the GUI interface where you can:
69
+ 1. Load data files using the "Load Files" button
70
+ 2. Write SQL queries in the query editor
71
+ 3. Execute queries using the "Execute" button or Ctrl+Enter
72
+ 4. View results in the table view below
73
+ 5. Load sample test data using the "Test" button
74
+
75
+ ## Requirements
76
+
77
+ - Python 3.8 or higher
78
+ - PyQt6
79
+ - DuckDB
80
+ - Pandas
81
+ - Other dependencies will be automatically installed
82
+
83
+ ## License
84
+
85
+ This project is licensed under the MIT License - see the LICENSE file for details.
@@ -0,0 +1,55 @@
1
+ # SQLShell
2
+
3
+ A powerful SQL shell with GUI interface for data analysis. SQLShell provides an intuitive interface for working with various data formats (CSV, Excel, Parquet) using SQL queries powered by DuckDB.
4
+
5
+ ## Features
6
+
7
+ - Load and analyze data from CSV, Excel (.xlsx, .xls), and Parquet files
8
+ - Interactive GUI with syntax highlighting
9
+ - Real-time query results
10
+ - Table preview functionality
11
+ - Built-in test data generation
12
+ - Support for multiple concurrent table views
13
+
14
+ ## Installation
15
+
16
+ You can install SQLShell using pip:
17
+
18
+ ```bash
19
+ pip install sqlshell
20
+ ```
21
+
22
+ For development installation:
23
+
24
+ ```bash
25
+ git clone https://github.com/yourusername/sqlshell.git
26
+ cd sqlshell
27
+ pip install -e .
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ After installation, you can start SQLShell from anywhere in your terminal by running:
33
+
34
+ ```bash
35
+ sqls
36
+ ```
37
+
38
+ This will open the GUI interface where you can:
39
+ 1. Load data files using the "Load Files" button
40
+ 2. Write SQL queries in the query editor
41
+ 3. Execute queries using the "Execute" button or Ctrl+Enter
42
+ 4. View results in the table view below
43
+ 5. Load sample test data using the "Test" button
44
+
45
+ ## Requirements
46
+
47
+ - Python 3.8 or higher
48
+ - PyQt6
49
+ - DuckDB
50
+ - Pandas
51
+ - Other dependencies will be automatically installed
52
+
53
+ ## License
54
+
55
+ This project is licensed under the MIT License - see the LICENSE file for details.
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=45", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sqlshell"
7
+ version = "0.1.0"
8
+ description = "A powerful SQL shell with GUI interface for data analysis"
9
+ readme = "README.md"
10
+ authors = [
11
+ {name = "SQLShell Team"}
12
+ ]
13
+ requires-python = ">=3.8"
14
+ keywords = ["sql", "data analysis", "gui", "duckdb"]
15
+ license = {text = "MIT"}
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ ]
25
+ dependencies = [
26
+ "pandas>=2.0.0",
27
+ "numpy>=1.24.0",
28
+ "PyQt6>=6.4.0",
29
+ "duckdb>=0.9.0",
30
+ "openpyxl>=3.1.0",
31
+ "pyarrow>=14.0.1",
32
+ "fastparquet>=2023.10.1",
33
+ "xlrd>=2.0.1"
34
+ ]
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/yourusername/sqlshell"
38
+
39
+ [project.scripts]
40
+ sqls = "sqlshell.main:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,42 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="sqlshell",
5
+ version="0.1.0",
6
+ packages=find_packages(),
7
+ install_requires=[
8
+ 'pandas>=2.0.0',
9
+ 'numpy>=1.24.0',
10
+ 'PyQt6>=6.4.0',
11
+ 'duckdb>=0.9.0',
12
+ 'openpyxl>=3.1.0',
13
+ 'pyarrow>=14.0.1',
14
+ 'fastparquet>=2023.10.1',
15
+ 'xlrd>=2.0.1'
16
+ ],
17
+ entry_points={
18
+ 'console_scripts': [
19
+ 'sqls=sqlshell.main:main',
20
+ ],
21
+ },
22
+ author="SQLShell Team",
23
+ description="A powerful SQL shell with GUI interface for data analysis",
24
+ long_description=open('README.md').read(),
25
+ long_description_content_type="text/markdown",
26
+ keywords="sql, data analysis, gui, duckdb",
27
+ url="https://github.com/yourusername/sqlshell",
28
+ classifiers=[
29
+ "Development Status :: 3 - Alpha",
30
+ "Intended Audience :: Developers",
31
+ "Programming Language :: Python :: 3",
32
+ "Programming Language :: Python :: 3.8",
33
+ "Programming Language :: Python :: 3.9",
34
+ "Programming Language :: Python :: 3.10",
35
+ "Programming Language :: Python :: 3.11",
36
+ ],
37
+ python_requires=">=3.8",
38
+ include_package_data=True,
39
+ package_data={
40
+ 'sqlshell': ['*.db'],
41
+ },
42
+ )
@@ -0,0 +1,5 @@
1
+ """
2
+ SQLShell - A powerful SQL shell with GUI interface for data analysis
3
+ """
4
+
5
+ __version__ = "0.1.0"
@@ -0,0 +1,137 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ from datetime import datetime, timedelta
4
+ import os
5
+
6
+ # Set random seed for reproducibility
7
+ np.random.seed(42)
8
+
9
+ # Define output directory
10
+ OUTPUT_DIR = 'test_data'
11
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
12
+
13
+ def create_sales_data(num_records=1000):
14
+ # Generate dates for the last 365 days
15
+ end_date = datetime.now()
16
+ start_date = end_date - timedelta(days=365)
17
+ dates = [start_date + timedelta(days=x) for x in range(366)]
18
+ random_dates = np.random.choice(dates, num_records)
19
+
20
+ # Create product data
21
+ products = ['Laptop', 'Smartphone', 'Tablet', 'Monitor', 'Keyboard', 'Mouse', 'Headphones', 'Printer']
22
+ product_prices = {
23
+ 'Laptop': (800, 2000),
24
+ 'Smartphone': (400, 1200),
25
+ 'Tablet': (200, 800),
26
+ 'Monitor': (150, 500),
27
+ 'Keyboard': (20, 150),
28
+ 'Mouse': (10, 80),
29
+ 'Headphones': (30, 300),
30
+ 'Printer': (100, 400)
31
+ }
32
+
33
+ # Generate random data
34
+ data = {
35
+ 'OrderID': range(1, num_records + 1),
36
+ 'Date': random_dates,
37
+ 'ProductID': np.random.randint(1, len(products) + 1, num_records), # Changed to ProductID for joining
38
+ 'Quantity': np.random.randint(1, 11, num_records),
39
+ 'CustomerID': np.random.randint(1, 201, num_records),
40
+ 'Region': np.random.choice(['North', 'South', 'East', 'West'], num_records)
41
+ }
42
+
43
+ # Calculate prices based on product
44
+ product_list = [products[pid-1] for pid in data['ProductID']]
45
+ data['Price'] = [np.random.uniform(product_prices[p][0], product_prices[p][1])
46
+ for p in product_list]
47
+ data['TotalAmount'] = [price * qty for price, qty in zip(data['Price'], data['Quantity'])]
48
+
49
+ # Create DataFrame
50
+ df = pd.DataFrame(data)
51
+
52
+ # Round numerical columns
53
+ df['Price'] = df['Price'].round(2)
54
+ df['TotalAmount'] = df['TotalAmount'].round(2)
55
+
56
+ # Sort by Date
57
+ return df.sort_values('Date')
58
+
59
+ def create_customer_data(num_customers=200):
60
+ # Generate customer data
61
+ data = {
62
+ 'CustomerID': range(1, num_customers + 1),
63
+ 'FirstName': [f'Customer{i}' for i in range(1, num_customers + 1)],
64
+ 'LastName': [f'Lastname{i}' for i in range(1, num_customers + 1)],
65
+ 'Email': [f'customer{i}@example.com' for i in range(1, num_customers + 1)],
66
+ 'JoinDate': [datetime.now() - timedelta(days=np.random.randint(1, 1000))
67
+ for _ in range(num_customers)],
68
+ 'CustomerType': np.random.choice(['Regular', 'Premium', 'VIP'], num_customers),
69
+ 'CreditScore': np.random.randint(300, 851, num_customers)
70
+ }
71
+
72
+ return pd.DataFrame(data)
73
+
74
+ def create_product_data():
75
+ # Create detailed product information
76
+ products = {
77
+ 'ProductID': range(1, 9),
78
+ 'ProductName': ['Laptop', 'Smartphone', 'Tablet', 'Monitor', 'Keyboard', 'Mouse', 'Headphones', 'Printer'],
79
+ 'Category': ['Computers', 'Mobile', 'Mobile', 'Accessories', 'Accessories', 'Accessories', 'Audio', 'Peripherals'],
80
+ 'Brand': ['TechPro', 'MobileX', 'TabletCo', 'ViewMax', 'TypeMaster', 'ClickPro', 'SoundMax', 'PrintPro'],
81
+ 'StockQuantity': np.random.randint(50, 500, 8),
82
+ 'MinPrice': [800, 400, 200, 150, 20, 10, 30, 100],
83
+ 'MaxPrice': [2000, 1200, 800, 500, 150, 80, 300, 400],
84
+ 'Weight_kg': [2.5, 0.2, 0.5, 3.0, 0.8, 0.1, 0.3, 5.0],
85
+ 'WarrantyMonths': [24, 12, 12, 36, 12, 12, 24, 12]
86
+ }
87
+
88
+ return pd.DataFrame(products)
89
+
90
+ if __name__ == '__main__':
91
+ # Create and save sales data
92
+ sales_df = create_sales_data()
93
+ sales_output = os.path.join(OUTPUT_DIR, 'sample_sales_data.xlsx')
94
+ sales_df.to_excel(sales_output, index=False)
95
+ print(f"Created sales data in '{sales_output}'")
96
+ print(f"Number of sales records: {len(sales_df)}")
97
+
98
+ # Create and save customer data as parquet
99
+ customer_df = create_customer_data()
100
+ customer_output = os.path.join(OUTPUT_DIR, 'customer_data.parquet')
101
+ customer_df.to_parquet(customer_output, index=False)
102
+ print(f"\nCreated customer data in '{customer_output}'")
103
+ print(f"Number of customers: {len(customer_df)}")
104
+
105
+ # Create and save product data
106
+ product_df = create_product_data()
107
+ product_output = os.path.join(OUTPUT_DIR, 'product_catalog.xlsx')
108
+ product_df.to_excel(product_output, index=False)
109
+ print(f"\nCreated product catalog in '{product_output}'")
110
+ print(f"Number of products: {len(product_df)}")
111
+
112
+ # Print sample queries
113
+ print("\nSample SQL queries for joining the data:")
114
+ print("""
115
+ -- Join sales with customer data
116
+ SELECT s.*, c.FirstName, c.LastName, c.CustomerType
117
+ FROM test_data.sample_sales_data s
118
+ JOIN test_data.customer_data c ON s.CustomerID = c.CustomerID;
119
+
120
+ -- Join sales with product data
121
+ SELECT s.*, p.ProductName, p.Category, p.Brand
122
+ FROM test_data.sample_sales_data s
123
+ JOIN test_data.product_catalog p ON s.ProductID = p.ProductID;
124
+
125
+ -- Three-way join with aggregation
126
+ SELECT
127
+ p.Category,
128
+ c.CustomerType,
129
+ COUNT(*) as NumOrders,
130
+ SUM(s.TotalAmount) as TotalRevenue,
131
+ AVG(s.Quantity) as AvgQuantity
132
+ FROM test_data.sample_sales_data s
133
+ JOIN test_data.customer_data c ON s.CustomerID = c.CustomerID
134
+ JOIN test_data.product_catalog p ON s.ProductID = p.ProductID
135
+ GROUP BY p.Category, c.CustomerType
136
+ ORDER BY p.Category, c.CustomerType;
137
+ """)
@@ -0,0 +1,346 @@
1
+ import sys
2
+ import os
3
+ import duckdb
4
+ import pandas as pd
5
+ from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
6
+ QHBoxLayout, QTextEdit, QPushButton, QFileDialog,
7
+ QLabel, QSplitter, QListWidget, QTableWidget,
8
+ QTableWidgetItem, QHeaderView)
9
+ from PyQt6.QtCore import Qt, QAbstractTableModel
10
+ from PyQt6.QtGui import QFont, QColor
11
+ import numpy as np
12
+ from datetime import datetime
13
+ from . import create_test_data # Import from the same package
14
+
15
+ class SQLShell(QMainWindow):
16
+ def __init__(self):
17
+ super().__init__()
18
+ self.conn = duckdb.connect('pool.db')
19
+ self.loaded_tables = {} # Keep track of loaded tables
20
+ self.init_ui()
21
+
22
+ def init_ui(self):
23
+ self.setWindowTitle('SQL Shell')
24
+ self.setGeometry(100, 100, 1400, 800)
25
+
26
+ # Create central widget and layout
27
+ central_widget = QWidget()
28
+ self.setCentralWidget(central_widget)
29
+ main_layout = QHBoxLayout(central_widget)
30
+
31
+ # Left panel for table list
32
+ left_panel = QWidget()
33
+ left_layout = QVBoxLayout(left_panel)
34
+
35
+ tables_label = QLabel("Loaded Tables:")
36
+ left_layout.addWidget(tables_label)
37
+
38
+ self.tables_list = QListWidget()
39
+ self.tables_list.itemClicked.connect(self.show_table_preview)
40
+ left_layout.addWidget(self.tables_list)
41
+
42
+ # Buttons for table management
43
+ table_buttons_layout = QHBoxLayout()
44
+ self.browse_btn = QPushButton('Load Files')
45
+ self.browse_btn.clicked.connect(self.browse_files)
46
+ self.remove_table_btn = QPushButton('Remove Selected')
47
+ self.remove_table_btn.clicked.connect(self.remove_selected_table)
48
+ self.test_btn = QPushButton('Test')
49
+ self.test_btn.clicked.connect(self.load_test_data)
50
+
51
+ table_buttons_layout.addWidget(self.browse_btn)
52
+ table_buttons_layout.addWidget(self.remove_table_btn)
53
+ table_buttons_layout.addWidget(self.test_btn)
54
+ left_layout.addLayout(table_buttons_layout)
55
+
56
+ # Right panel for query and results
57
+ right_panel = QWidget()
58
+ right_layout = QVBoxLayout(right_panel)
59
+
60
+ # Create splitter for query and results
61
+ splitter = QSplitter(Qt.Orientation.Vertical)
62
+
63
+ # Top part - Query section
64
+ query_widget = QWidget()
65
+ query_layout = QVBoxLayout(query_widget)
66
+
67
+ # Button row
68
+ button_layout = QHBoxLayout()
69
+ self.execute_btn = QPushButton('Execute (Ctrl+Enter)')
70
+ self.execute_btn.clicked.connect(self.execute_query)
71
+ self.clear_btn = QPushButton('Clear')
72
+ self.clear_btn.clicked.connect(self.clear_query)
73
+
74
+ button_layout.addWidget(self.execute_btn)
75
+ button_layout.addWidget(self.clear_btn)
76
+ button_layout.addStretch()
77
+
78
+ query_layout.addLayout(button_layout)
79
+
80
+ # Query input
81
+ self.query_edit = QTextEdit()
82
+ self.query_edit.setPlaceholderText("Enter your SQL query here...")
83
+ query_layout.addWidget(self.query_edit)
84
+
85
+ # Bottom part - Results section
86
+ results_widget = QWidget()
87
+ results_layout = QVBoxLayout(results_widget)
88
+
89
+ # Results header with row count
90
+ results_header = QWidget()
91
+ results_header_layout = QHBoxLayout(results_header)
92
+ self.results_label = QLabel("Results:")
93
+ self.row_count_label = QLabel("")
94
+ results_header_layout.addWidget(self.results_label)
95
+ results_header_layout.addWidget(self.row_count_label)
96
+ results_header_layout.addStretch()
97
+ results_layout.addWidget(results_header)
98
+
99
+ # Table widget for results
100
+ self.results_table = QTableWidget()
101
+ self.results_table.setSortingEnabled(True)
102
+ self.results_table.setAlternatingRowColors(True)
103
+ self.results_table.horizontalHeader().setStretchLastSection(True)
104
+ self.results_table.horizontalHeader().setSectionsMovable(True)
105
+ self.results_table.verticalHeader().setVisible(False)
106
+ results_layout.addWidget(self.results_table)
107
+
108
+ # Add widgets to splitter
109
+ splitter.addWidget(query_widget)
110
+ splitter.addWidget(results_widget)
111
+
112
+ # Set initial sizes for splitter
113
+ splitter.setSizes([300, 500])
114
+
115
+ right_layout.addWidget(splitter)
116
+
117
+ # Add panels to main layout
118
+ main_layout.addWidget(left_panel, 1)
119
+ main_layout.addWidget(right_panel, 4)
120
+
121
+ # Status bar
122
+ self.statusBar().showMessage('Ready')
123
+
124
+ def format_value(self, value):
125
+ """Format values for display"""
126
+ if pd.isna(value):
127
+ return 'NULL'
128
+ elif isinstance(value, (int, np.integer)):
129
+ return f"{value:,}"
130
+ elif isinstance(value, (float, np.floating)):
131
+ return f"{value:,.2f}"
132
+ elif isinstance(value, (datetime, pd.Timestamp)):
133
+ return value.strftime('%Y-%m-%d %H:%M:%S')
134
+ return str(value)
135
+
136
+ def populate_table(self, df):
137
+ """Populate the table widget with DataFrame content"""
138
+ if len(df) == 0:
139
+ self.results_table.setRowCount(0)
140
+ self.results_table.setColumnCount(0)
141
+ self.row_count_label.setText("No results")
142
+ return
143
+
144
+ # Set dimensions
145
+ self.results_table.setRowCount(len(df))
146
+ self.results_table.setColumnCount(len(df.columns))
147
+
148
+ # Set headers
149
+ self.results_table.setHorizontalHeaderLabels(df.columns)
150
+
151
+ # Populate data
152
+ for i, (_, row) in enumerate(df.iterrows()):
153
+ for j, value in enumerate(row):
154
+ formatted_value = self.format_value(value)
155
+ item = QTableWidgetItem(formatted_value)
156
+
157
+ # Set alignment based on data type
158
+ if isinstance(value, (int, float, np.integer, np.floating)):
159
+ item.setTextAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
160
+ else:
161
+ item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
162
+
163
+ # Make cells read-only
164
+ item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable)
165
+
166
+ self.results_table.setItem(i, j, item)
167
+
168
+ # Auto-adjust column widths while ensuring minimum and maximum sizes
169
+ self.results_table.resizeColumnsToContents()
170
+ for i in range(len(df.columns)):
171
+ width = self.results_table.columnWidth(i)
172
+ self.results_table.setColumnWidth(i, min(max(width, 50), 300))
173
+
174
+ # Update row count
175
+ row_text = "row" if len(df) == 1 else "rows"
176
+ self.row_count_label.setText(f"{len(df):,} {row_text}")
177
+
178
+ def browse_files(self):
179
+ file_names, _ = QFileDialog.getOpenFileNames(
180
+ self,
181
+ "Open Data Files",
182
+ "",
183
+ "Data Files (*.xlsx *.xls *.csv *.parquet);;Excel Files (*.xlsx *.xls);;CSV Files (*.csv);;Parquet Files (*.parquet);;All Files (*)"
184
+ )
185
+
186
+ for file_name in file_names:
187
+ try:
188
+ if file_name.endswith(('.xlsx', '.xls')):
189
+ df = pd.read_excel(file_name)
190
+ elif file_name.endswith('.csv'):
191
+ df = pd.read_csv(file_name)
192
+ elif file_name.endswith('.parquet'):
193
+ df = pd.read_parquet(file_name)
194
+ else:
195
+ raise ValueError("Unsupported file format")
196
+
197
+ # Generate table name from file name
198
+ base_name = os.path.splitext(os.path.basename(file_name))[0]
199
+ table_name = self.sanitize_table_name(base_name)
200
+
201
+ # Ensure unique table name
202
+ original_name = table_name
203
+ counter = 1
204
+ while table_name in self.loaded_tables:
205
+ table_name = f"{original_name}_{counter}"
206
+ counter += 1
207
+
208
+ # Register table in DuckDB
209
+ self.conn.register(table_name, df)
210
+ self.loaded_tables[table_name] = file_name
211
+
212
+ # Update UI
213
+ self.tables_list.addItem(f"{table_name} ({os.path.basename(file_name)})")
214
+ self.statusBar().showMessage(f'Loaded {file_name} as table "{table_name}"')
215
+
216
+ # Show preview of loaded data
217
+ preview_df = df.head()
218
+ self.populate_table(preview_df)
219
+ self.results_label.setText(f"Preview of {table_name}:")
220
+
221
+ except Exception as e:
222
+ self.statusBar().showMessage(f'Error loading file: {str(e)}')
223
+ self.results_table.setRowCount(0)
224
+ self.results_table.setColumnCount(0)
225
+ self.row_count_label.setText("")
226
+ self.results_label.setText(f"Error loading file: {str(e)}")
227
+
228
+ def sanitize_table_name(self, name):
229
+ # Replace invalid characters with underscores
230
+ import re
231
+ name = re.sub(r'[^a-zA-Z0-9_]', '_', name)
232
+ # Ensure it starts with a letter
233
+ if not name[0].isalpha():
234
+ name = 'table_' + name
235
+ return name.lower()
236
+
237
+ def remove_selected_table(self):
238
+ current_item = self.tables_list.currentItem()
239
+ if current_item:
240
+ table_name = current_item.text().split(' (')[0]
241
+ if table_name in self.loaded_tables:
242
+ # Remove from DuckDB
243
+ self.conn.execute(f'DROP VIEW IF EXISTS {table_name}')
244
+ # Remove from our tracking
245
+ del self.loaded_tables[table_name]
246
+ # Remove from list widget
247
+ self.tables_list.takeItem(self.tables_list.row(current_item))
248
+ self.statusBar().showMessage(f'Removed table "{table_name}"')
249
+ self.results_table.setRowCount(0)
250
+ self.results_table.setColumnCount(0)
251
+ self.row_count_label.setText("")
252
+ self.results_label.setText(f"Removed table: {table_name}")
253
+
254
+ def execute_query(self):
255
+ query = self.query_edit.toPlainText().strip()
256
+ if not query:
257
+ return
258
+
259
+ try:
260
+ result = self.conn.execute(query).fetchdf()
261
+ self.populate_table(result)
262
+ self.results_label.setText("Query Results:")
263
+ self.statusBar().showMessage('Query executed successfully')
264
+ except Exception as e:
265
+ self.results_table.setRowCount(0)
266
+ self.results_table.setColumnCount(0)
267
+ self.row_count_label.setText("")
268
+ self.results_label.setText(f"Error executing query: {str(e)}")
269
+ self.statusBar().showMessage('Error executing query')
270
+
271
+ def clear_query(self):
272
+ self.query_edit.clear()
273
+
274
+ def show_table_preview(self, item):
275
+ """Show a preview of the selected table"""
276
+ if item:
277
+ table_name = item.text().split(' (')[0]
278
+ try:
279
+ preview_df = self.conn.execute(f'SELECT * FROM {table_name} LIMIT 5').fetchdf()
280
+ self.populate_table(preview_df)
281
+ self.results_label.setText(f"Preview of {table_name}:")
282
+ self.statusBar().showMessage(f'Showing preview of table "{table_name}"')
283
+ except Exception as e:
284
+ self.results_table.setRowCount(0)
285
+ self.results_table.setColumnCount(0)
286
+ self.row_count_label.setText("")
287
+ self.results_label.setText(f"Error showing preview: {str(e)}")
288
+ self.statusBar().showMessage('Error showing table preview')
289
+
290
+ def keyPressEvent(self, event):
291
+ if event.key() == Qt.Key.Key_Return and event.modifiers() == Qt.KeyboardModifier.ControlModifier:
292
+ self.execute_query()
293
+ else:
294
+ super().keyPressEvent(event)
295
+
296
+ def load_test_data(self):
297
+ """Generate and load test data"""
298
+ try:
299
+ # Create test data directory if it doesn't exist
300
+ os.makedirs('test_data', exist_ok=True)
301
+
302
+ # Generate test data
303
+ sales_df = create_test_data.create_sales_data()
304
+ customer_df = create_test_data.create_customer_data()
305
+ product_df = create_test_data.create_product_data()
306
+
307
+ # Save test data
308
+ sales_df.to_excel('test_data/sample_sales_data.xlsx', index=False)
309
+ customer_df.to_parquet('test_data/customer_data.parquet', index=False)
310
+ product_df.to_excel('test_data/product_catalog.xlsx', index=False)
311
+
312
+ # Load the files into DuckDB
313
+ self.conn.register('sample_sales_data', sales_df)
314
+ self.conn.register('product_catalog', product_df)
315
+ self.conn.register('customer_data', customer_df)
316
+
317
+ # Update loaded tables tracking
318
+ self.loaded_tables['sample_sales_data'] = 'test_data/sample_sales_data.xlsx'
319
+ self.loaded_tables['product_catalog'] = 'test_data/product_catalog.xlsx'
320
+ self.loaded_tables['customer_data'] = 'test_data/customer_data.parquet'
321
+
322
+ # Update UI
323
+ self.tables_list.clear()
324
+ for table_name, file_path in self.loaded_tables.items():
325
+ self.tables_list.addItem(f"{table_name} ({os.path.basename(file_path)})")
326
+
327
+ # Set the sample query
328
+ self.query_edit.setText("select * from sample_sales_data cd inner join product_catalog pc on pc.productid = cd.productid limit 3")
329
+
330
+ self.statusBar().showMessage('Test data loaded successfully')
331
+
332
+ except Exception as e:
333
+ self.statusBar().showMessage(f'Error loading test data: {str(e)}')
334
+
335
+ def main():
336
+ app = QApplication(sys.argv)
337
+
338
+ # Set application style
339
+ app.setStyle('Fusion')
340
+
341
+ sql_shell = SQLShell()
342
+ sql_shell.show()
343
+ sys.exit(app.exec())
344
+
345
+ if __name__ == '__main__':
346
+ main()
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.2
2
+ Name: sqlshell
3
+ Version: 0.1.0
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/yourusername/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
+ License-File: LICENSE
20
+ Requires-Dist: pandas>=2.0.0
21
+ Requires-Dist: numpy>=1.24.0
22
+ Requires-Dist: PyQt6>=6.4.0
23
+ Requires-Dist: duckdb>=0.9.0
24
+ Requires-Dist: openpyxl>=3.1.0
25
+ Requires-Dist: pyarrow>=14.0.1
26
+ Requires-Dist: fastparquet>=2023.10.1
27
+ Requires-Dist: xlrd>=2.0.1
28
+ Dynamic: home-page
29
+ Dynamic: requires-python
30
+
31
+ # SQLShell
32
+
33
+ A powerful SQL shell with GUI interface for data analysis. SQLShell provides an intuitive interface for working with various data formats (CSV, Excel, Parquet) using SQL queries powered by DuckDB.
34
+
35
+ ## Features
36
+
37
+ - Load and analyze data from CSV, Excel (.xlsx, .xls), and Parquet files
38
+ - Interactive GUI with syntax highlighting
39
+ - Real-time query results
40
+ - Table preview functionality
41
+ - Built-in test data generation
42
+ - Support for multiple concurrent table views
43
+
44
+ ## Installation
45
+
46
+ You can install SQLShell using pip:
47
+
48
+ ```bash
49
+ pip install sqlshell
50
+ ```
51
+
52
+ For development installation:
53
+
54
+ ```bash
55
+ git clone https://github.com/yourusername/sqlshell.git
56
+ cd sqlshell
57
+ pip install -e .
58
+ ```
59
+
60
+ ## Usage
61
+
62
+ After installation, you can start SQLShell from anywhere in your terminal by running:
63
+
64
+ ```bash
65
+ sqls
66
+ ```
67
+
68
+ This will open the GUI interface where you can:
69
+ 1. Load data files using the "Load Files" button
70
+ 2. Write SQL queries in the query editor
71
+ 3. Execute queries using the "Execute" button or Ctrl+Enter
72
+ 4. View results in the table view below
73
+ 5. Load sample test data using the "Test" button
74
+
75
+ ## Requirements
76
+
77
+ - Python 3.8 or higher
78
+ - PyQt6
79
+ - DuckDB
80
+ - Pandas
81
+ - Other dependencies will be automatically installed
82
+
83
+ ## License
84
+
85
+ This project is licensed under the MIT License - see the LICENSE file for details.
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ setup.py
6
+ sqlshell/__init__.py
7
+ sqlshell/create_test_data.py
8
+ sqlshell/main.py
9
+ sqlshell.egg-info/PKG-INFO
10
+ sqlshell.egg-info/SOURCES.txt
11
+ sqlshell.egg-info/dependency_links.txt
12
+ sqlshell.egg-info/entry_points.txt
13
+ sqlshell.egg-info/requires.txt
14
+ sqlshell.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sqls = sqlshell.main:main
@@ -0,0 +1,8 @@
1
+ pandas>=2.0.0
2
+ numpy>=1.24.0
3
+ PyQt6>=6.4.0
4
+ duckdb>=0.9.0
5
+ openpyxl>=3.1.0
6
+ pyarrow>=14.0.1
7
+ fastparquet>=2023.10.1
8
+ xlrd>=2.0.1
@@ -0,0 +1 @@
1
+ sqlshell