sqlbench 0.1.1__tar.gz → 0.1.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlbench
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: A multi-database SQL workbench with support for IBM i, MySQL, and PostgreSQL
5
5
  Project-URL: Homepage, https://github.com/jpsteil/sqlbench
6
6
  Project-URL: Repository, https://github.com/jpsteil/sqlbench
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sqlbench"
7
- version = "0.1.1"
7
+ version = "0.1.2"
8
8
  description = "A multi-database SQL workbench with support for IBM i, MySQL, and PostgreSQL"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -60,6 +60,7 @@ Issues = "https://github.com/jpsteil/sqlbench/issues"
60
60
 
61
61
  [tool.hatch.build.targets.wheel]
62
62
  packages = ["sqlbench"]
63
+ include = ["sqlbench/resources/*"]
63
64
 
64
65
  [tool.ruff]
65
66
  line-length = 100
@@ -0,0 +1,40 @@
1
+ """Entry point for sqlbench."""
2
+
3
+ import sys
4
+
5
+
6
+ def main():
7
+ """Main entry point with argument handling."""
8
+ if len(sys.argv) > 1:
9
+ arg = sys.argv[1].lower()
10
+
11
+ if arg in ("--install-launcher", "--create-launcher"):
12
+ from sqlbench.launcher import create_launcher
13
+ success = create_launcher()
14
+ sys.exit(0 if success else 1)
15
+
16
+ elif arg in ("--remove-launcher", "--uninstall-launcher"):
17
+ from sqlbench.launcher import remove_launcher
18
+ success = remove_launcher()
19
+ sys.exit(0 if success else 1)
20
+
21
+ elif arg in ("--help", "-h"):
22
+ print("SQLBench - Multi-database SQL Workbench")
23
+ print()
24
+ print("Usage: sqlbench [options]")
25
+ print()
26
+ print("Options:")
27
+ print(" --install-launcher Create a desktop launcher for this OS")
28
+ print(" --remove-launcher Remove the desktop launcher")
29
+ print(" --help, -h Show this help message")
30
+ print()
31
+ print("Run without arguments to start the application.")
32
+ sys.exit(0)
33
+
34
+ # Start the GUI application
35
+ from sqlbench.app import main as app_main
36
+ app_main()
37
+
38
+
39
+ if __name__ == "__main__":
40
+ main()
@@ -41,6 +41,9 @@ class SQLBenchApp:
41
41
  # Restore last connection and tabs after UI is ready
42
42
  self.root.after(100, self._restore_session)
43
43
 
44
+ # Check for updates in background
45
+ self.root.after(500, self._check_for_updates)
46
+
44
47
  def _create_menu(self):
45
48
  menubar = tk.Menu(self.root)
46
49
  self.root.config(menu=menubar)
@@ -939,8 +942,112 @@ class SQLBenchApp:
939
942
  self.db.delete_connection(conn["id"])
940
943
  self._refresh_connections()
941
944
 
945
+ def _install_launcher(self):
946
+ """Install desktop launcher for the current OS."""
947
+ from sqlbench.launcher import create_launcher
948
+ try:
949
+ success = create_launcher()
950
+ if success:
951
+ tk.messagebox.showinfo(
952
+ "Launcher Installed",
953
+ "Desktop launcher installed successfully.\n\n"
954
+ "You may need to log out and back in for it to appear in your application menu."
955
+ )
956
+ else:
957
+ tk.messagebox.showwarning(
958
+ "Installation Failed",
959
+ "Could not install desktop launcher for this operating system."
960
+ )
961
+ except Exception as e:
962
+ tk.messagebox.showerror("Error", f"Failed to install launcher:\n{e}")
963
+
964
+ def _remove_launcher(self):
965
+ """Remove desktop launcher for the current OS."""
966
+ from sqlbench.launcher import remove_launcher
967
+ try:
968
+ success = remove_launcher()
969
+ if success:
970
+ tk.messagebox.showinfo(
971
+ "Launcher Removed",
972
+ "Desktop launcher removed successfully."
973
+ )
974
+ else:
975
+ tk.messagebox.showinfo(
976
+ "No Launcher Found",
977
+ "No desktop launcher was found to remove."
978
+ )
979
+ except Exception as e:
980
+ tk.messagebox.showerror("Error", f"Failed to remove launcher:\n{e}")
981
+
982
+ def _check_for_updates(self):
983
+ """Check for updates in background."""
984
+ from sqlbench.version import check_for_updates, __version__
985
+
986
+ def on_update_check(has_update, latest_version):
987
+ if has_update:
988
+ self.root.after(0, lambda: self._show_update_dialog(latest_version))
989
+
990
+ check_for_updates(on_update_check)
991
+
992
+ def _show_update_dialog(self, latest_version):
993
+ """Show update available dialog."""
994
+ from sqlbench.version import __version__
995
+
996
+ result = tk.messagebox.askyesno(
997
+ "Update Available",
998
+ f"A new version of SQLBench is available.\n\n"
999
+ f"Installed: {__version__}\n"
1000
+ f"Latest: {latest_version}\n\n"
1001
+ f"Would you like to upgrade now?\n\n"
1002
+ f"(This will run: pipx upgrade sqlbench)"
1003
+ )
1004
+
1005
+ if result:
1006
+ self._run_upgrade()
1007
+
1008
+ def _run_upgrade(self):
1009
+ """Run pipx upgrade in background."""
1010
+ import subprocess
1011
+ import threading
1012
+
1013
+ def do_upgrade():
1014
+ try:
1015
+ result = subprocess.run(
1016
+ ["pipx", "upgrade", "sqlbench"],
1017
+ capture_output=True,
1018
+ text=True
1019
+ )
1020
+ if result.returncode == 0:
1021
+ self.root.after(0, lambda: tk.messagebox.showinfo(
1022
+ "Upgrade Complete",
1023
+ "SQLBench has been upgraded.\n\n"
1024
+ "Please restart the application to use the new version."
1025
+ ))
1026
+ else:
1027
+ error = result.stderr or result.stdout or "Unknown error"
1028
+ self.root.after(0, lambda: tk.messagebox.showerror(
1029
+ "Upgrade Failed",
1030
+ f"Failed to upgrade:\n{error}"
1031
+ ))
1032
+ except FileNotFoundError:
1033
+ self.root.after(0, lambda: tk.messagebox.showerror(
1034
+ "Upgrade Failed",
1035
+ "pipx not found. Please upgrade manually:\n\n"
1036
+ "pipx upgrade sqlbench"
1037
+ ))
1038
+ except Exception as e:
1039
+ self.root.after(0, lambda: tk.messagebox.showerror(
1040
+ "Upgrade Failed",
1041
+ f"Failed to upgrade:\n{e}"
1042
+ ))
1043
+
1044
+ self.statusbar.config(text="Upgrading SQLBench...")
1045
+ thread = threading.Thread(target=do_upgrade, daemon=True)
1046
+ thread.start()
1047
+
942
1048
  def _show_about(self):
943
- tk.messagebox.showinfo("About", "IBM i Utility\nVersion 0.2")
1049
+ from sqlbench.version import __version__
1050
+ tk.messagebox.showinfo("About", f"SQLBench v{__version__}\nMulti-database SQL Workbench")
944
1051
 
945
1052
  def _show_settings(self):
946
1053
  """Show settings dialog."""
@@ -950,10 +1057,10 @@ class SQLBenchApp:
950
1057
  settings_win.grab_set()
951
1058
 
952
1059
  # Center on parent
953
- settings_win.geometry("300x150")
1060
+ settings_win.geometry("350x200")
954
1061
  settings_win.update_idletasks()
955
- x = self.root.winfo_x() + (self.root.winfo_width() - 300) // 2
956
- y = self.root.winfo_y() + (self.root.winfo_height() - 150) // 2
1062
+ x = self.root.winfo_x() + (self.root.winfo_width() - 350) // 2
1063
+ y = self.root.winfo_y() + (self.root.winfo_height() - 200) // 2
957
1064
  settings_win.geometry(f"+{x}+{y}")
958
1065
 
959
1066
  # Apply dark mode
@@ -968,7 +1075,7 @@ class SQLBenchApp:
968
1075
 
969
1076
  # Font size setting
970
1077
  font_frame = ttk.Frame(settings_win)
971
- font_frame.pack(fill=tk.X, padx=20, pady=20)
1078
+ font_frame.pack(fill=tk.X, padx=20, pady=(20, 10))
972
1079
 
973
1080
  ttk.Label(font_frame, text="Font Size:").pack(side=tk.LEFT)
974
1081
 
@@ -977,6 +1084,16 @@ class SQLBenchApp:
977
1084
  textvariable=font_size_var)
978
1085
  font_spin.pack(side=tk.LEFT, padx=(10, 0))
979
1086
 
1087
+ # Desktop launcher
1088
+ launcher_frame = ttk.Frame(settings_win)
1089
+ launcher_frame.pack(fill=tk.X, padx=20, pady=10)
1090
+
1091
+ ttk.Label(launcher_frame, text="Desktop:").pack(side=tk.LEFT)
1092
+ ttk.Button(launcher_frame, text="Install Launcher",
1093
+ command=self._install_launcher).pack(side=tk.LEFT, padx=(10, 0))
1094
+ ttk.Button(launcher_frame, text="Remove Launcher",
1095
+ command=self._remove_launcher).pack(side=tk.LEFT, padx=(5, 0))
1096
+
980
1097
  # Buttons
981
1098
  btn_frame = ttk.Frame(settings_win)
982
1099
  btn_frame.pack(fill=tk.X, padx=20, pady=10)
@@ -0,0 +1,278 @@
1
+ """Desktop launcher creation for SQLBench."""
2
+
3
+ import os
4
+ import platform
5
+ import shutil
6
+ import sys
7
+ from pathlib import Path
8
+
9
+
10
+ def get_executable_path():
11
+ """Get the path to the sqlbench executable."""
12
+ # When installed via pipx, the executable is in the PATH
13
+ return shutil.which("sqlbench") or sys.executable
14
+
15
+
16
+ def get_icon_path():
17
+ """Get the path to the bundled icon."""
18
+ # Icon is in the resources subdirectory of the package
19
+ package_dir = Path(__file__).parent
20
+ return package_dir / "resources" / "sqlbench.png"
21
+
22
+
23
+ def install_icon_linux():
24
+ """Install the icon to the standard Linux icon location."""
25
+ icon_source = get_icon_path()
26
+ if not icon_source.exists():
27
+ print(f"Warning: Icon not found at {icon_source}")
28
+ return None
29
+
30
+ # Install to user's icon directory
31
+ icon_dir = Path.home() / ".local" / "share" / "icons" / "hicolor" / "256x256" / "apps"
32
+ icon_dir.mkdir(parents=True, exist_ok=True)
33
+
34
+ icon_dest = icon_dir / "sqlbench.png"
35
+ shutil.copy2(icon_source, icon_dest)
36
+
37
+ # Update icon cache (optional, may not work without root)
38
+ try:
39
+ os.system("gtk-update-icon-cache -f -t ~/.local/share/icons/hicolor 2>/dev/null")
40
+ except Exception:
41
+ pass
42
+
43
+ return "sqlbench" # Return icon name for .desktop file
44
+
45
+
46
+ def create_linux_launcher():
47
+ """Create a .desktop file for Linux."""
48
+ desktop_dir = os.path.expanduser("~/.local/share/applications")
49
+ os.makedirs(desktop_dir, exist_ok=True)
50
+
51
+ desktop_file = os.path.join(desktop_dir, "sqlbench.desktop")
52
+
53
+ # Get executable path
54
+ exec_path = shutil.which("sqlbench")
55
+ if not exec_path:
56
+ # Fallback to python -m sqlbench
57
+ exec_path = f"{sys.executable} -m sqlbench"
58
+
59
+ # Install icon and get icon name/path
60
+ icon_name = install_icon_linux()
61
+ if not icon_name:
62
+ # Fallback to bundled icon path or generic icon
63
+ icon_path = get_icon_path()
64
+ icon_name = str(icon_path) if icon_path.exists() else "utilities-terminal"
65
+
66
+ content = f"""[Desktop Entry]
67
+ Name=SQLBench
68
+ Comment=Multi-database SQL Workbench
69
+ Exec={exec_path}
70
+ Icon={icon_name}
71
+ Type=Application
72
+ Categories=Development;Database;
73
+ Terminal=false
74
+ StartupWMClass=sqlbench
75
+ """
76
+
77
+ with open(desktop_file, "w") as f:
78
+ f.write(content)
79
+
80
+ # Make executable
81
+ os.chmod(desktop_file, 0o755)
82
+
83
+ print(f"Created Linux desktop launcher: {desktop_file}")
84
+ print("Icon installed to ~/.local/share/icons/hicolor/256x256/apps/sqlbench.png")
85
+ print("You may need to log out and back in for it to appear in your application menu.")
86
+ return True
87
+
88
+
89
+ def create_macos_launcher():
90
+ """Create an application launcher for macOS."""
91
+ app_base = os.path.expanduser("~/Applications/SQLBench.app/Contents")
92
+ app_dir = os.path.join(app_base, "MacOS")
93
+ resources_dir = os.path.join(app_base, "Resources")
94
+ os.makedirs(app_dir, exist_ok=True)
95
+ os.makedirs(resources_dir, exist_ok=True)
96
+
97
+ # Get executable path
98
+ exec_path = shutil.which("sqlbench")
99
+ if not exec_path:
100
+ exec_path = f"{sys.executable} -m sqlbench"
101
+
102
+ # Create the launcher script
103
+ launcher_script = os.path.join(app_dir, "SQLBench")
104
+ content = f"""#!/bin/bash
105
+ exec {exec_path}
106
+ """
107
+
108
+ with open(launcher_script, "w") as f:
109
+ f.write(content)
110
+ os.chmod(launcher_script, 0o755)
111
+
112
+ # Copy icon to Resources
113
+ icon_source = get_icon_path()
114
+ icon_dest = os.path.join(resources_dir, "sqlbench.png")
115
+ if icon_source.exists():
116
+ shutil.copy2(icon_source, icon_dest)
117
+ # Also try to create .icns for better macOS integration
118
+ # (PNG works but .icns is preferred)
119
+
120
+ # Create Info.plist
121
+ plist_file = os.path.join(app_base, "Info.plist")
122
+ plist_content = """<?xml version="1.0" encoding="UTF-8"?>
123
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
124
+ <plist version="1.0">
125
+ <dict>
126
+ <key>CFBundleExecutable</key>
127
+ <string>SQLBench</string>
128
+ <key>CFBundleIdentifier</key>
129
+ <string>com.sqlbench.app</string>
130
+ <key>CFBundleName</key>
131
+ <string>SQLBench</string>
132
+ <key>CFBundleVersion</key>
133
+ <string>1.0</string>
134
+ <key>CFBundlePackageType</key>
135
+ <string>APPL</string>
136
+ <key>CFBundleIconFile</key>
137
+ <string>sqlbench.png</string>
138
+ </dict>
139
+ </plist>
140
+ """
141
+
142
+ with open(plist_file, "w") as f:
143
+ f.write(plist_content)
144
+
145
+ print(f"Created macOS application: ~/Applications/SQLBench.app")
146
+ print("You can drag it to your Dock or find it in ~/Applications.")
147
+ return True
148
+
149
+
150
+ def create_windows_launcher():
151
+ """Create a Start Menu shortcut for Windows."""
152
+ try:
153
+ import winreg
154
+ from pathlib import Path
155
+
156
+ # Get Start Menu path
157
+ start_menu = Path(os.environ.get("APPDATA", "")) / "Microsoft" / "Windows" / "Start Menu" / "Programs"
158
+
159
+ if not start_menu.exists():
160
+ print(f"Could not find Start Menu folder: {start_menu}")
161
+ return False
162
+
163
+ # Get executable path
164
+ exec_path = shutil.which("sqlbench.exe") or shutil.which("sqlbench")
165
+ if not exec_path:
166
+ exec_path = f'"{sys.executable}" -m sqlbench'
167
+
168
+ # Create .bat launcher (simpler than .lnk without pywin32)
169
+ bat_file = start_menu / "SQLBench.bat"
170
+ content = f"""@echo off
171
+ start "" {exec_path}
172
+ """
173
+
174
+ with open(bat_file, "w") as f:
175
+ f.write(content)
176
+
177
+ print(f"Created Windows Start Menu launcher: {bat_file}")
178
+ print("You can find SQLBench in your Start Menu.")
179
+ return True
180
+
181
+ except Exception as e:
182
+ print(f"Failed to create Windows launcher: {e}")
183
+
184
+ # Fallback: create on Desktop
185
+ desktop = os.path.expanduser("~/Desktop")
186
+ if os.path.exists(desktop):
187
+ exec_path = shutil.which("sqlbench.exe") or shutil.which("sqlbench")
188
+ if not exec_path:
189
+ exec_path = f'"{sys.executable}" -m sqlbench'
190
+
191
+ bat_file = os.path.join(desktop, "SQLBench.bat")
192
+ content = f"""@echo off
193
+ start "" {exec_path}
194
+ """
195
+ with open(bat_file, "w") as f:
196
+ f.write(content)
197
+
198
+ print(f"Created Desktop launcher instead: {bat_file}")
199
+ return True
200
+
201
+ return False
202
+
203
+
204
+ def create_launcher():
205
+ """Create a desktop launcher appropriate for the current OS."""
206
+ system = platform.system().lower()
207
+
208
+ print(f"Detected OS: {platform.system()}")
209
+
210
+ if system == "linux":
211
+ return create_linux_launcher()
212
+ elif system == "darwin":
213
+ return create_macos_launcher()
214
+ elif system == "windows":
215
+ return create_windows_launcher()
216
+ else:
217
+ print(f"Unsupported operating system: {system}")
218
+ return False
219
+
220
+
221
+ def remove_launcher():
222
+ """Remove the desktop launcher for the current OS."""
223
+ system = platform.system().lower()
224
+
225
+ if system == "linux":
226
+ removed = False
227
+ desktop_file = os.path.expanduser("~/.local/share/applications/sqlbench.desktop")
228
+ if os.path.exists(desktop_file):
229
+ os.remove(desktop_file)
230
+ print(f"Removed: {desktop_file}")
231
+ removed = True
232
+
233
+ # Also remove the icon
234
+ icon_file = os.path.expanduser("~/.local/share/icons/hicolor/256x256/apps/sqlbench.png")
235
+ if os.path.exists(icon_file):
236
+ os.remove(icon_file)
237
+ print(f"Removed: {icon_file}")
238
+ removed = True
239
+
240
+ if not removed:
241
+ print("No Linux launcher found.")
242
+ return False
243
+ return True
244
+
245
+ elif system == "darwin":
246
+ app_dir = os.path.expanduser("~/Applications/SQLBench.app")
247
+ if os.path.exists(app_dir):
248
+ shutil.rmtree(app_dir)
249
+ print(f"Removed: {app_dir}")
250
+ return True
251
+ else:
252
+ print("No macOS launcher found.")
253
+ return False
254
+
255
+ elif system == "windows":
256
+ start_menu = os.path.join(
257
+ os.environ.get("APPDATA", ""),
258
+ "Microsoft", "Windows", "Start Menu", "Programs", "SQLBench.bat"
259
+ )
260
+ desktop = os.path.expanduser("~/Desktop/SQLBench.bat")
261
+
262
+ removed = False
263
+ if os.path.exists(start_menu):
264
+ os.remove(start_menu)
265
+ print(f"Removed: {start_menu}")
266
+ removed = True
267
+ if os.path.exists(desktop):
268
+ os.remove(desktop)
269
+ print(f"Removed: {desktop}")
270
+ removed = True
271
+
272
+ if not removed:
273
+ print("No Windows launcher found.")
274
+ return removed
275
+
276
+ else:
277
+ print(f"Unsupported operating system: {system}")
278
+ return False
@@ -0,0 +1,57 @@
1
+ """Version checking for SQLBench."""
2
+
3
+ import threading
4
+ import urllib.request
5
+ import json
6
+
7
+ __version__ = "0.1.2"
8
+
9
+
10
+ def get_installed_version():
11
+ """Get the currently installed version."""
12
+ return __version__
13
+
14
+
15
+ def get_pypi_version():
16
+ """Fetch the latest version from PyPI."""
17
+ try:
18
+ url = "https://pypi.org/pypi/sqlbench/json"
19
+ with urllib.request.urlopen(url, timeout=5) as response:
20
+ data = json.loads(response.read().decode())
21
+ return data["info"]["version"]
22
+ except Exception:
23
+ return None
24
+
25
+
26
+ def parse_version(version_str):
27
+ """Parse version string into tuple for comparison."""
28
+ try:
29
+ parts = version_str.split(".")
30
+ return tuple(int(p) for p in parts)
31
+ except (ValueError, AttributeError):
32
+ return (0, 0, 0)
33
+
34
+
35
+ def is_newer_version(pypi_version, installed_version):
36
+ """Check if PyPI version is newer than installed version."""
37
+ return parse_version(pypi_version) > parse_version(installed_version)
38
+
39
+
40
+ def check_for_updates(callback):
41
+ """Check for updates in background thread.
42
+
43
+ Args:
44
+ callback: Function to call with (has_update, latest_version) or (False, None) on error
45
+ """
46
+ def do_check():
47
+ try:
48
+ latest = get_pypi_version()
49
+ if latest and is_newer_version(latest, __version__):
50
+ callback(True, latest)
51
+ else:
52
+ callback(False, latest)
53
+ except Exception:
54
+ callback(False, None)
55
+
56
+ thread = threading.Thread(target=do_check, daemon=True)
57
+ thread.start()
@@ -1,7 +0,0 @@
1
- """Entry point for sqlbench."""
2
-
3
- from sqlbench.app import main
4
-
5
-
6
- if __name__ == "__main__":
7
- main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes