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.
- sqlshell/__init__.py +84 -0
- sqlshell/__main__.py +4926 -0
- sqlshell/ai_autocomplete.py +392 -0
- sqlshell/ai_settings_dialog.py +337 -0
- sqlshell/context_suggester.py +768 -0
- sqlshell/create_test_data.py +152 -0
- sqlshell/data/create_test_data.py +137 -0
- sqlshell/db/__init__.py +6 -0
- sqlshell/db/database_manager.py +1318 -0
- sqlshell/db/export_manager.py +188 -0
- sqlshell/editor.py +1166 -0
- sqlshell/editor_integration.py +127 -0
- sqlshell/execution_handler.py +421 -0
- sqlshell/menus.py +262 -0
- sqlshell/notification_manager.py +370 -0
- sqlshell/query_tab.py +904 -0
- sqlshell/resources/__init__.py +1 -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/space_invaders.py +501 -0
- sqlshell/splash_screen.py +405 -0
- sqlshell/sqlshell/__init__.py +5 -0
- sqlshell/sqlshell/create_test_data.py +118 -0
- sqlshell/sqlshell/create_test_databases.py +96 -0
- sqlshell/sqlshell_demo.png +0 -0
- sqlshell/styles.py +257 -0
- sqlshell/suggester_integration.py +330 -0
- sqlshell/syntax_highlighter.py +124 -0
- sqlshell/table_list.py +996 -0
- sqlshell/ui/__init__.py +6 -0
- sqlshell/ui/bar_chart_delegate.py +49 -0
- sqlshell/ui/filter_header.py +469 -0
- sqlshell/utils/__init__.py +16 -0
- sqlshell/utils/profile_cn2.py +1661 -0
- sqlshell/utils/profile_column.py +2635 -0
- sqlshell/utils/profile_distributions.py +616 -0
- sqlshell/utils/profile_entropy.py +347 -0
- sqlshell/utils/profile_foreign_keys.py +779 -0
- sqlshell/utils/profile_keys.py +2834 -0
- sqlshell/utils/profile_ohe.py +934 -0
- sqlshell/utils/profile_ohe_advanced.py +754 -0
- sqlshell/utils/profile_ohe_comparison.py +237 -0
- sqlshell/utils/profile_prediction.py +926 -0
- sqlshell/utils/profile_similarity.py +876 -0
- sqlshell/utils/search_in_df.py +90 -0
- sqlshell/widgets.py +400 -0
- sqlshell-0.4.4.dist-info/METADATA +441 -0
- sqlshell-0.4.4.dist-info/RECORD +54 -0
- sqlshell-0.4.4.dist-info/WHEEL +5 -0
- sqlshell-0.4.4.dist-info/entry_points.txt +2 -0
- sqlshell-0.4.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout
|
|
2
|
+
from PyQt6.QtCore import Qt, QTimer, QPropertyAnimation, QEasingCurve, QPoint, QRect, pyqtProperty
|
|
3
|
+
from PyQt6.QtGui import QPainter, QColor, QFont, QMovie, QPainterPath, QLinearGradient, QPixmap
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
class AnimatedSplashScreen(QWidget):
|
|
7
|
+
def __init__(self):
|
|
8
|
+
super().__init__()
|
|
9
|
+
|
|
10
|
+
# Initialize properties for animations first
|
|
11
|
+
self._opacity = 0.0
|
|
12
|
+
self._progress = 0.0
|
|
13
|
+
self.next_widget = None
|
|
14
|
+
self.use_fallback = False
|
|
15
|
+
|
|
16
|
+
# Set up the window properties
|
|
17
|
+
self.setWindowFlags(
|
|
18
|
+
Qt.WindowType.WindowStaysOnTopHint |
|
|
19
|
+
Qt.WindowType.FramelessWindowHint |
|
|
20
|
+
Qt.WindowType.SplashScreen
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Set widget attributes for proper compositing
|
|
24
|
+
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
|
25
|
+
self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
|
|
26
|
+
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, False)
|
|
27
|
+
|
|
28
|
+
# Set fixed size
|
|
29
|
+
self.setFixedSize(400, 300)
|
|
30
|
+
|
|
31
|
+
# Center the splash screen on the screen
|
|
32
|
+
screen_geometry = self.screen().geometry()
|
|
33
|
+
self.move(
|
|
34
|
+
(screen_geometry.width() - self.width()) // 2,
|
|
35
|
+
(screen_geometry.height() - self.height()) // 2
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Create movie label first (background)
|
|
39
|
+
self.movie_label = QLabel(self)
|
|
40
|
+
self.movie_label.setGeometry(0, 0, self.width(), self.height())
|
|
41
|
+
|
|
42
|
+
# Create overlay for fade effect (between movie and content)
|
|
43
|
+
self.overlay = QLabel(self)
|
|
44
|
+
self.overlay.setStyleSheet("background-color: rgba(0, 0, 0, 0);")
|
|
45
|
+
self.overlay.setGeometry(0, 0, self.width(), self.height())
|
|
46
|
+
|
|
47
|
+
# Create text label for animated text
|
|
48
|
+
self.text_label = QLabel(self)
|
|
49
|
+
self.text_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
50
|
+
self.text_label.setStyleSheet("color: rgba(255, 255, 255, 0); background: transparent;")
|
|
51
|
+
self.text_label.setGeometry(0, 0, self.width(), self.height())
|
|
52
|
+
|
|
53
|
+
# Create layout
|
|
54
|
+
layout = QVBoxLayout(self)
|
|
55
|
+
layout.setContentsMargins(20, 140, 20, 20) # Increased top margin to accommodate title bar and logo
|
|
56
|
+
layout.setSpacing(10)
|
|
57
|
+
|
|
58
|
+
# Create background container for the subtitle
|
|
59
|
+
self.content_container = QWidget(self)
|
|
60
|
+
self.content_container.setStyleSheet("background: transparent;")
|
|
61
|
+
content_layout = QVBoxLayout(self.content_container)
|
|
62
|
+
content_layout.setContentsMargins(20, 5, 20, 20)
|
|
63
|
+
content_layout.setSpacing(5)
|
|
64
|
+
|
|
65
|
+
# Create subtitle label
|
|
66
|
+
self.subtitle_label = QLabel("Loading...", self.content_container)
|
|
67
|
+
self.subtitle_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
68
|
+
self.subtitle_label.setStyleSheet("""
|
|
69
|
+
QLabel {
|
|
70
|
+
color: #2C3E50;
|
|
71
|
+
font-size: 16px;
|
|
72
|
+
font-family: 'Segoe UI', Arial, sans-serif;
|
|
73
|
+
background: transparent;
|
|
74
|
+
}
|
|
75
|
+
""")
|
|
76
|
+
content_layout.addWidget(self.subtitle_label)
|
|
77
|
+
|
|
78
|
+
# Add content container to main layout
|
|
79
|
+
layout.addWidget(self.content_container)
|
|
80
|
+
|
|
81
|
+
# Create progress bar (always on top)
|
|
82
|
+
self.progress_bar = QLabel(self)
|
|
83
|
+
self.progress_bar.setFixedHeight(4)
|
|
84
|
+
self.progress_bar.setStyleSheet("background-color: #3498DB; border-radius: 2px;")
|
|
85
|
+
self.progress_bar.move(100, self.height() - 40)
|
|
86
|
+
self.progress_bar.setFixedWidth(0)
|
|
87
|
+
|
|
88
|
+
# Create a top overlay widget that will always be on top
|
|
89
|
+
self.top_overlay = QWidget(self)
|
|
90
|
+
self.top_overlay.setGeometry(0, 0, self.width(), 130) # Covers title and logo area
|
|
91
|
+
self.top_overlay.setStyleSheet("background: transparent;")
|
|
92
|
+
|
|
93
|
+
# Now create the top title and logo elements on the overlay
|
|
94
|
+
|
|
95
|
+
# Create title bar at the very top
|
|
96
|
+
self.title_bar = QLabel(self.top_overlay)
|
|
97
|
+
self.title_bar.setText("SQL Shell") # Set the text
|
|
98
|
+
self.title_bar.setFixedSize(self.width(), 50)
|
|
99
|
+
self.title_bar.move(0, 0)
|
|
100
|
+
self.title_bar.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
101
|
+
self.title_bar.setStyleSheet("""
|
|
102
|
+
QLabel {
|
|
103
|
+
color: white;
|
|
104
|
+
font-size: 28px;
|
|
105
|
+
font-weight: bold;
|
|
106
|
+
font-family: 'Segoe UI', Arial, sans-serif;
|
|
107
|
+
background-color: rgba(52, 152, 219, 0.9);
|
|
108
|
+
border-bottom: 2px solid #2980B9;
|
|
109
|
+
border-top-left-radius: 10px;
|
|
110
|
+
border-top-right-radius: 10px;
|
|
111
|
+
}
|
|
112
|
+
""")
|
|
113
|
+
|
|
114
|
+
# Create a dedicated logo container right below the title bar
|
|
115
|
+
self.logo_container = QLabel(self.top_overlay)
|
|
116
|
+
self.logo_container.setFixedSize(self.width(), 80)
|
|
117
|
+
self.logo_container.move(0, 50) # Position right below title bar
|
|
118
|
+
self.logo_container.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
119
|
+
self.logo_container.setStyleSheet("background: rgba(255, 255, 255, 0.8); border: 0px;")
|
|
120
|
+
|
|
121
|
+
# Try to load logo directly here
|
|
122
|
+
logo_path = os.path.join(os.path.dirname(__file__), "resources", "logo_medium.png")
|
|
123
|
+
if os.path.exists(logo_path):
|
|
124
|
+
logo_pixmap = QPixmap(logo_path)
|
|
125
|
+
# Scale logo to appropriate size
|
|
126
|
+
scaled_logo = logo_pixmap.scaledToWidth(200, Qt.TransformationMode.SmoothTransformation)
|
|
127
|
+
self.logo_container.setPixmap(scaled_logo)
|
|
128
|
+
print(f"Logo loaded with size: {scaled_logo.width()}x{scaled_logo.height()}")
|
|
129
|
+
print(f"Logo container geometry: {self.logo_container.geometry()}")
|
|
130
|
+
else:
|
|
131
|
+
print(f"Logo not found at path: {logo_path}")
|
|
132
|
+
# Try the small logo as fallback
|
|
133
|
+
logo_path = os.path.join(os.path.dirname(__file__), "resources", "logo_small.png")
|
|
134
|
+
if os.path.exists(logo_path):
|
|
135
|
+
logo_pixmap = QPixmap(logo_path)
|
|
136
|
+
scaled_logo = logo_pixmap.scaledToWidth(150, Qt.TransformationMode.SmoothTransformation)
|
|
137
|
+
self.logo_container.setPixmap(scaled_logo)
|
|
138
|
+
print(f"Fallback logo loaded with size: {scaled_logo.width()}x{scaled_logo.height()}")
|
|
139
|
+
print(f"Logo container geometry: {self.logo_container.geometry()}")
|
|
140
|
+
|
|
141
|
+
print(f"Title bar geometry: {self.title_bar.geometry()}")
|
|
142
|
+
print(f"Title bar text: {self.title_bar.text()}")
|
|
143
|
+
print(f"Top overlay geometry: {self.top_overlay.geometry()}")
|
|
144
|
+
|
|
145
|
+
# Set appropriate z-order of elements
|
|
146
|
+
self.movie_label.lower() # Background at the very back
|
|
147
|
+
self.overlay.raise_() # Overlay above background
|
|
148
|
+
self.text_label.raise_() # Text above overlay
|
|
149
|
+
self.content_container.raise_() # Content above text
|
|
150
|
+
self.progress_bar.raise_() # Progress bar on top
|
|
151
|
+
self.top_overlay.raise_() # Top overlay with title and logo at the very top
|
|
152
|
+
|
|
153
|
+
# Set up the loading animation - do it immediately in init
|
|
154
|
+
self.movie = None # Initialize to None for safety
|
|
155
|
+
self.load_animation()
|
|
156
|
+
|
|
157
|
+
# Set up fade animation
|
|
158
|
+
self.fade_anim = QPropertyAnimation(self, b"opacity")
|
|
159
|
+
self.fade_anim.setDuration(1000)
|
|
160
|
+
self.fade_anim.setStartValue(0.0)
|
|
161
|
+
self.fade_anim.setEndValue(1.0)
|
|
162
|
+
self.fade_anim.setEasingCurve(QEasingCurve.Type.InOutQuad)
|
|
163
|
+
|
|
164
|
+
# Set up progress animation
|
|
165
|
+
self.progress_anim = QPropertyAnimation(self, b"progress")
|
|
166
|
+
self.progress_anim.setDuration(2000)
|
|
167
|
+
self.progress_anim.setStartValue(0.0)
|
|
168
|
+
self.progress_anim.setEndValue(1.0)
|
|
169
|
+
self.progress_anim.setEasingCurve(QEasingCurve.Type.InOutQuad)
|
|
170
|
+
|
|
171
|
+
# Create a dedicated timer to ensure title and logo always stay on top
|
|
172
|
+
self.z_order_timer = QTimer(self)
|
|
173
|
+
self.z_order_timer.timeout.connect(self.ensure_top_elements_visible)
|
|
174
|
+
self.z_order_timer.start(50) # Check every 50ms
|
|
175
|
+
|
|
176
|
+
# Start animations after everything is initialized
|
|
177
|
+
QTimer.singleShot(100, self.start_animations) # Small delay to ensure everything is ready
|
|
178
|
+
|
|
179
|
+
def load_animation(self):
|
|
180
|
+
"""Load the splash screen animation"""
|
|
181
|
+
# Check multiple potential paths for the splash screen GIF
|
|
182
|
+
possible_paths = [
|
|
183
|
+
os.path.join(os.path.dirname(__file__), "resources", "splash_screen.gif"),
|
|
184
|
+
os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "splash_screen.gif"),
|
|
185
|
+
os.path.join(os.path.dirname(__file__), "splash_screen.gif"),
|
|
186
|
+
os.path.abspath("sqlshell/resources/splash_screen.gif"),
|
|
187
|
+
os.path.abspath("resources/splash_screen.gif")
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
# Try each possible path
|
|
191
|
+
for path in possible_paths:
|
|
192
|
+
if os.path.exists(path):
|
|
193
|
+
print(f"Loading splash screen animation from: {path}")
|
|
194
|
+
try:
|
|
195
|
+
self.movie = QMovie(path)
|
|
196
|
+
self.movie.setCacheMode(QMovie.CacheMode.CacheAll) # Cache all frames for smoother playback
|
|
197
|
+
self.movie.setScaledSize(self.size())
|
|
198
|
+
|
|
199
|
+
# Connect frameChanged signal to update the label
|
|
200
|
+
self.movie.frameChanged.connect(self.update_frame)
|
|
201
|
+
|
|
202
|
+
# Ensure the movie label is visible but below other elements
|
|
203
|
+
self.movie_label.lower()
|
|
204
|
+
self.movie_label.setStyleSheet("background: transparent;")
|
|
205
|
+
|
|
206
|
+
# Set the movie to the label
|
|
207
|
+
self.movie_label.setMovie(self.movie)
|
|
208
|
+
|
|
209
|
+
# Test if the movie is valid
|
|
210
|
+
if self.movie.isValid():
|
|
211
|
+
print(f"Successfully loaded animation with {self.movie.frameCount()} frames")
|
|
212
|
+
self.use_fallback = False
|
|
213
|
+
|
|
214
|
+
# Create a timer to ensure animation updates
|
|
215
|
+
self.animation_timer = QTimer(self)
|
|
216
|
+
self.animation_timer.timeout.connect(self.update_animation)
|
|
217
|
+
self.animation_timer.start(50) # Update every 50ms
|
|
218
|
+
|
|
219
|
+
# Force our top overlay to be visible
|
|
220
|
+
self.top_overlay.raise_()
|
|
221
|
+
|
|
222
|
+
return
|
|
223
|
+
else:
|
|
224
|
+
print(f"Warning: Animation file at {path} is not valid")
|
|
225
|
+
self.use_fallback = True
|
|
226
|
+
except Exception as e:
|
|
227
|
+
print(f"Error loading animation from {path}: {e}")
|
|
228
|
+
|
|
229
|
+
# If we get here, no valid animation was found
|
|
230
|
+
print("No valid animation found, using fallback static splash screen")
|
|
231
|
+
self.use_fallback = True
|
|
232
|
+
|
|
233
|
+
def update_frame(self):
|
|
234
|
+
"""Handle frame changed in the animation"""
|
|
235
|
+
# Make sure the movie label is refreshed and visible
|
|
236
|
+
self.movie_label.update()
|
|
237
|
+
self.movie_label.show()
|
|
238
|
+
|
|
239
|
+
# Always ensure title and logo stay on top
|
|
240
|
+
self.title_bar.raise_()
|
|
241
|
+
self.logo_container.raise_()
|
|
242
|
+
|
|
243
|
+
def update_animation(self):
|
|
244
|
+
"""Ensure animation keeps running"""
|
|
245
|
+
if self.movie and not self.use_fallback:
|
|
246
|
+
# Check if movie is running
|
|
247
|
+
if self.movie.state() != QMovie.MovieState.Running:
|
|
248
|
+
self.movie.start()
|
|
249
|
+
|
|
250
|
+
# Force update of the movie label
|
|
251
|
+
self.movie_label.update()
|
|
252
|
+
|
|
253
|
+
# Always ensure title and logo stay on top
|
|
254
|
+
self.title_bar.raise_()
|
|
255
|
+
self.logo_container.raise_()
|
|
256
|
+
|
|
257
|
+
def paintEvent(self, event):
|
|
258
|
+
"""Custom paint event to draw a fallback splash screen if needed"""
|
|
259
|
+
if self.use_fallback:
|
|
260
|
+
painter = QPainter(self)
|
|
261
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
262
|
+
|
|
263
|
+
# Draw rounded rectangle background
|
|
264
|
+
gradient = QLinearGradient(0, 0, 0, self.height())
|
|
265
|
+
gradient.setColorAt(0, QColor(44, 62, 80)) # Dark blue-gray
|
|
266
|
+
gradient.setColorAt(1, QColor(52, 152, 219)) # Bright blue
|
|
267
|
+
|
|
268
|
+
painter.setBrush(gradient)
|
|
269
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
270
|
+
painter.drawRoundedRect(0, 0, self.width(), self.height(), 10, 10)
|
|
271
|
+
|
|
272
|
+
# Draw title bar at the top
|
|
273
|
+
title_rect = QRect(0, 0, self.width(), 50)
|
|
274
|
+
painter.setBrush(QColor(52, 152, 219)) # Bright blue
|
|
275
|
+
painter.drawRect(title_rect)
|
|
276
|
+
|
|
277
|
+
# Draw title text
|
|
278
|
+
painter.setPen(QColor(255, 255, 255))
|
|
279
|
+
font = QFont("Segoe UI", 24)
|
|
280
|
+
font.setBold(True)
|
|
281
|
+
painter.setFont(font)
|
|
282
|
+
painter.drawText(title_rect, Qt.AlignmentFlag.AlignCenter, "SQL Shell")
|
|
283
|
+
|
|
284
|
+
# Try to draw logo
|
|
285
|
+
logo_path = os.path.join(os.path.dirname(__file__), "resources", "logo_small.png")
|
|
286
|
+
if os.path.exists(logo_path):
|
|
287
|
+
logo_pixmap = QPixmap(logo_path)
|
|
288
|
+
scaled_logo = logo_pixmap.scaledToHeight(70, Qt.TransformationMode.SmoothTransformation)
|
|
289
|
+
logo_x = (self.width() - scaled_logo.width()) // 2
|
|
290
|
+
painter.drawPixmap(logo_x, 60, scaled_logo)
|
|
291
|
+
|
|
292
|
+
# Draw progress bar
|
|
293
|
+
if hasattr(self, '_progress'):
|
|
294
|
+
progress_width = int(200 * self._progress)
|
|
295
|
+
progress_rect = QRect(100, self.height() - 40, progress_width, 4)
|
|
296
|
+
painter.setBrush(QColor(26, 188, 156)) # Teal
|
|
297
|
+
painter.drawRoundedRect(progress_rect, 2, 2)
|
|
298
|
+
|
|
299
|
+
super().paintEvent(event)
|
|
300
|
+
|
|
301
|
+
def start_animations(self):
|
|
302
|
+
"""Start all animations"""
|
|
303
|
+
# Try to start the movie if we have one
|
|
304
|
+
if self.movie and not self.use_fallback:
|
|
305
|
+
self.movie.start()
|
|
306
|
+
|
|
307
|
+
# Check if movie is running after attempt to start
|
|
308
|
+
if not self.movie.state() == QMovie.MovieState.Running:
|
|
309
|
+
print("Warning: Could not start the animation")
|
|
310
|
+
self.use_fallback = True
|
|
311
|
+
else:
|
|
312
|
+
# Ensure the movie label is visible and updated
|
|
313
|
+
self.movie_label.show()
|
|
314
|
+
self.movie_label.update()
|
|
315
|
+
|
|
316
|
+
self.fade_anim.start()
|
|
317
|
+
self.progress_anim.start()
|
|
318
|
+
self.progress_anim.finished.connect(self._on_animation_finished)
|
|
319
|
+
|
|
320
|
+
@pyqtProperty(float)
|
|
321
|
+
def opacity(self):
|
|
322
|
+
return self._opacity
|
|
323
|
+
|
|
324
|
+
@opacity.setter
|
|
325
|
+
def opacity(self, value):
|
|
326
|
+
self._opacity = value
|
|
327
|
+
# Update opacity of overlay and text
|
|
328
|
+
self.overlay.setStyleSheet(f"background-color: rgba(0, 0, 0, {int(100 * value)});")
|
|
329
|
+
self.text_label.setStyleSheet(f"""
|
|
330
|
+
QLabel {{
|
|
331
|
+
color: rgba(255, 255, 255, {int(255 * value)});
|
|
332
|
+
background: transparent;
|
|
333
|
+
text-shadow: 2px 2px 4px rgba(0, 0, 0, {int(180 * value)}),
|
|
334
|
+
0px 0px 10px rgba(52, 152, 219, {int(160 * value)});
|
|
335
|
+
}}
|
|
336
|
+
""")
|
|
337
|
+
|
|
338
|
+
@pyqtProperty(float)
|
|
339
|
+
def progress(self):
|
|
340
|
+
return self._progress
|
|
341
|
+
|
|
342
|
+
@progress.setter
|
|
343
|
+
def progress(self, value):
|
|
344
|
+
self._progress = value
|
|
345
|
+
# Update progress bar width
|
|
346
|
+
if hasattr(self, 'progress_bar'):
|
|
347
|
+
self.progress_bar.setFixedWidth(int(200 * value))
|
|
348
|
+
# Force repaint if using fallback
|
|
349
|
+
if self.use_fallback:
|
|
350
|
+
self.update()
|
|
351
|
+
|
|
352
|
+
def ensure_top_elements_visible(self):
|
|
353
|
+
"""Ensure title bar and logo container are always on top"""
|
|
354
|
+
self.top_overlay.raise_()
|
|
355
|
+
|
|
356
|
+
def _on_animation_finished(self):
|
|
357
|
+
"""Handle animation completion"""
|
|
358
|
+
if self.next_widget:
|
|
359
|
+
QTimer.singleShot(500, self._finish_splash)
|
|
360
|
+
|
|
361
|
+
def _finish_splash(self):
|
|
362
|
+
"""Clean up and show the main window"""
|
|
363
|
+
# Stop the animation timer if it exists
|
|
364
|
+
if hasattr(self, 'animation_timer') and self.animation_timer:
|
|
365
|
+
self.animation_timer.stop()
|
|
366
|
+
|
|
367
|
+
# Stop the z-order timer
|
|
368
|
+
if hasattr(self, 'z_order_timer'):
|
|
369
|
+
self.z_order_timer.stop()
|
|
370
|
+
|
|
371
|
+
if self.movie:
|
|
372
|
+
self.movie.stop()
|
|
373
|
+
if self.fade_anim:
|
|
374
|
+
self.fade_anim.stop()
|
|
375
|
+
if self.progress_anim:
|
|
376
|
+
self.progress_anim.stop()
|
|
377
|
+
self.close()
|
|
378
|
+
if self.next_widget:
|
|
379
|
+
self.next_widget.show()
|
|
380
|
+
|
|
381
|
+
def finish(self, widget):
|
|
382
|
+
"""Store the widget to show after animation completes"""
|
|
383
|
+
self.next_widget = widget
|
|
384
|
+
|
|
385
|
+
# On Windows, we need to explicitly trigger the finish process
|
|
386
|
+
# instead of waiting for the animation to complete
|
|
387
|
+
|
|
388
|
+
# First forcibly stop all animations
|
|
389
|
+
if hasattr(self, 'animation_timer') and self.animation_timer:
|
|
390
|
+
self.animation_timer.stop()
|
|
391
|
+
|
|
392
|
+
# Stop the z-order timer
|
|
393
|
+
if hasattr(self, 'z_order_timer'):
|
|
394
|
+
self.z_order_timer.stop()
|
|
395
|
+
|
|
396
|
+
if self.movie:
|
|
397
|
+
self.movie.stop()
|
|
398
|
+
if self.fade_anim:
|
|
399
|
+
self.fade_anim.stop()
|
|
400
|
+
if self.progress_anim:
|
|
401
|
+
self.progress_anim.stop()
|
|
402
|
+
|
|
403
|
+
# Close the splash screen and show the main window directly
|
|
404
|
+
# Use a very short timer to allow the event loop to process
|
|
405
|
+
QTimer.singleShot(50, self._finish_splash)
|
|
@@ -0,0 +1,118 @@
|
|
|
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")
|
|
114
|
+
print("""
|
|
115
|
+
select * from product_catalog;
|
|
116
|
+
select * from customer_data;
|
|
117
|
+
select * from sample_sales_data;
|
|
118
|
+
""")
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import sqlite3
|
|
3
|
+
import duckdb
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
# Define paths
|
|
7
|
+
TEST_DATA_DIR = 'test_data'
|
|
8
|
+
SQLITE_DB_PATH = os.path.join(TEST_DATA_DIR, 'test.db')
|
|
9
|
+
DUCKDB_PATH = os.path.join(TEST_DATA_DIR, 'test.duckdb')
|
|
10
|
+
|
|
11
|
+
def load_source_data():
|
|
12
|
+
"""Load the source Excel and Parquet files."""
|
|
13
|
+
sales_df = pd.read_excel(os.path.join(TEST_DATA_DIR, 'sample_sales_data.xlsx'))
|
|
14
|
+
customer_df = pd.read_parquet(os.path.join(TEST_DATA_DIR, 'customer_data.parquet'))
|
|
15
|
+
product_df = pd.read_excel(os.path.join(TEST_DATA_DIR, 'product_catalog.xlsx'))
|
|
16
|
+
return sales_df, customer_df, product_df
|
|
17
|
+
|
|
18
|
+
def create_sqlite_database():
|
|
19
|
+
"""Create SQLite database with the test data."""
|
|
20
|
+
# Remove existing database if it exists
|
|
21
|
+
if os.path.exists(SQLITE_DB_PATH):
|
|
22
|
+
os.remove(SQLITE_DB_PATH)
|
|
23
|
+
|
|
24
|
+
# Load data
|
|
25
|
+
sales_df, customer_df, product_df = load_source_data()
|
|
26
|
+
|
|
27
|
+
# Create connection and write tables
|
|
28
|
+
with sqlite3.connect(SQLITE_DB_PATH) as conn:
|
|
29
|
+
sales_df.to_sql('sales', conn, index=False)
|
|
30
|
+
customer_df.to_sql('customers', conn, index=False)
|
|
31
|
+
product_df.to_sql('products', conn, index=False)
|
|
32
|
+
|
|
33
|
+
# Create indexes for better performance
|
|
34
|
+
conn.execute('CREATE INDEX idx_sales_customer ON sales(CustomerID)')
|
|
35
|
+
conn.execute('CREATE INDEX idx_sales_product ON sales(ProductID)')
|
|
36
|
+
|
|
37
|
+
print(f"Created SQLite database at {SQLITE_DB_PATH}")
|
|
38
|
+
|
|
39
|
+
def create_duckdb_database():
|
|
40
|
+
"""Create DuckDB database with the test data."""
|
|
41
|
+
# Remove existing database if it exists
|
|
42
|
+
if os.path.exists(DUCKDB_PATH):
|
|
43
|
+
os.remove(DUCKDB_PATH)
|
|
44
|
+
|
|
45
|
+
# Load data
|
|
46
|
+
sales_df, customer_df, product_df = load_source_data()
|
|
47
|
+
|
|
48
|
+
# Create connection and write tables
|
|
49
|
+
with duckdb.connect(DUCKDB_PATH) as conn:
|
|
50
|
+
conn.execute("CREATE TABLE sales AS SELECT * FROM sales_df")
|
|
51
|
+
conn.execute("CREATE TABLE customers AS SELECT * FROM customer_df")
|
|
52
|
+
conn.execute("CREATE TABLE products AS SELECT * FROM product_df")
|
|
53
|
+
|
|
54
|
+
# Create indexes for better performance
|
|
55
|
+
conn.execute('CREATE INDEX idx_sales_customer ON sales(CustomerID)')
|
|
56
|
+
conn.execute('CREATE INDEX idx_sales_product ON sales(ProductID)')
|
|
57
|
+
|
|
58
|
+
print(f"Created DuckDB database at {DUCKDB_PATH}")
|
|
59
|
+
|
|
60
|
+
def verify_databases():
|
|
61
|
+
"""Verify the databases were created correctly by running test queries."""
|
|
62
|
+
# Test SQLite
|
|
63
|
+
with sqlite3.connect(SQLITE_DB_PATH) as conn:
|
|
64
|
+
sales_count = pd.read_sql("SELECT COUNT(*) as count FROM sales", conn).iloc[0]['count']
|
|
65
|
+
print(f"\nSQLite verification:")
|
|
66
|
+
print(f"Sales records: {sales_count}")
|
|
67
|
+
|
|
68
|
+
# Test a join query
|
|
69
|
+
sample_query = """
|
|
70
|
+
SELECT
|
|
71
|
+
p.Category,
|
|
72
|
+
COUNT(*) as NumOrders,
|
|
73
|
+
ROUND(SUM(s.TotalAmount), 2) as TotalRevenue
|
|
74
|
+
FROM sales s
|
|
75
|
+
JOIN products p ON s.ProductID = p.ProductID
|
|
76
|
+
GROUP BY p.Category
|
|
77
|
+
LIMIT 3
|
|
78
|
+
"""
|
|
79
|
+
print("\nSample SQLite query result:")
|
|
80
|
+
print(pd.read_sql(sample_query, conn))
|
|
81
|
+
|
|
82
|
+
# Test DuckDB
|
|
83
|
+
with duckdb.connect(DUCKDB_PATH) as conn:
|
|
84
|
+
sales_count = conn.execute("SELECT COUNT(*) as count FROM sales").fetchone()[0]
|
|
85
|
+
print(f"\nDuckDB verification:")
|
|
86
|
+
print(f"Sales records: {sales_count}")
|
|
87
|
+
|
|
88
|
+
# Test the same join query
|
|
89
|
+
print("\nSample DuckDB query result:")
|
|
90
|
+
print(conn.execute(sample_query).df())
|
|
91
|
+
|
|
92
|
+
if __name__ == '__main__':
|
|
93
|
+
print("Creating test databases...")
|
|
94
|
+
create_sqlite_database()
|
|
95
|
+
create_duckdb_database()
|
|
96
|
+
verify_databases()
|
|
Binary file
|