pipcentral 1.0.0__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.
pipcentral.py
ADDED
@@ -0,0 +1,1575 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
import shutil
|
4
|
+
import time
|
5
|
+
import tkinter as tk
|
6
|
+
from tkinter import ttk, messagebox, scrolledtext, filedialog
|
7
|
+
import pkg_resources
|
8
|
+
import subprocess
|
9
|
+
import threading
|
10
|
+
import csv
|
11
|
+
import requests
|
12
|
+
import json
|
13
|
+
|
14
|
+
|
15
|
+
try:
|
16
|
+
import networkx as nx
|
17
|
+
import matplotlib.pyplot as plt
|
18
|
+
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
19
|
+
HAS_DEPENDENCY_GRAPH = True
|
20
|
+
except ImportError:
|
21
|
+
HAS_DEPENDENCY_GRAPH = False
|
22
|
+
|
23
|
+
|
24
|
+
VENVS_DIR = os.path.join(os.getcwd(), "venvs")
|
25
|
+
if not os.path.exists(VENVS_DIR):
|
26
|
+
os.makedirs(VENVS_DIR)
|
27
|
+
|
28
|
+
LOCAL_REPO_DIR = os.path.join(os.getcwd(), "local_repo")
|
29
|
+
if not os.path.exists(LOCAL_REPO_DIR):
|
30
|
+
os.makedirs(LOCAL_REPO_DIR)
|
31
|
+
|
32
|
+
|
33
|
+
BACKUP_DIR = os.path.join(os.getcwd(), "backups")
|
34
|
+
if not os.path.exists(BACKUP_DIR):
|
35
|
+
os.makedirs(BACKUP_DIR)
|
36
|
+
|
37
|
+
|
38
|
+
class AdvancedPipManager:
|
39
|
+
def __init__(self, master):
|
40
|
+
self.master = master
|
41
|
+
self.master.title("Pipcentral")
|
42
|
+
self.master.geometry("1300x950")
|
43
|
+
|
44
|
+
self.style = ttk.Style(master)
|
45
|
+
self.style.theme_use('clam')
|
46
|
+
|
47
|
+
|
48
|
+
self.command_history = []
|
49
|
+
|
50
|
+
self.notebook = ttk.Notebook(master)
|
51
|
+
self.notebook.pack(fill='both', expand=True)
|
52
|
+
|
53
|
+
|
54
|
+
self.create_header()
|
55
|
+
|
56
|
+
self.create_installed_tab()
|
57
|
+
self.create_manage_tab()
|
58
|
+
self.create_advanced_features_tab()
|
59
|
+
if HAS_DEPENDENCY_GRAPH:
|
60
|
+
self.create_dependency_graph_tab()
|
61
|
+
else:
|
62
|
+
self.create_placeholder_tab("Dependency Graph", "Install networkx and matplotlib for dependency graph.")
|
63
|
+
self.create_virtualenv_tab()
|
64
|
+
self.create_env_comparison_tab()
|
65
|
+
self.create_localrepo_tab()
|
66
|
+
self.create_package_groups_tab()
|
67
|
+
self.create_pip_command_builder_tab()
|
68
|
+
self.create_command_history_tab()
|
69
|
+
self.create_scheduler_tab()
|
70
|
+
|
71
|
+
self.refresh_installed_packages()
|
72
|
+
self.refresh_virtualenvs()
|
73
|
+
self.refresh_env_comparison_options()
|
74
|
+
self.refresh_package_groups()
|
75
|
+
self.update_command_history_display()
|
76
|
+
|
77
|
+
self.master.after(100, self.refresh_installed_packages)
|
78
|
+
|
79
|
+
|
80
|
+
def create_header(self):
|
81
|
+
|
82
|
+
header_frame = ttk.Frame(self.master)
|
83
|
+
header_frame.pack(fill=tk.X, padx=10, pady=10)
|
84
|
+
|
85
|
+
|
86
|
+
welcome_message = (
|
87
|
+
"PipCentral is your one-stop tool for managing Python packages. "
|
88
|
+
"With features like installed package management, virtual environments, "
|
89
|
+
"local repository setup, pip scheduler, dependency graph visualization, and more, "
|
90
|
+
"it’s a comprehensive solution for Python developers.\n"
|
91
|
+
|
92
|
+
"Pipcentral © All Rights Reserved."
|
93
|
+
)
|
94
|
+
ttk.Label(header_frame, text=welcome_message, wraplength=600, justify="left").pack(side=tk.LEFT, padx=10)
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
def add_to_command_history(self, cmd, output):
|
100
|
+
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
|
101
|
+
entry = f"[{timestamp}] Command: {' '.join(cmd)}\nOutput:\n{output}\n{'-'*40}\n"
|
102
|
+
self.command_history.append(entry)
|
103
|
+
|
104
|
+
def create_command_history_tab(self):
|
105
|
+
self.history_frame = ttk.Frame(self.notebook)
|
106
|
+
self.notebook.add(self.history_frame, text="Command History")
|
107
|
+
|
108
|
+
btn_frame = ttk.Frame(self.history_frame)
|
109
|
+
btn_frame.pack(fill=tk.X, padx=10, pady=5)
|
110
|
+
ttk.Button(btn_frame, text="Refresh History", command=self.update_command_history_display).pack(side=tk.LEFT, padx=5)
|
111
|
+
ttk.Button(btn_frame, text="Export History", command=self.export_history).pack(side=tk.LEFT, padx=5)
|
112
|
+
|
113
|
+
self.history_text = scrolledtext.ScrolledText(self.history_frame, height=20)
|
114
|
+
self.history_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
115
|
+
|
116
|
+
def update_command_history_display(self):
|
117
|
+
self.history_text.delete("1.0", tk.END)
|
118
|
+
self.history_text.insert(tk.END, "\n".join(self.command_history))
|
119
|
+
|
120
|
+
def export_history(self):
|
121
|
+
file_path = filedialog.asksaveasfilename(defaultextension=".txt",
|
122
|
+
filetypes=[("Text Files", "*.txt")])
|
123
|
+
if not file_path:
|
124
|
+
return
|
125
|
+
try:
|
126
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
127
|
+
f.write("\n".join(self.command_history))
|
128
|
+
messagebox.showinfo("Export", f"Command history exported to {file_path}")
|
129
|
+
except Exception as e:
|
130
|
+
messagebox.showerror("Error", f"Export failed: {e}")
|
131
|
+
|
132
|
+
|
133
|
+
|
134
|
+
|
135
|
+
def run_pip_command(self, command, delay=0):
|
136
|
+
start_time = time.strftime("%Y-%m-%d %H:%M:%S")
|
137
|
+
full_output = ""
|
138
|
+
if delay:
|
139
|
+
self.manage_output.insert(tk.END, f"Waiting for {delay} seconds before executing command...\n")
|
140
|
+
self.manage_output.see(tk.END)
|
141
|
+
time.sleep(delay)
|
142
|
+
self.manage_output.insert(tk.END, f"Running command: {' '.join(command)}\n")
|
143
|
+
self.manage_output.see(tk.END)
|
144
|
+
self.manage_progress.start()
|
145
|
+
try:
|
146
|
+
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
147
|
+
|
148
|
+
while True:
|
149
|
+
line = process.stdout.readline()
|
150
|
+
if not line:
|
151
|
+
break
|
152
|
+
full_output += line
|
153
|
+
self.manage_output.insert(tk.END, line)
|
154
|
+
self.manage_output.see(tk.END)
|
155
|
+
stdout, stderr = process.communicate()
|
156
|
+
full_output += stdout + stderr
|
157
|
+
if stdout:
|
158
|
+
self.manage_output.insert(tk.END, stdout + "\n")
|
159
|
+
if stderr:
|
160
|
+
self.manage_output.insert(tk.END, stderr + "\n")
|
161
|
+
except Exception as e:
|
162
|
+
err_msg = f"Error: {e}\n"
|
163
|
+
full_output += err_msg
|
164
|
+
self.manage_output.insert(tk.END, err_msg)
|
165
|
+
self.manage_progress.stop()
|
166
|
+
self.manage_output.insert(tk.END, "Command finished.\n\n")
|
167
|
+
self.manage_output.see(tk.END)
|
168
|
+
self.refresh_installed_packages()
|
169
|
+
|
170
|
+
self.add_to_command_history(command, full_output)
|
171
|
+
self.update_command_history_display()
|
172
|
+
|
173
|
+
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
def optimize_environment(self):
|
178
|
+
|
179
|
+
self.advanced_output.insert(tk.END, "Optimizing Environment: Running pip check...\n")
|
180
|
+
self.advanced_output.see(tk.END)
|
181
|
+
try:
|
182
|
+
process = subprocess.Popen(["pip", "check"],
|
183
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
184
|
+
stdout, stderr = process.communicate()
|
185
|
+
output = stdout + "\n" + stderr
|
186
|
+
self.advanced_output.insert(tk.END, output + "\n")
|
187
|
+
self.add_to_command_history(["pip", "check"], output)
|
188
|
+
if stdout.strip() == "":
|
189
|
+
self.advanced_output.insert(tk.END, "No dependency conflicts detected.\n")
|
190
|
+
else:
|
191
|
+
self.advanced_output.insert(tk.END, "Dependency issues detected above. Consider upgrading or reinstalling conflicting packages.\n")
|
192
|
+
except Exception as e:
|
193
|
+
self.advanced_output.insert(tk.END, f"Optimize failed: {e}\n")
|
194
|
+
self.advanced_output.see(tk.END)
|
195
|
+
|
196
|
+
|
197
|
+
|
198
|
+
|
199
|
+
def create_scheduler_tab(self):
|
200
|
+
self.scheduler_frame = ttk.Frame(self.notebook)
|
201
|
+
self.notebook.add(self.scheduler_frame, text="Scheduler")
|
202
|
+
|
203
|
+
|
204
|
+
backup_frame = ttk.LabelFrame(self.scheduler_frame, text="Schedule Environment Backup")
|
205
|
+
backup_frame.pack(fill=tk.X, padx=10, pady=5)
|
206
|
+
ttk.Label(backup_frame, text="Delay (sec):").pack(side=tk.LEFT, padx=5)
|
207
|
+
self.backup_delay_entry = ttk.Entry(backup_frame, width=10)
|
208
|
+
self.backup_delay_entry.pack(side=tk.LEFT, padx=5)
|
209
|
+
ttk.Button(backup_frame, text="Start Backup", command=self.schedule_backup).pack(side=tk.LEFT, padx=5)
|
210
|
+
|
211
|
+
|
212
|
+
audit_frame = ttk.LabelFrame(self.scheduler_frame, text="Schedule Dependency Audit")
|
213
|
+
audit_frame.pack(fill=tk.X, padx=10, pady=5)
|
214
|
+
ttk.Label(audit_frame, text="Delay (sec):").pack(side=tk.LEFT, padx=5)
|
215
|
+
self.audit_delay_entry = ttk.Entry(audit_frame, width=10)
|
216
|
+
self.audit_delay_entry.pack(side=tk.LEFT, padx=5)
|
217
|
+
ttk.Button(audit_frame, text="Start Audit", command=self.schedule_dependency_audit).pack(side=tk.LEFT, padx=5)
|
218
|
+
|
219
|
+
|
220
|
+
upgrade_frame = ttk.LabelFrame(self.scheduler_frame, text="Schedule Bulk Upgrade")
|
221
|
+
upgrade_frame.pack(fill=tk.X, padx=10, pady=5)
|
222
|
+
ttk.Label(upgrade_frame, text="Delay (sec):").pack(side=tk.LEFT, padx=5)
|
223
|
+
self.upgrade_delay_entry = ttk.Entry(upgrade_frame, width=10)
|
224
|
+
self.upgrade_delay_entry.pack(side=tk.LEFT, padx=5)
|
225
|
+
ttk.Button(upgrade_frame, text="Start Bulk Upgrade", command=self.schedule_bulk_upgrade).pack(side=tk.LEFT, padx=5)
|
226
|
+
|
227
|
+
|
228
|
+
self.scheduler_output = scrolledtext.ScrolledText(self.scheduler_frame, height=10)
|
229
|
+
self.scheduler_output.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
230
|
+
|
231
|
+
def schedule_backup(self):
|
232
|
+
try:
|
233
|
+
delay = float(self.backup_delay_entry.get().strip())
|
234
|
+
except:
|
235
|
+
messagebox.showwarning("Input Error", "Enter a valid delay in seconds")
|
236
|
+
return
|
237
|
+
self.scheduler_output.insert(tk.END, f"Environment backup scheduled in {delay} seconds...\n")
|
238
|
+
self.scheduler_output.see(tk.END)
|
239
|
+
threading.Thread(target=self.delayed_backup, args=(delay,), daemon=True).start()
|
240
|
+
|
241
|
+
def delayed_backup(self, delay):
|
242
|
+
time.sleep(delay)
|
243
|
+
self.backup_environments()
|
244
|
+
|
245
|
+
def backup_environments(self):
|
246
|
+
backup_logs = []
|
247
|
+
envs = [env for env in os.listdir(VENVS_DIR) if os.path.isdir(os.path.join(VENVS_DIR, env))]
|
248
|
+
for env in envs:
|
249
|
+
env_path = os.path.join(VENVS_DIR, env)
|
250
|
+
pip_path = self.get_env_pip_path(env_path)
|
251
|
+
try:
|
252
|
+
process = subprocess.Popen([pip_path, "freeze"],
|
253
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
254
|
+
stdout, _ = process.communicate()
|
255
|
+
timestamp = time.strftime("%Y%m%d_%H%M%S")
|
256
|
+
backup_file = os.path.join(BACKUP_DIR, f"{env}_backup_{timestamp}.txt")
|
257
|
+
with open(backup_file, "w", encoding="utf-8") as f:
|
258
|
+
f.write(stdout)
|
259
|
+
backup_logs.append(f"Backup for {env} saved to {backup_file}")
|
260
|
+
except Exception as e:
|
261
|
+
backup_logs.append(f"Backup for {env} failed: {e}")
|
262
|
+
log_text = "\n".join(backup_logs)
|
263
|
+
self.scheduler_output.insert(tk.END, log_text + "\n")
|
264
|
+
self.scheduler_output.see(tk.END)
|
265
|
+
self.add_to_command_history(["Backup Envs"], log_text)
|
266
|
+
|
267
|
+
def schedule_dependency_audit(self):
|
268
|
+
try:
|
269
|
+
delay = float(self.audit_delay_entry.get().strip())
|
270
|
+
except:
|
271
|
+
messagebox.showwarning("Input Error", "Enter a valid delay in seconds")
|
272
|
+
return
|
273
|
+
self.scheduler_output.insert(tk.END, f"Dependency audit scheduled in {delay} seconds...\n")
|
274
|
+
self.scheduler_output.see(tk.END)
|
275
|
+
threading.Thread(target=self.delayed_audit, args=(delay,), daemon=True).start()
|
276
|
+
|
277
|
+
def delayed_audit(self, delay):
|
278
|
+
time.sleep(delay)
|
279
|
+
self.dependency_audit()
|
280
|
+
|
281
|
+
def dependency_audit(self):
|
282
|
+
try:
|
283
|
+
process = subprocess.Popen(["pip", "list", "--outdated"],
|
284
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
285
|
+
stdout, stderr = process.communicate()
|
286
|
+
audit_result = stdout + "\n" + stderr
|
287
|
+
self.scheduler_output.insert(tk.END, "Dependency Audit Result:\n" + audit_result + "\n")
|
288
|
+
self.scheduler_output.see(tk.END)
|
289
|
+
self.add_to_command_history(["pip", "list", "--outdated"], audit_result)
|
290
|
+
except Exception as e:
|
291
|
+
self.scheduler_output.insert(tk.END, f"Dependency audit failed: {e}\n")
|
292
|
+
self.scheduler_output.see(tk.END)
|
293
|
+
|
294
|
+
def schedule_bulk_upgrade(self):
|
295
|
+
try:
|
296
|
+
delay = float(self.upgrade_delay_entry.get().strip())
|
297
|
+
except:
|
298
|
+
messagebox.showwarning("Input Error", "Enter a valid delay in seconds")
|
299
|
+
return
|
300
|
+
self.scheduler_output.insert(tk.END, f"Bulk upgrade scheduled in {delay} seconds...\n")
|
301
|
+
self.scheduler_output.see(tk.END)
|
302
|
+
threading.Thread(target=self.delayed_bulk_upgrade, args=(delay,), daemon=True).start()
|
303
|
+
|
304
|
+
def delayed_bulk_upgrade(self, delay):
|
305
|
+
time.sleep(delay)
|
306
|
+
self.bulk_upgrade()
|
307
|
+
|
308
|
+
def bulk_upgrade(self):
|
309
|
+
try:
|
310
|
+
|
311
|
+
process = subprocess.Popen(["pip", "list", "--outdated"],
|
312
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
313
|
+
stdout, _ = process.communicate()
|
314
|
+
outdated_packages = []
|
315
|
+
for line in stdout.splitlines()[2:]:
|
316
|
+
if line.strip():
|
317
|
+
parts = line.split()
|
318
|
+
pkg = parts[0]
|
319
|
+
outdated_packages.append(pkg)
|
320
|
+
upgrade_logs = []
|
321
|
+
for pkg in outdated_packages:
|
322
|
+
proc = subprocess.Popen(["pip", "install", "--upgrade", pkg],
|
323
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
324
|
+
out, err = proc.communicate()
|
325
|
+
upgrade_logs.append(f"{pkg}: {out if out else err}")
|
326
|
+
log_text = "\n".join(upgrade_logs)
|
327
|
+
self.scheduler_output.insert(tk.END, "Bulk Upgrade Result:\n" + log_text + "\n")
|
328
|
+
self.scheduler_output.see(tk.END)
|
329
|
+
self.add_to_command_history(["Bulk Upgrade"], log_text)
|
330
|
+
self.refresh_installed_packages()
|
331
|
+
except Exception as e:
|
332
|
+
self.scheduler_output.insert(tk.END, f"Bulk upgrade failed: {e}\n")
|
333
|
+
self.scheduler_output.see(tk.END)
|
334
|
+
|
335
|
+
|
336
|
+
|
337
|
+
|
338
|
+
|
339
|
+
|
340
|
+
|
341
|
+
|
342
|
+
|
343
|
+
|
344
|
+
def create_installed_tab(self):
|
345
|
+
|
346
|
+
self.installed_frame = ttk.Frame(self.notebook)
|
347
|
+
self.notebook.add(self.installed_frame, text="Installed Packages")
|
348
|
+
|
349
|
+
|
350
|
+
search_frame = ttk.Frame(self.installed_frame)
|
351
|
+
search_frame.pack(fill=tk.X, padx=10, pady=5)
|
352
|
+
ttk.Label(search_frame, text="Search:").pack(side=tk.LEFT, padx=5)
|
353
|
+
self.search_var = tk.StringVar()
|
354
|
+
search_entry = ttk.Entry(search_frame, textvariable=self.search_var)
|
355
|
+
search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
356
|
+
search_entry.bind("<KeyRelease>", self.filter_by_search)
|
357
|
+
|
358
|
+
|
359
|
+
columns = ("Package", "Version", "Location")
|
360
|
+
self.installed_tree = ttk.Treeview(self.installed_frame, columns=columns, show="headings")
|
361
|
+
for col in columns:
|
362
|
+
self.installed_tree.heading(col, text=col)
|
363
|
+
self.installed_tree.column(col, anchor=tk.W, width=300)
|
364
|
+
self.installed_tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
365
|
+
|
366
|
+
|
367
|
+
self.installed_tree.bind("<Button-3>", self.popup_installed_context)
|
368
|
+
self.installed_context_menu = tk.Menu(self.master, tearoff=0)
|
369
|
+
self.installed_context_menu.add_command(label="View Details", command=self.show_package_details)
|
370
|
+
self.installed_context_menu.add_command(label="Upgrade Package", command=self.context_upgrade)
|
371
|
+
self.installed_context_menu.add_command(label="Uninstall Package", command=self.context_uninstall)
|
372
|
+
self.installed_context_menu.add_command(label="Copy Info", command=self.context_copy)
|
373
|
+
|
374
|
+
|
375
|
+
btn_frame = ttk.Frame(self.installed_frame)
|
376
|
+
btn_frame.pack(fill=tk.X, padx=10, pady=5)
|
377
|
+
ttk.Button(btn_frame, text="Refresh", command=self.refresh_installed_packages).pack(side=tk.LEFT, padx=5)
|
378
|
+
|
379
|
+
def filter_by_search(self, event=None):
|
380
|
+
|
381
|
+
search_term = self.search_var.get().lower()
|
382
|
+
|
383
|
+
for row in self.installed_tree.get_children():
|
384
|
+
self.installed_tree.delete(row)
|
385
|
+
|
386
|
+
filtered_packages = [dist for dist in self.all_packages if search_term in dist.project_name.lower()]
|
387
|
+
|
388
|
+
for dist in filtered_packages:
|
389
|
+
self.installed_tree.insert("", tk.END, values=(dist.project_name, dist.version, dist.location))
|
390
|
+
|
391
|
+
def popup_installed_context(self, event):
|
392
|
+
region = self.installed_tree.identify("region", event.x, event.y)
|
393
|
+
if region == "cell":
|
394
|
+
row_id = self.installed_tree.identify_row(event.y)
|
395
|
+
if row_id:
|
396
|
+
self.installed_tree.selection_set(row_id)
|
397
|
+
try:
|
398
|
+
self.installed_context_menu.tk_popup(event.x_root, event.y_root)
|
399
|
+
finally:
|
400
|
+
self.installed_context_menu.grab_release()
|
401
|
+
|
402
|
+
def refresh_installed_packages(self):
|
403
|
+
|
404
|
+
for row in self.installed_tree.get_children():
|
405
|
+
self.installed_tree.delete(row)
|
406
|
+
self.all_packages = sorted(pkg_resources.working_set, key=lambda d: d.project_name.lower())
|
407
|
+
for dist in self.all_packages:
|
408
|
+
self.installed_tree.insert("", tk.END, values=(dist.project_name, dist.version, dist.location))
|
409
|
+
|
410
|
+
def show_package_details(self):
|
411
|
+
selected = self.installed_tree.selection()
|
412
|
+
if not selected:
|
413
|
+
messagebox.showwarning("No Selection", "Please select a package first.")
|
414
|
+
return
|
415
|
+
item = self.installed_tree.item(selected[0])
|
416
|
+
pkg_name = item['values'][0]
|
417
|
+
try:
|
418
|
+
dist = pkg_resources.get_distribution(pkg_name)
|
419
|
+
details = f"Name: {dist.project_name}\nVersion: {dist.version}\nLocation: {dist.location}\n"
|
420
|
+
metadata = ""
|
421
|
+
if dist.has_metadata("METADATA"):
|
422
|
+
metadata = dist.get_metadata("METADATA")
|
423
|
+
elif dist.has_metadata("PKG-INFO"):
|
424
|
+
metadata = dist.get_metadata("PKG-INFO")
|
425
|
+
summary = "No summary available."
|
426
|
+
for line in metadata.splitlines():
|
427
|
+
if line.startswith("Summary:"):
|
428
|
+
summary = line.split(":", 1)[1].strip()
|
429
|
+
break
|
430
|
+
details += f"Summary: {summary}"
|
431
|
+
messagebox.showinfo("Package Details", details)
|
432
|
+
except Exception as e:
|
433
|
+
messagebox.showerror("Error", str(e))
|
434
|
+
|
435
|
+
def context_upgrade(self):
|
436
|
+
selected = self.installed_tree.selection()
|
437
|
+
if not selected:
|
438
|
+
messagebox.showwarning("No Selection", "Select a package to upgrade.")
|
439
|
+
return
|
440
|
+
pkg_name = self.installed_tree.item(selected[0])['values'][0]
|
441
|
+
threading.Thread(target=self.run_pip_command, args=(["pip", "install", "--upgrade", pkg_name], 0), daemon=True).start()
|
442
|
+
|
443
|
+
def context_uninstall(self):
|
444
|
+
selected = self.installed_tree.selection()
|
445
|
+
if not selected:
|
446
|
+
messagebox.showwarning("No Selection", "Select a package to uninstall.")
|
447
|
+
return
|
448
|
+
pkg_name = self.installed_tree.item(selected[0])['values'][0]
|
449
|
+
if not messagebox.askyesno("Confirm", f"Are you sure you want to uninstall {pkg_name}?"):
|
450
|
+
return
|
451
|
+
threading.Thread(target=self.run_pip_command, args=(["pip", "uninstall", "-y", pkg_name], 0), daemon=True).start()
|
452
|
+
|
453
|
+
def context_copy(self):
|
454
|
+
selected = self.installed_tree.selection()
|
455
|
+
if not selected:
|
456
|
+
messagebox.showwarning("No Selection", "Select a package to copy info.")
|
457
|
+
return
|
458
|
+
pkg, ver, loc = self.installed_tree.item(selected[0])['values']
|
459
|
+
info_text = f"{pkg}=={ver}\nLocation: {loc}"
|
460
|
+
self.master.clipboard_clear()
|
461
|
+
self.master.clipboard_append(info_text)
|
462
|
+
messagebox.showinfo("Copied", "Package info copied to clipboard.")
|
463
|
+
|
464
|
+
|
465
|
+
|
466
|
+
|
467
|
+
def create_manage_tab(self):
|
468
|
+
self.manage_frame = ttk.Frame(self.notebook)
|
469
|
+
self.notebook.add(self.manage_frame, text="Manage Package")
|
470
|
+
|
471
|
+
|
472
|
+
ttk.Label(self.manage_frame, text="Enter package name:").pack(pady=5)
|
473
|
+
self.manage_entry = ttk.Entry(self.manage_frame, width=50)
|
474
|
+
self.manage_entry.pack(pady=5)
|
475
|
+
|
476
|
+
|
477
|
+
version_frame = ttk.Frame(self.manage_frame)
|
478
|
+
version_frame.pack(pady=5)
|
479
|
+
ttk.Label(version_frame, text="Version (optional):").pack(side=tk.LEFT, padx=5)
|
480
|
+
self.version_entry = ttk.Entry(version_frame, width=20)
|
481
|
+
self.version_entry.pack(side=tk.LEFT)
|
482
|
+
|
483
|
+
|
484
|
+
delay_frame = ttk.Frame(self.manage_frame)
|
485
|
+
delay_frame.pack(pady=5)
|
486
|
+
ttk.Label(delay_frame, text="Delay (seconds):").pack(side=tk.LEFT, padx=5)
|
487
|
+
self.delay_entry = ttk.Entry(delay_frame, width=10)
|
488
|
+
self.delay_entry.pack(side=tk.LEFT)
|
489
|
+
|
490
|
+
|
491
|
+
extra_frame = ttk.Frame(self.manage_frame)
|
492
|
+
extra_frame.pack(pady=5)
|
493
|
+
ttk.Label(extra_frame, text="Extra pip options (optional):").pack(side=tk.LEFT, padx=5)
|
494
|
+
self.extra_options_entry = ttk.Entry(extra_frame, width=50)
|
495
|
+
self.extra_options_entry.pack(side=tk.LEFT)
|
496
|
+
|
497
|
+
|
498
|
+
verbose_frame = ttk.Frame(self.manage_frame)
|
499
|
+
verbose_frame.pack(pady=5)
|
500
|
+
self.verbose_var = tk.BooleanVar()
|
501
|
+
ttk.Checkbutton(verbose_frame, text="Verbose Output", variable=self.verbose_var).pack(side=tk.LEFT, padx=5)
|
502
|
+
|
503
|
+
|
504
|
+
dry_run_frame = ttk.Frame(self.manage_frame)
|
505
|
+
dry_run_frame.pack(pady=5)
|
506
|
+
self.dry_run_var = tk.BooleanVar()
|
507
|
+
ttk.Checkbutton(dry_run_frame, text="Dry Run (simulate command)", variable=self.dry_run_var).pack(side=tk.LEFT, padx=5)
|
508
|
+
|
509
|
+
|
510
|
+
btn_frame = ttk.Frame(self.manage_frame)
|
511
|
+
btn_frame.pack(pady=5)
|
512
|
+
ttk.Button(btn_frame, text="Install", command=self.install_package).pack(side=tk.LEFT, padx=5)
|
513
|
+
ttk.Button(btn_frame, text="Uninstall", command=self.uninstall_package).pack(side=tk.LEFT, padx=5)
|
514
|
+
ttk.Button(btn_frame, text="Upgrade", command=self.upgrade_package).pack(side=tk.LEFT, padx=5)
|
515
|
+
ttk.Button(btn_frame, text="Cancel", command=self.cancel_operation).pack(side=tk.LEFT, padx=5)
|
516
|
+
ttk.Button(btn_frame, text="History", command=self.show_history).pack(side=tk.LEFT, padx=5)
|
517
|
+
ttk.Button(btn_frame, text="Clear Output", command=self.clear_output).pack(side=tk.LEFT, padx=5)
|
518
|
+
|
519
|
+
|
520
|
+
self.manage_progress = ttk.Progressbar(self.manage_frame, mode="indeterminate")
|
521
|
+
self.manage_progress.pack(fill=tk.X, padx=10, pady=5)
|
522
|
+
|
523
|
+
|
524
|
+
self.manage_output = scrolledtext.ScrolledText(self.manage_frame, height=15)
|
525
|
+
self.manage_output.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
526
|
+
|
527
|
+
def install_package(self):
|
528
|
+
pkg = self.manage_entry.get().strip()
|
529
|
+
if not pkg:
|
530
|
+
messagebox.showwarning("Input Required", "Please enter a package name to install.")
|
531
|
+
return
|
532
|
+
|
533
|
+
version = self.version_entry.get().strip()
|
534
|
+
if version:
|
535
|
+
pkg = f"{pkg}=={version}"
|
536
|
+
delay = self.get_delay_seconds()
|
537
|
+
|
538
|
+
command = ["pip", "install"]
|
539
|
+
if self.verbose_var.get():
|
540
|
+
command.append("-v")
|
541
|
+
|
542
|
+
extra_opts = self.extra_options_entry.get().strip()
|
543
|
+
if extra_opts:
|
544
|
+
command.extend(extra_opts.split())
|
545
|
+
command.append(pkg)
|
546
|
+
threading.Thread(target=self.run_pip_command, args=(command, delay), daemon=True).start()
|
547
|
+
|
548
|
+
def uninstall_package(self):
|
549
|
+
pkg = self.manage_entry.get().strip()
|
550
|
+
if not pkg:
|
551
|
+
messagebox.showwarning("Input Required", "Please enter a package name to uninstall.")
|
552
|
+
return
|
553
|
+
if not messagebox.askyesno("Confirm", f"Are you sure you want to uninstall {pkg}?"):
|
554
|
+
return
|
555
|
+
delay = self.get_delay_seconds()
|
556
|
+
|
557
|
+
command = ["pip", "uninstall", "-y", pkg]
|
558
|
+
if self.verbose_var.get():
|
559
|
+
command.insert(1, "-v")
|
560
|
+
extra_opts = self.extra_options_entry.get().strip()
|
561
|
+
if extra_opts:
|
562
|
+
command[1:1] = extra_opts.split()
|
563
|
+
threading.Thread(target=self.run_pip_command, args=(command, delay), daemon=True).start()
|
564
|
+
|
565
|
+
def upgrade_package(self):
|
566
|
+
pkg = self.manage_entry.get().strip()
|
567
|
+
if not pkg:
|
568
|
+
messagebox.showwarning("Input Required", "Please enter a package name to upgrade.")
|
569
|
+
return
|
570
|
+
delay = self.get_delay_seconds()
|
571
|
+
|
572
|
+
command = ["pip", "install", "--upgrade", pkg]
|
573
|
+
if self.verbose_var.get():
|
574
|
+
command.insert(1, "-v")
|
575
|
+
extra_opts = self.extra_options_entry.get().strip()
|
576
|
+
if extra_opts:
|
577
|
+
command[1:1] = extra_opts.split()
|
578
|
+
threading.Thread(target=self.run_pip_command, args=(command, delay), daemon=True).start()
|
579
|
+
|
580
|
+
def get_delay_seconds(self):
|
581
|
+
try:
|
582
|
+
delay = float(self.delay_entry.get().strip())
|
583
|
+
return delay if delay > 0 else 0
|
584
|
+
except Exception:
|
585
|
+
return 0
|
586
|
+
|
587
|
+
def run_pip_command(self, command, delay):
|
588
|
+
self.manage_progress.start()
|
589
|
+
command_str = " ".join(command)
|
590
|
+
|
591
|
+
self.command_history.append(command_str)
|
592
|
+
self.manage_output.insert(tk.END, f"Running: {command_str}\n")
|
593
|
+
self.manage_output.see(tk.END)
|
594
|
+
|
595
|
+
|
596
|
+
if self.dry_run_var.get():
|
597
|
+
self.manage_output.insert(tk.END, "Dry Run selected: Command not executed.\n")
|
598
|
+
self.manage_progress.stop()
|
599
|
+
return
|
600
|
+
|
601
|
+
if delay > 0:
|
602
|
+
time.sleep(delay)
|
603
|
+
|
604
|
+
try:
|
605
|
+
self.current_process = subprocess.Popen(
|
606
|
+
command,
|
607
|
+
stdout=subprocess.PIPE,
|
608
|
+
stderr=subprocess.PIPE,
|
609
|
+
text=True
|
610
|
+
)
|
611
|
+
|
612
|
+
for line in self.current_process.stdout:
|
613
|
+
self.manage_output.insert(tk.END, line)
|
614
|
+
self.manage_output.see(tk.END)
|
615
|
+
|
616
|
+
err = self.current_process.stderr.read()
|
617
|
+
if err:
|
618
|
+
self.manage_output.insert(tk.END, err)
|
619
|
+
except Exception as e:
|
620
|
+
self.manage_output.insert(tk.END, f"Error: {e}\n")
|
621
|
+
finally:
|
622
|
+
self.manage_progress.stop()
|
623
|
+
self.current_process = None
|
624
|
+
|
625
|
+
def cancel_operation(self):
|
626
|
+
|
627
|
+
if self.current_process:
|
628
|
+
try:
|
629
|
+
self.current_process.terminate()
|
630
|
+
self.manage_output.insert(tk.END, "Process terminated by user.\n")
|
631
|
+
except Exception as e:
|
632
|
+
self.manage_output.insert(tk.END, f"Error terminating process: {e}\n")
|
633
|
+
self.current_process = None
|
634
|
+
else:
|
635
|
+
messagebox.showinfo("Cancel", "No running operation to cancel.")
|
636
|
+
|
637
|
+
def show_history(self):
|
638
|
+
|
639
|
+
history_window = tk.Toplevel(self.manage_frame)
|
640
|
+
history_window.title("Command History")
|
641
|
+
history_text = scrolledtext.ScrolledText(history_window, width=80, height=20)
|
642
|
+
history_text.pack(fill=tk.BOTH, expand=True)
|
643
|
+
history_text.insert(tk.END, "\n".join(self.command_history))
|
644
|
+
history_text.config(state=tk.DISABLED)
|
645
|
+
|
646
|
+
def clear_output(self):
|
647
|
+
self.manage_output.delete("1.0", tk.END)
|
648
|
+
|
649
|
+
|
650
|
+
def create_advanced_features_tab(self):
|
651
|
+
self.advanced_frame = ttk.Frame(self.notebook)
|
652
|
+
self.notebook.add(self.advanced_frame, text="Advanced Features")
|
653
|
+
|
654
|
+
btn_frame = ttk.Frame(self.advanced_frame)
|
655
|
+
btn_frame.pack(pady=10)
|
656
|
+
|
657
|
+
ttk.Button(btn_frame, text="Check Outdated Packages", command=self.check_outdated_packages).pack(side=tk.LEFT, padx=5)
|
658
|
+
ttk.Button(btn_frame, text="Show Pip Version", command=self.show_pip_version).pack(side=tk.LEFT, padx=5)
|
659
|
+
ttk.Button(btn_frame, text="Update Pip", command=self.update_pip).pack(side=tk.LEFT, padx=5)
|
660
|
+
ttk.Button(btn_frame, text="Scan Vulnerabilities", command=self.scan_vulnerabilities).pack(side=tk.LEFT, padx=5)
|
661
|
+
ttk.Button(btn_frame, text="Generate Optimized Requirements", command=self.generate_optimized_requirements).pack(side=tk.LEFT, padx=5)
|
662
|
+
ttk.Button(btn_frame, text="Cleanup Dependencies", command=self.cleanup_dependencies).pack(side=tk.LEFT, padx=5)
|
663
|
+
ttk.Button(btn_frame, text="Optimize Environment", command=self.optimize_environment).pack(side=tk.LEFT, padx=5)
|
664
|
+
|
665
|
+
self.advanced_output = scrolledtext.ScrolledText(self.advanced_frame, height=15)
|
666
|
+
self.advanced_output.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
667
|
+
|
668
|
+
def check_outdated_packages(self):
|
669
|
+
threading.Thread(target=self.run_outdated_command, daemon=True).start()
|
670
|
+
|
671
|
+
def run_outdated_command(self):
|
672
|
+
self.advanced_output.insert(tk.END, "Checking for outdated packages...\n")
|
673
|
+
self.advanced_output.see(tk.END)
|
674
|
+
try:
|
675
|
+
process = subprocess.Popen(["pip", "list", "--outdated"],
|
676
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
677
|
+
stdout, stderr = process.communicate()
|
678
|
+
if stdout:
|
679
|
+
self.advanced_output.insert(tk.END, stdout + "\n")
|
680
|
+
if stderr:
|
681
|
+
self.advanced_output.insert(tk.END, stderr + "\n")
|
682
|
+
except Exception as e:
|
683
|
+
self.advanced_output.insert(tk.END, f"Error: {e}\n")
|
684
|
+
self.advanced_output.insert(tk.END, "Finished checking outdated packages.\n\n")
|
685
|
+
self.advanced_output.see(tk.END)
|
686
|
+
|
687
|
+
def show_pip_version(self):
|
688
|
+
threading.Thread(target=self.run_pip_version, daemon=True).start()
|
689
|
+
|
690
|
+
def run_pip_version(self):
|
691
|
+
self.advanced_output.insert(tk.END, "Retrieving pip version...\n")
|
692
|
+
self.advanced_output.see(tk.END)
|
693
|
+
try:
|
694
|
+
process = subprocess.Popen(["pip", "--version"],
|
695
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
696
|
+
stdout, stderr = process.communicate()
|
697
|
+
if stdout:
|
698
|
+
self.advanced_output.insert(tk.END, stdout + "\n")
|
699
|
+
if stderr:
|
700
|
+
self.advanced_output.insert(tk.END, stderr + "\n")
|
701
|
+
except Exception as e:
|
702
|
+
self.advanced_output.insert(tk.END, f"Error: {e}\n")
|
703
|
+
self.advanced_output.insert(tk.END, "Finished retrieving pip version.\n\n")
|
704
|
+
self.advanced_output.see(tk.END)
|
705
|
+
|
706
|
+
def update_pip(self):
|
707
|
+
if not messagebox.askyesno("Confirm", "Update pip to the latest version?"):
|
708
|
+
return
|
709
|
+
threading.Thread(target=self.run_update_pip, daemon=True).start()
|
710
|
+
|
711
|
+
def run_update_pip(self):
|
712
|
+
self.advanced_output.insert(tk.END, "Updating pip...\n")
|
713
|
+
self.advanced_output.see(tk.END)
|
714
|
+
try:
|
715
|
+
process = subprocess.Popen(["python", "-m", "pip", "install", "--upgrade", "pip"],
|
716
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
717
|
+
stdout, stderr = process.communicate()
|
718
|
+
if stdout:
|
719
|
+
self.advanced_output.insert(tk.END, stdout + "\n")
|
720
|
+
if stderr:
|
721
|
+
self.advanced_output.insert(tk.END, stderr + "\n")
|
722
|
+
except Exception as e:
|
723
|
+
self.advanced_output.insert(tk.END, f"Error: {e}\n")
|
724
|
+
self.advanced_output.insert(tk.END, "Finished updating pip.\n\n")
|
725
|
+
self.advanced_output.see(tk.END)
|
726
|
+
|
727
|
+
def scan_vulnerabilities(self):
|
728
|
+
threading.Thread(target=self.run_scan_vulnerabilities, daemon=True).start()
|
729
|
+
|
730
|
+
def run_scan_vulnerabilities(self):
|
731
|
+
self.advanced_output.insert(tk.END, "Scanning for vulnerabilities...\n")
|
732
|
+
self.advanced_output.see(tk.END)
|
733
|
+
try:
|
734
|
+
process = subprocess.Popen(["pip-audit"],
|
735
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
736
|
+
stdout, stderr = process.communicate()
|
737
|
+
if stdout:
|
738
|
+
self.advanced_output.insert(tk.END, stdout + "\n")
|
739
|
+
if stderr:
|
740
|
+
self.advanced_output.insert(tk.END, stderr + "\n")
|
741
|
+
except Exception as e:
|
742
|
+
self.advanced_output.insert(tk.END, f"Error (pip-audit might not be installed): {e}\n")
|
743
|
+
self.advanced_output.insert(tk.END, "Finished vulnerability scan.\n\n")
|
744
|
+
self.advanced_output.see(tk.END)
|
745
|
+
|
746
|
+
def generate_optimized_requirements(self):
|
747
|
+
threading.Thread(target=self.run_generate_optimized, daemon=True).start()
|
748
|
+
|
749
|
+
def run_generate_optimized(self):
|
750
|
+
self.advanced_output.insert(tk.END, "Generating optimized requirements using pip-compile...\n")
|
751
|
+
self.advanced_output.see(tk.END)
|
752
|
+
try:
|
753
|
+
process = subprocess.Popen(["pip-compile", "requirements.in"],
|
754
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
755
|
+
stdout, stderr = process.communicate()
|
756
|
+
if stdout:
|
757
|
+
self.advanced_output.insert(tk.END, stdout + "\n")
|
758
|
+
if stderr:
|
759
|
+
self.advanced_output.insert(tk.END, stderr + "\n")
|
760
|
+
except Exception as e:
|
761
|
+
self.advanced_output.insert(tk.END, f"Error (pip-compile might not be installed): {e}\n")
|
762
|
+
self.advanced_output.insert(tk.END, "Finished generating optimized requirements.\n\n")
|
763
|
+
self.advanced_output.see(tk.END)
|
764
|
+
|
765
|
+
def cleanup_dependencies(self):
|
766
|
+
threading.Thread(target=self.run_cleanup_dependencies, daemon=True).start()
|
767
|
+
|
768
|
+
def run_cleanup_dependencies(self):
|
769
|
+
self.advanced_output.insert(tk.END, "Cleaning up unused dependencies...\n")
|
770
|
+
self.advanced_output.see(tk.END)
|
771
|
+
pkg = self.manage_entry.get().strip()
|
772
|
+
if not pkg:
|
773
|
+
self.advanced_output.insert(tk.END, "Provide a package name in Manage Package tab for cleanup.\n")
|
774
|
+
return
|
775
|
+
try:
|
776
|
+
process = subprocess.Popen(["pip-autoremove", pkg, "-y"],
|
777
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
778
|
+
stdout, stderr = process.communicate()
|
779
|
+
if stdout:
|
780
|
+
self.advanced_output.insert(tk.END, stdout + "\n")
|
781
|
+
if stderr:
|
782
|
+
self.advanced_output.insert(tk.END, stderr + "\n")
|
783
|
+
except Exception as e:
|
784
|
+
self.advanced_output.insert(tk.END, f"Error (pip-autoremove might not be installed): {e}\n")
|
785
|
+
self.advanced_output.insert(tk.END, "Finished cleanup.\n\n")
|
786
|
+
self.advanced_output.see(tk.END)
|
787
|
+
|
788
|
+
|
789
|
+
def create_dependency_graph_tab(self):
|
790
|
+
self.depgraph_frame = ttk.Frame(self.notebook)
|
791
|
+
self.notebook.add(self.depgraph_frame, text="Dependency Graph")
|
792
|
+
|
793
|
+
top_frame = ttk.Frame(self.depgraph_frame)
|
794
|
+
top_frame.pack(fill=tk.X, padx=10, pady=5)
|
795
|
+
ttk.Label(top_frame, text="Enter package name (leave blank for all):").pack(side=tk.LEFT)
|
796
|
+
self.dep_entry = ttk.Entry(top_frame, width=30)
|
797
|
+
self.dep_entry.pack(side=tk.LEFT, padx=5)
|
798
|
+
ttk.Button(top_frame, text="Generate Graph", command=self.generate_dependency_graph).pack(side=tk.LEFT, padx=5)
|
799
|
+
|
800
|
+
self.canvas_frame = ttk.Frame(self.depgraph_frame)
|
801
|
+
self.canvas_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
802
|
+
self.dep_message = ttk.Label(self.canvas_frame, text="Click 'Generate Graph' to display dependencies.")
|
803
|
+
self.dep_message.pack()
|
804
|
+
|
805
|
+
def generate_dependency_graph(self):
|
806
|
+
pkg_name = self.dep_entry.get().strip()
|
807
|
+
G = nx.DiGraph()
|
808
|
+
processed = set()
|
809
|
+
if pkg_name:
|
810
|
+
try:
|
811
|
+
self.build_dependency_tree(pkg_name, G, processed)
|
812
|
+
except Exception as e:
|
813
|
+
messagebox.showerror("Error", f"Error generating dependency graph: {e}")
|
814
|
+
return
|
815
|
+
else:
|
816
|
+
for dist in pkg_resources.working_set:
|
817
|
+
pname = dist.project_name
|
818
|
+
G.add_node(pname)
|
819
|
+
for req in dist.requires():
|
820
|
+
req_name = req.project_name
|
821
|
+
G.add_edge(pname, req_name)
|
822
|
+
if G.number_of_nodes() == 0:
|
823
|
+
messagebox.showinfo("No Dependencies", "No dependency information available.")
|
824
|
+
return
|
825
|
+
labels = {}
|
826
|
+
for node in G.nodes():
|
827
|
+
license_info = "N/A"
|
828
|
+
try:
|
829
|
+
dist = pkg_resources.get_distribution(node)
|
830
|
+
metadata = ""
|
831
|
+
if dist.has_metadata("METADATA"):
|
832
|
+
metadata = dist.get_metadata("METADATA")
|
833
|
+
elif dist.has_metadata("PKG-INFO"):
|
834
|
+
metadata = dist.get_metadata("PKG-INFO")
|
835
|
+
for line in metadata.splitlines():
|
836
|
+
if line.startswith("License:"):
|
837
|
+
license_info = line.split(":", 1)[1].strip()
|
838
|
+
break
|
839
|
+
except Exception:
|
840
|
+
pass
|
841
|
+
labels[node] = f"{node}\n({license_info})"
|
842
|
+
for widget in self.canvas_frame.winfo_children():
|
843
|
+
widget.destroy()
|
844
|
+
fig = plt.Figure(figsize=(8, 6))
|
845
|
+
ax = fig.add_subplot(111)
|
846
|
+
ax.set_title("Dependency Graph")
|
847
|
+
try:
|
848
|
+
pos = nx.nx_agraph.graphviz_layout(G, prog='dot')
|
849
|
+
except Exception as e:
|
850
|
+
print("Graphviz layout failed. Falling back to spring layout.", e)
|
851
|
+
pos = nx.spring_layout(G, k=0.5)
|
852
|
+
nx.draw_networkx(G, pos, ax=ax, labels=labels,
|
853
|
+
node_color="lightblue", edge_color="gray", font_size=8)
|
854
|
+
x_vals, y_vals = zip(*pos.values())
|
855
|
+
padding_x = (max(x_vals) - min(x_vals)) * 0.25
|
856
|
+
padding_y = (max(y_vals) - min(y_vals)) * 0.25
|
857
|
+
ax.set_xlim(min(x_vals) - padding_x, max(x_vals) + padding_x)
|
858
|
+
ax.set_ylim(min(y_vals) - padding_y, max(y_vals) + padding_y)
|
859
|
+
fig.tight_layout()
|
860
|
+
canvas = FigureCanvasTkAgg(fig, master=self.canvas_frame)
|
861
|
+
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
862
|
+
canvas.draw()
|
863
|
+
|
864
|
+
def build_dependency_tree(self, pkg_name, graph, processed):
|
865
|
+
if pkg_name in processed:
|
866
|
+
return
|
867
|
+
processed.add(pkg_name)
|
868
|
+
try:
|
869
|
+
dist = pkg_resources.get_distribution(pkg_name)
|
870
|
+
except Exception:
|
871
|
+
return
|
872
|
+
graph.add_node(pkg_name)
|
873
|
+
for req in dist.requires():
|
874
|
+
req_name = req.project_name
|
875
|
+
graph.add_edge(pkg_name, req_name)
|
876
|
+
self.build_dependency_tree(req_name, graph, processed)
|
877
|
+
|
878
|
+
|
879
|
+
def create_virtualenv_tab(self):
|
880
|
+
self.venv_frame = ttk.Frame(self.notebook)
|
881
|
+
self.notebook.add(self.venv_frame, text="Virtual Env Manager")
|
882
|
+
|
883
|
+
top_frame = ttk.Frame(self.venv_frame)
|
884
|
+
top_frame.pack(fill=tk.X, padx=10, pady=5)
|
885
|
+
ttk.Label(top_frame, text="Environment Name:").pack(side=tk.LEFT)
|
886
|
+
self.venv_entry = ttk.Entry(top_frame, width=30)
|
887
|
+
self.venv_entry.pack(side=tk.LEFT, padx=5)
|
888
|
+
ttk.Label(top_frame, text="Python Interpreter:").pack(side=tk.LEFT, padx=5)
|
889
|
+
self.python_interpreter_entry = ttk.Entry(top_frame, width=20)
|
890
|
+
self.python_interpreter_entry.insert(0, sys.executable)
|
891
|
+
self.python_interpreter_entry.pack(side=tk.LEFT, padx=5)
|
892
|
+
ttk.Button(top_frame, text="Create Environment", command=self.create_virtualenv).pack(side=tk.LEFT, padx=5)
|
893
|
+
|
894
|
+
list_frame = ttk.Frame(self.venv_frame)
|
895
|
+
list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
|
896
|
+
ttk.Label(list_frame, text="Available Environments:").pack(anchor=tk.W)
|
897
|
+
self.venv_listbox = tk.Listbox(list_frame, height=8)
|
898
|
+
self.venv_listbox.pack(fill=tk.BOTH, expand=True, side=tk.LEFT)
|
899
|
+
scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=self.venv_listbox.yview)
|
900
|
+
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
901
|
+
self.venv_listbox.config(yscrollcommand=scrollbar.set)
|
902
|
+
|
903
|
+
btn_frame = ttk.Frame(self.venv_frame)
|
904
|
+
btn_frame.pack(padx=10, pady=5)
|
905
|
+
ttk.Button(btn_frame, text="Refresh Environments", command=self.refresh_virtualenvs).pack(side=tk.LEFT, padx=5)
|
906
|
+
ttk.Button(btn_frame, text="Delete Selected Env", command=self.delete_virtualenv).pack(side=tk.LEFT, padx=5)
|
907
|
+
ttk.Button(btn_frame, text="Export Requirements", command=self.export_requirements).pack(side=tk.LEFT, padx=5)
|
908
|
+
ttk.Button(btn_frame, text="Import Requirements", command=self.import_requirements).pack(side=tk.LEFT, padx=5)
|
909
|
+
ttk.Button(btn_frame, text="Health Check", command=self.health_check_env).pack(side=tk.LEFT, padx=5)
|
910
|
+
|
911
|
+
def create_virtualenv(self):
|
912
|
+
env_name = self.venv_entry.get().strip()
|
913
|
+
if not env_name:
|
914
|
+
messagebox.showwarning("Input Required", "Please enter a name for the virtual environment.")
|
915
|
+
return
|
916
|
+
env_path = os.path.join(VENVS_DIR, env_name)
|
917
|
+
if os.path.exists(env_path):
|
918
|
+
messagebox.showwarning("Exists", "A virtual environment with this name already exists.")
|
919
|
+
return
|
920
|
+
interpreter = self.python_interpreter_entry.get().strip() or "python"
|
921
|
+
threading.Thread(target=self.run_create_virtualenv, args=(env_path, interpreter), daemon=True).start()
|
922
|
+
|
923
|
+
def run_create_virtualenv(self, env_path, interpreter):
|
924
|
+
self.show_info_in_venv_output(f"Creating virtual environment at {env_path} using {interpreter}...\n")
|
925
|
+
try:
|
926
|
+
process = subprocess.Popen([interpreter, "-m", "venv", env_path],
|
927
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
928
|
+
stdout, stderr = process.communicate()
|
929
|
+
output = (stdout if stdout else "") + (stderr if stderr else "")
|
930
|
+
self.show_info_in_venv_output(output)
|
931
|
+
except Exception as e:
|
932
|
+
self.show_info_in_venv_output(f"Error: {e}\n")
|
933
|
+
self.refresh_virtualenvs()
|
934
|
+
|
935
|
+
def refresh_virtualenvs(self):
|
936
|
+
self.venv_listbox.delete(0, tk.END)
|
937
|
+
try:
|
938
|
+
envs = [d for d in os.listdir(VENVS_DIR) if os.path.isdir(os.path.join(VENVS_DIR, d))]
|
939
|
+
for env in sorted(envs):
|
940
|
+
self.venv_listbox.insert(tk.END, env)
|
941
|
+
except Exception as e:
|
942
|
+
messagebox.showerror("Error", f"Failed to list virtual environments: {e}")
|
943
|
+
|
944
|
+
def delete_virtualenv(self):
|
945
|
+
selected = self.venv_listbox.curselection()
|
946
|
+
if not selected:
|
947
|
+
messagebox.showwarning("No Selection", "Please select an environment to delete.")
|
948
|
+
return
|
949
|
+
env_name = self.venv_listbox.get(selected[0])
|
950
|
+
env_path = os.path.join(VENVS_DIR, env_name)
|
951
|
+
if messagebox.askyesno("Confirm Delete", f"Are you sure you want to delete the environment '{env_name}'?"):
|
952
|
+
try:
|
953
|
+
shutil.rmtree(env_path)
|
954
|
+
messagebox.showinfo("Deleted", f"Environment '{env_name}' has been deleted.")
|
955
|
+
self.refresh_virtualenvs()
|
956
|
+
except Exception as e:
|
957
|
+
messagebox.showerror("Error", f"Failed to delete environment: {e}")
|
958
|
+
|
959
|
+
def export_requirements(self):
|
960
|
+
selected = self.venv_listbox.curselection()
|
961
|
+
if not selected:
|
962
|
+
messagebox.showwarning("No Selection", "Select an environment to export requirements.")
|
963
|
+
return
|
964
|
+
env_name = self.venv_listbox.get(selected[0])
|
965
|
+
env_path = os.path.join(VENVS_DIR, env_name)
|
966
|
+
pip_path = self.get_env_pip_path(env_path)
|
967
|
+
file_path = filedialog.asksaveasfilename(defaultextension=".txt", title="Export Requirements",
|
968
|
+
filetypes=[("Text Files", "*.txt")])
|
969
|
+
if not file_path:
|
970
|
+
return
|
971
|
+
try:
|
972
|
+
process = subprocess.Popen([pip_path, "freeze"],
|
973
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
974
|
+
stdout, _ = process.communicate()
|
975
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
976
|
+
f.write(stdout)
|
977
|
+
messagebox.showinfo("Exported", f"Requirements exported to {file_path}")
|
978
|
+
except Exception as e:
|
979
|
+
messagebox.showerror("Error", f"Failed to export requirements: {e}")
|
980
|
+
|
981
|
+
def import_requirements(self):
|
982
|
+
selected = self.venv_listbox.curselection()
|
983
|
+
if not selected:
|
984
|
+
messagebox.showwarning("No Selection", "Select an environment to import requirements.")
|
985
|
+
return
|
986
|
+
env_name = self.venv_listbox.get(selected[0])
|
987
|
+
env_path = os.path.join(VENVS_DIR, env_name)
|
988
|
+
pip_path = self.get_env_pip_path(env_path)
|
989
|
+
file_path = filedialog.askopenfilename(title="Import Requirements",
|
990
|
+
filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")])
|
991
|
+
if not file_path:
|
992
|
+
return
|
993
|
+
try:
|
994
|
+
process = subprocess.Popen([pip_path, "install", "-r", file_path],
|
995
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
996
|
+
stdout, stderr = process.communicate()
|
997
|
+
messagebox.showinfo("Imported", f"Requirements imported:\n{stdout}\n{stderr}")
|
998
|
+
except Exception as e:
|
999
|
+
messagebox.showerror("Error", f"Failed to import requirements: {e}")
|
1000
|
+
|
1001
|
+
def health_check_env(self):
|
1002
|
+
selected = self.venv_listbox.curselection()
|
1003
|
+
if not selected:
|
1004
|
+
messagebox.showwarning("No Selection", "Select an environment for health check.")
|
1005
|
+
return
|
1006
|
+
env_name = self.venv_listbox.get(selected[0])
|
1007
|
+
env_path = os.path.join(VENVS_DIR, env_name)
|
1008
|
+
pip_path = self.get_env_pip_path(env_path)
|
1009
|
+
def run_check():
|
1010
|
+
try:
|
1011
|
+
process = subprocess.Popen([pip_path, "check"],
|
1012
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
1013
|
+
stdout, stderr = process.communicate()
|
1014
|
+
result = stdout if stdout else stderr
|
1015
|
+
messagebox.showinfo("Environment Health Check", result)
|
1016
|
+
except Exception as e:
|
1017
|
+
messagebox.showerror("Error", f"Health check failed: {e}")
|
1018
|
+
threading.Thread(target=run_check, daemon=True).start()
|
1019
|
+
|
1020
|
+
def get_env_pip_path(self, env_path):
|
1021
|
+
if os.name == 'nt':
|
1022
|
+
return os.path.join(env_path, "Scripts", "pip.exe")
|
1023
|
+
else:
|
1024
|
+
return os.path.join(env_path, "bin", "pip")
|
1025
|
+
|
1026
|
+
def show_info_in_venv_output(self, text):
|
1027
|
+
messagebox.showinfo("Virtual Environment Manager", text)
|
1028
|
+
|
1029
|
+
|
1030
|
+
def create_env_comparison_tab(self):
|
1031
|
+
self.env_comp_frame = ttk.Frame(self.notebook)
|
1032
|
+
self.notebook.add(self.env_comp_frame, text="Env Comparison")
|
1033
|
+
|
1034
|
+
comp_top = ttk.Frame(self.env_comp_frame)
|
1035
|
+
comp_top.pack(fill=tk.X, padx=10, pady=5)
|
1036
|
+
ttk.Label(comp_top, text="Select Env A:").pack(side=tk.LEFT, padx=5)
|
1037
|
+
self.env_a_combo = ttk.Combobox(comp_top, state="readonly", width=20)
|
1038
|
+
self.env_a_combo.pack(side=tk.LEFT, padx=5)
|
1039
|
+
|
1040
|
+
ttk.Label(comp_top, text="Select Env B:").pack(side=tk.LEFT, padx=5)
|
1041
|
+
self.env_b_combo = ttk.Combobox(comp_top, state="readonly", width=20)
|
1042
|
+
self.env_b_combo.pack(side=tk.LEFT, padx=5)
|
1043
|
+
|
1044
|
+
ttk.Button(comp_top, text="Compare Environments", command=self.compare_envs).pack(side=tk.LEFT, padx=5)
|
1045
|
+
|
1046
|
+
self.env_comp_output = scrolledtext.ScrolledText(self.env_comp_frame, height=15)
|
1047
|
+
self.env_comp_output.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
1048
|
+
|
1049
|
+
def refresh_env_comparison_options(self):
|
1050
|
+
try:
|
1051
|
+
envs = [d for d in os.listdir(VENVS_DIR) if os.path.isdir(os.path.join(VENVS_DIR, d))]
|
1052
|
+
envs_sorted = sorted(envs)
|
1053
|
+
self.env_a_combo['values'] = envs_sorted
|
1054
|
+
self.env_b_combo['values'] = envs_sorted
|
1055
|
+
except Exception as e:
|
1056
|
+
messagebox.showerror("Error", f"Failed to load environments: {e}")
|
1057
|
+
|
1058
|
+
def compare_envs(self):
|
1059
|
+
env_a = self.env_a_combo.get()
|
1060
|
+
env_b = self.env_b_combo.get()
|
1061
|
+
if not env_a or not env_b:
|
1062
|
+
messagebox.showwarning("Input Required", "Please select both environments.")
|
1063
|
+
return
|
1064
|
+
env_a_path = os.path.join(VENVS_DIR, env_a)
|
1065
|
+
env_b_path = os.path.join(VENVS_DIR, env_b)
|
1066
|
+
pip_a = self.get_env_pip_path(env_a_path)
|
1067
|
+
pip_b = self.get_env_pip_path(env_b_path)
|
1068
|
+
try:
|
1069
|
+
proc_a = subprocess.Popen([pip_a, "freeze"],
|
1070
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
1071
|
+
out_a, _ = proc_a.communicate()
|
1072
|
+
|
1073
|
+
proc_b = subprocess.Popen([pip_b, "freeze"],
|
1074
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
1075
|
+
out_b, _ = proc_b.communicate()
|
1076
|
+
|
1077
|
+
pkgs_a = set(out_a.splitlines())
|
1078
|
+
pkgs_b = set(out_b.splitlines())
|
1079
|
+
|
1080
|
+
only_a = pkgs_a - pkgs_b
|
1081
|
+
only_b = pkgs_b - pkgs_a
|
1082
|
+
common = pkgs_a & pkgs_b
|
1083
|
+
|
1084
|
+
result_text = "Packages only in Env A:\n" + "\n".join(sorted(only_a)) + "\n\n"
|
1085
|
+
result_text += "Packages only in Env B:\n" + "\n".join(sorted(only_b)) + "\n\n"
|
1086
|
+
result_text += "Common Packages:\n" + "\n".join(sorted(common)) + "\n\n"
|
1087
|
+
|
1088
|
+
self.env_comp_output.delete("1.0", tk.END)
|
1089
|
+
self.env_comp_output.insert(tk.END, result_text)
|
1090
|
+
except Exception as e:
|
1091
|
+
messagebox.showerror("Error", f"Error comparing environments: {e}")
|
1092
|
+
|
1093
|
+
|
1094
|
+
def create_localrepo_tab(self):
|
1095
|
+
|
1096
|
+
self.localrepo_frame = ttk.Frame(self.notebook)
|
1097
|
+
self.notebook.add(self.localrepo_frame, text="Local Repo")
|
1098
|
+
|
1099
|
+
|
1100
|
+
top_frame = ttk.Frame(self.localrepo_frame)
|
1101
|
+
top_frame.pack(fill=tk.X, padx=10, pady=5)
|
1102
|
+
ttk.Label(top_frame, text="Package name to download:").pack(side=tk.LEFT, padx=5)
|
1103
|
+
self.localrepo_entry = ttk.Entry(top_frame, width=30)
|
1104
|
+
self.localrepo_entry.pack(side=tk.LEFT, padx=5)
|
1105
|
+
ttk.Button(top_frame, text="Download Package", command=self.download_package_to_localrepo).pack(side=tk.LEFT, padx=5)
|
1106
|
+
ttk.Button(top_frame, text="Refresh Repo List", command=self.refresh_local_repo).pack(side=tk.LEFT, padx=5)
|
1107
|
+
|
1108
|
+
|
1109
|
+
self.localrepo_listbox = tk.Listbox(self.localrepo_frame, height=10)
|
1110
|
+
self.localrepo_listbox.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
1111
|
+
|
1112
|
+
|
1113
|
+
bottom_frame = ttk.Frame(self.localrepo_frame)
|
1114
|
+
bottom_frame.pack(fill=tk.X, padx=10, pady=5)
|
1115
|
+
ttk.Button(bottom_frame, text="Install from Local Repo", command=self.install_from_local_repo).pack(side=tk.LEFT, padx=5)
|
1116
|
+
ttk.Button(bottom_frame, text="Delete Package", command=self.delete_package).pack(side=tk.LEFT, padx=5)
|
1117
|
+
self.progress = ttk.Progressbar(bottom_frame, mode="indeterminate")
|
1118
|
+
self.progress.pack(side=tk.RIGHT, fill=tk.X, expand=True, padx=5)
|
1119
|
+
|
1120
|
+
|
1121
|
+
log_frame = ttk.Labelframe(self.localrepo_frame, text="Operation Log")
|
1122
|
+
log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
|
1123
|
+
self.log_text = tk.Text(log_frame, height=8, state="disabled", wrap="word")
|
1124
|
+
self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
1125
|
+
ttk.Button(log_frame, text="Clear Log", command=self.clear_log).pack(padx=5, pady=5)
|
1126
|
+
|
1127
|
+
self.refresh_local_repo()
|
1128
|
+
|
1129
|
+
|
1130
|
+
def log_message(self, message):
|
1131
|
+
"""Append messages to the log pane."""
|
1132
|
+
self.log_text.config(state="normal")
|
1133
|
+
self.log_text.insert(tk.END, message + "\n")
|
1134
|
+
self.log_text.config(state="disabled")
|
1135
|
+
self.log_text.see(tk.END)
|
1136
|
+
|
1137
|
+
|
1138
|
+
def clear_log(self):
|
1139
|
+
"""Clears the operation log."""
|
1140
|
+
self.log_text.config(state="normal")
|
1141
|
+
self.log_text.delete("1.0", tk.END)
|
1142
|
+
self.log_text.config(state="disabled")
|
1143
|
+
|
1144
|
+
|
1145
|
+
def download_package_to_localrepo(self):
|
1146
|
+
pkg = self.localrepo_entry.get().strip()
|
1147
|
+
if not pkg:
|
1148
|
+
messagebox.showwarning("Input Required", "Please enter a package name to download.")
|
1149
|
+
return
|
1150
|
+
|
1151
|
+
threading.Thread(target=self.run_download_localrepo, args=(pkg,), daemon=True).start()
|
1152
|
+
|
1153
|
+
|
1154
|
+
def run_download_localrepo(self, pkg):
|
1155
|
+
try:
|
1156
|
+
self.progress.start()
|
1157
|
+
self.log_message(f"Downloading package: {pkg}")
|
1158
|
+
process = subprocess.Popen(
|
1159
|
+
["pip", "download", pkg, "-d", LOCAL_REPO_DIR],
|
1160
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
1161
|
+
)
|
1162
|
+
stdout, stderr = process.communicate()
|
1163
|
+
result = stdout + "\n" + stderr
|
1164
|
+
self.log_message("Download results:\n" + result)
|
1165
|
+
messagebox.showinfo("Download", result)
|
1166
|
+
self.refresh_local_repo()
|
1167
|
+
except Exception as e:
|
1168
|
+
messagebox.showerror("Error", f"Download failed: {e}")
|
1169
|
+
self.log_message(f"Error downloading package {pkg}: {e}")
|
1170
|
+
finally:
|
1171
|
+
self.progress.stop()
|
1172
|
+
|
1173
|
+
|
1174
|
+
def refresh_local_repo(self):
|
1175
|
+
self.localrepo_listbox.delete(0, tk.END)
|
1176
|
+
try:
|
1177
|
+
files = os.listdir(LOCAL_REPO_DIR)
|
1178
|
+
for f in files:
|
1179
|
+
self.localrepo_listbox.insert(tk.END, f)
|
1180
|
+
self.log_message("Local repository refreshed.")
|
1181
|
+
except Exception as e:
|
1182
|
+
messagebox.showerror("Error", f"Failed to list local repository: {e}")
|
1183
|
+
self.log_message(f"Error refreshing local repository: {e}")
|
1184
|
+
|
1185
|
+
|
1186
|
+
def install_from_local_repo(self):
|
1187
|
+
selected = self.localrepo_listbox.curselection()
|
1188
|
+
if not selected:
|
1189
|
+
messagebox.showwarning("No Selection", "Select a package file to install.")
|
1190
|
+
return
|
1191
|
+
filename = self.localrepo_listbox.get(selected[0])
|
1192
|
+
pkg_path = os.path.join(LOCAL_REPO_DIR, filename)
|
1193
|
+
threading.Thread(target=self.run_install_from_local, args=(pkg_path,), daemon=True).start()
|
1194
|
+
|
1195
|
+
|
1196
|
+
def run_install_from_local(self, pkg_path):
|
1197
|
+
try:
|
1198
|
+
self.progress.start()
|
1199
|
+
self.log_message(f"Installing package from: {pkg_path}")
|
1200
|
+
process = subprocess.Popen(
|
1201
|
+
["pip", "install", pkg_path],
|
1202
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
1203
|
+
)
|
1204
|
+
stdout, stderr = process.communicate()
|
1205
|
+
result = stdout + "\n" + stderr
|
1206
|
+
self.log_message("Installation results:\n" + result)
|
1207
|
+
messagebox.showinfo("Install from Local Repo", result)
|
1208
|
+
except Exception as e:
|
1209
|
+
messagebox.showerror("Error", f"Installation failed: {e}")
|
1210
|
+
self.log_message(f"Error installing package from {pkg_path}: {e}")
|
1211
|
+
finally:
|
1212
|
+
self.progress.stop()
|
1213
|
+
|
1214
|
+
|
1215
|
+
def delete_package(self):
|
1216
|
+
selected = self.localrepo_listbox.curselection()
|
1217
|
+
if not selected:
|
1218
|
+
messagebox.showwarning("No Selection", "Select a package file to delete.")
|
1219
|
+
return
|
1220
|
+
filename = self.localrepo_listbox.get(selected[0])
|
1221
|
+
pkg_path = os.path.join(LOCAL_REPO_DIR, filename)
|
1222
|
+
if messagebox.askyesno("Delete Confirmation", f"Are you sure you want to delete {filename}?"):
|
1223
|
+
try:
|
1224
|
+
os.remove(pkg_path)
|
1225
|
+
self.log_message(f"Deleted package file: {filename}")
|
1226
|
+
self.refresh_local_repo()
|
1227
|
+
except Exception as e:
|
1228
|
+
messagebox.showerror("Error", f"Failed to delete {filename}: {e}")
|
1229
|
+
self.log_message(f"Error deleting package {filename}: {e}")
|
1230
|
+
|
1231
|
+
|
1232
|
+
|
1233
|
+
def create_package_groups_tab(self):
|
1234
|
+
self.package_groups_frame = ttk.Frame(self.notebook)
|
1235
|
+
self.notebook.add(self.package_groups_frame, text="Package Groups")
|
1236
|
+
|
1237
|
+
group_frame = ttk.Frame(self.package_groups_frame)
|
1238
|
+
group_frame.pack(fill=tk.X, padx=10, pady=5)
|
1239
|
+
ttk.Label(group_frame, text="Group Name:").pack(side=tk.LEFT, padx=5)
|
1240
|
+
self.group_name_entry = ttk.Entry(group_frame, width=20)
|
1241
|
+
self.group_name_entry.pack(side=tk.LEFT, padx=5)
|
1242
|
+
ttk.Label(group_frame, text="Packages (comma separated):").pack(side=tk.LEFT, padx=5)
|
1243
|
+
self.group_packages_entry = ttk.Entry(group_frame, width=40)
|
1244
|
+
self.group_packages_entry.pack(side=tk.LEFT, padx=5)
|
1245
|
+
ttk.Button(group_frame, text="Add Group", command=self.add_package_group).pack(side=tk.LEFT, padx=5)
|
1246
|
+
|
1247
|
+
list_frame = ttk.Frame(self.package_groups_frame)
|
1248
|
+
list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
|
1249
|
+
ttk.Label(list_frame, text="Saved Package Groups:").pack(anchor=tk.W)
|
1250
|
+
self.group_listbox = tk.Listbox(list_frame, height=8)
|
1251
|
+
self.group_listbox.pack(fill=tk.BOTH, expand=True, side=tk.LEFT)
|
1252
|
+
scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=self.group_listbox.yview)
|
1253
|
+
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
1254
|
+
self.group_listbox.config(yscrollcommand=scrollbar.set)
|
1255
|
+
|
1256
|
+
btn_frame = ttk.Frame(self.package_groups_frame)
|
1257
|
+
btn_frame.pack(padx=10, pady=5)
|
1258
|
+
ttk.Button(btn_frame, text="Install Selected Group", command=self.install_group).pack(side=tk.LEFT, padx=5)
|
1259
|
+
ttk.Button(btn_frame, text="Delete Selected Group", command=self.delete_group).pack(side=tk.LEFT, padx=5)
|
1260
|
+
|
1261
|
+
self.package_groups = {}
|
1262
|
+
|
1263
|
+
def add_package_group(self):
|
1264
|
+
group_name = self.group_name_entry.get().strip()
|
1265
|
+
pkg_list_str = self.group_packages_entry.get().strip()
|
1266
|
+
if not group_name or not pkg_list_str:
|
1267
|
+
messagebox.showwarning("Input Required", "Provide both group name and package list.")
|
1268
|
+
return
|
1269
|
+
packages = [pkg.strip() for pkg in pkg_list_str.split(",") if pkg.strip()]
|
1270
|
+
self.package_groups[group_name] = packages
|
1271
|
+
self.refresh_package_groups()
|
1272
|
+
self.group_name_entry.delete(0, tk.END)
|
1273
|
+
self.group_packages_entry.delete(0, tk.END)
|
1274
|
+
|
1275
|
+
def refresh_package_groups(self):
|
1276
|
+
self.group_listbox.delete(0, tk.END)
|
1277
|
+
for group_name, packages in self.package_groups.items():
|
1278
|
+
display = f"{group_name}: {', '.join(packages)}"
|
1279
|
+
self.group_listbox.insert(tk.END, display)
|
1280
|
+
|
1281
|
+
def install_group(self):
|
1282
|
+
selected = self.group_listbox.curselection()
|
1283
|
+
if not selected:
|
1284
|
+
messagebox.showwarning("No Selection", "Select a group to install.")
|
1285
|
+
return
|
1286
|
+
group_entry = self.group_listbox.get(selected[0])
|
1287
|
+
group_name = group_entry.split(":")[0].strip()
|
1288
|
+
packages = self.package_groups.get(group_name, [])
|
1289
|
+
if not packages:
|
1290
|
+
messagebox.showinfo("Empty Group", "No packages found in the selected group.")
|
1291
|
+
return
|
1292
|
+
cmd = ["pip", "install"] + packages
|
1293
|
+
threading.Thread(target=self.run_pip_command_group, args=(cmd,), daemon=True).start()
|
1294
|
+
|
1295
|
+
def run_pip_command_group(self, command):
|
1296
|
+
try:
|
1297
|
+
process = subprocess.Popen(command,
|
1298
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
1299
|
+
stdout, stderr = process.communicate()
|
1300
|
+
result = stdout + "\n" + stderr
|
1301
|
+
messagebox.showinfo("Install Group", result)
|
1302
|
+
except Exception as e:
|
1303
|
+
messagebox.showerror("Error", f"Error installing group: {e}")
|
1304
|
+
|
1305
|
+
def delete_group(self):
|
1306
|
+
selected = self.group_listbox.curselection()
|
1307
|
+
if not selected:
|
1308
|
+
messagebox.showwarning("No Selection", "Select a group to delete.")
|
1309
|
+
return
|
1310
|
+
group_entry = self.group_listbox.get(selected[0])
|
1311
|
+
group_name = group_entry.split(":")[0].strip()
|
1312
|
+
if group_name in self.package_groups:
|
1313
|
+
del self.package_groups[group_name]
|
1314
|
+
self.refresh_package_groups()
|
1315
|
+
|
1316
|
+
|
1317
|
+
def create_pip_command_builder_tab(self):
|
1318
|
+
self.pip_builder_frame = ttk.Frame(self.notebook)
|
1319
|
+
self.notebook.add(self.pip_builder_frame, text="Pip Command Builder")
|
1320
|
+
|
1321
|
+
top_frame = ttk.Frame(self.pip_builder_frame)
|
1322
|
+
top_frame.pack(fill=tk.X, padx=10, pady=5)
|
1323
|
+
|
1324
|
+
|
1325
|
+
ttk.Label(top_frame, text="Package Name:").pack(side=tk.LEFT, padx=5)
|
1326
|
+
self.builder_pkg_entry = ttk.Entry(top_frame, width=20)
|
1327
|
+
self.builder_pkg_entry.pack(side=tk.LEFT, padx=5)
|
1328
|
+
|
1329
|
+
|
1330
|
+
ttk.Label(top_frame, text="Index URL:").pack(side=tk.LEFT, padx=5)
|
1331
|
+
self.builder_index_entry = ttk.Entry(top_frame, width=20)
|
1332
|
+
self.builder_index_entry.pack(side=tk.LEFT, padx=5)
|
1333
|
+
|
1334
|
+
|
1335
|
+
ttk.Label(top_frame, text="Venv Name:").pack(side=tk.LEFT, padx=5)
|
1336
|
+
self.builder_venv_entry = ttk.Entry(top_frame, width=15)
|
1337
|
+
self.builder_venv_entry.pack(side=tk.LEFT, padx=5)
|
1338
|
+
|
1339
|
+
|
1340
|
+
self.builder_install_var = tk.BooleanVar()
|
1341
|
+
self.builder_upgrade_var = tk.BooleanVar()
|
1342
|
+
self.builder_no_deps_var = tk.BooleanVar()
|
1343
|
+
self.builder_user_var = tk.BooleanVar()
|
1344
|
+
self.builder_dry_run_var = tk.BooleanVar()
|
1345
|
+
self.builder_offline_var = tk.BooleanVar()
|
1346
|
+
|
1347
|
+
ttk.Checkbutton(top_frame, text="Install", variable=self.builder_install_var).pack(side=tk.LEFT, padx=5)
|
1348
|
+
ttk.Checkbutton(top_frame, text="Upgrade", variable=self.builder_upgrade_var).pack(side=tk.LEFT, padx=5)
|
1349
|
+
ttk.Checkbutton(top_frame, text="No-deps", variable=self.builder_no_deps_var).pack(side=tk.LEFT, padx=5)
|
1350
|
+
ttk.Checkbutton(top_frame, text="User", variable=self.builder_user_var).pack(side=tk.LEFT, padx=5)
|
1351
|
+
ttk.Checkbutton(top_frame, text="Dry Run", variable=self.builder_dry_run_var).pack(side=tk.LEFT, padx=5)
|
1352
|
+
ttk.Checkbutton(top_frame, text="Offline", variable=self.builder_offline_var).pack(side=tk.LEFT, padx=5)
|
1353
|
+
|
1354
|
+
|
1355
|
+
ttk.Button(top_frame, text="Build & Run Command", command=self.run_pip_command_builder).pack(side=tk.LEFT, padx=5)
|
1356
|
+
ttk.Button(top_frame, text="Check Dependencies", command=self.check_dependencies).pack(side=tk.LEFT, padx=5)
|
1357
|
+
ttk.Button(top_frame, text="Package Info", command=self.fetch_package_info).pack(side=tk.LEFT, padx=5)
|
1358
|
+
ttk.Button(top_frame, text="Suggest Packages", command=self.suggest_packages).pack(side=tk.LEFT, padx=5)
|
1359
|
+
ttk.Button(top_frame, text="Usage Stats", command=self.show_usage_stats).pack(side=tk.LEFT, padx=5)
|
1360
|
+
ttk.Button(top_frame, text="Save Config", command=self.save_config).pack(side=tk.LEFT, padx=5)
|
1361
|
+
ttk.Button(top_frame, text="Load Config", command=self.load_config).pack(side=tk.LEFT, padx=5)
|
1362
|
+
ttk.Button(top_frame, text="Create Venv", command=self.create_virtual_env).pack(side=tk.LEFT, padx=5)
|
1363
|
+
ttk.Button(top_frame, text="Clear Output", command=self.clear_output).pack(side=tk.LEFT, padx=5)
|
1364
|
+
|
1365
|
+
|
1366
|
+
self.builder_output = scrolledtext.ScrolledText(self.pip_builder_frame, height=15)
|
1367
|
+
self.builder_output.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
1368
|
+
|
1369
|
+
def run_pip_command_builder(self):
|
1370
|
+
pkg = self.builder_pkg_entry.get().strip()
|
1371
|
+
if not pkg:
|
1372
|
+
messagebox.showwarning("Input Required", "Enter a package name.")
|
1373
|
+
return
|
1374
|
+
cmd = ["pip"]
|
1375
|
+
if self.builder_upgrade_var.get():
|
1376
|
+
cmd.append("install")
|
1377
|
+
cmd.append("--upgrade")
|
1378
|
+
elif self.builder_install_var.get():
|
1379
|
+
cmd.append("install")
|
1380
|
+
else:
|
1381
|
+
cmd.append("install")
|
1382
|
+
if self.builder_no_deps_var.get():
|
1383
|
+
cmd.append("--no-deps")
|
1384
|
+
if self.builder_user_var.get():
|
1385
|
+
cmd.append("--user")
|
1386
|
+
|
1387
|
+
index_url = self.builder_index_entry.get().strip()
|
1388
|
+
if index_url:
|
1389
|
+
cmd.append("--index-url")
|
1390
|
+
cmd.append(index_url)
|
1391
|
+
cmd.append(pkg)
|
1392
|
+
self.builder_output.insert(tk.END, f"Running command: {' '.join(cmd)}\n")
|
1393
|
+
self.builder_output.see(tk.END)
|
1394
|
+
|
1395
|
+
if self.builder_dry_run_var.get():
|
1396
|
+
self.builder_output.insert(tk.END, "[Dry Run] Command not executed.\n")
|
1397
|
+
self.builder_output.see(tk.END)
|
1398
|
+
self.add_to_command_history(cmd, "[Dry Run] Command not executed.\n")
|
1399
|
+
return
|
1400
|
+
threading.Thread(target=self.run_pip_command_builder_thread, args=(cmd,), daemon=True).start()
|
1401
|
+
|
1402
|
+
def run_pip_command_builder_thread(self, cmd):
|
1403
|
+
try:
|
1404
|
+
process = subprocess.Popen(cmd,
|
1405
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
1406
|
+
stdout, stderr = process.communicate()
|
1407
|
+
output = stdout + "\n" + stderr
|
1408
|
+
self.builder_output.insert(tk.END, output + "\n")
|
1409
|
+
diagnostics = self.get_error_diagnostics(output)
|
1410
|
+
if diagnostics:
|
1411
|
+
self.builder_output.insert(tk.END, "Diagnostics: " + diagnostics + "\n")
|
1412
|
+
self.builder_output.see(tk.END)
|
1413
|
+
self.add_to_command_history(cmd, output)
|
1414
|
+
except Exception as e:
|
1415
|
+
self.builder_output.insert(tk.END, f"Error: {e}\n")
|
1416
|
+
self.builder_output.see(tk.END)
|
1417
|
+
|
1418
|
+
def check_dependencies(self):
|
1419
|
+
pkg = self.builder_pkg_entry.get().strip()
|
1420
|
+
if not pkg:
|
1421
|
+
messagebox.showwarning("Input Required", "Enter a package name.")
|
1422
|
+
return
|
1423
|
+
cmd = ["pip", "show", pkg]
|
1424
|
+
try:
|
1425
|
+
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
1426
|
+
stdout, stderr = process.communicate()
|
1427
|
+
output = stdout if stdout else stderr
|
1428
|
+
self.builder_output.insert(tk.END, f"Dependencies for {pkg}:\n{output}\n")
|
1429
|
+
self.builder_output.see(tk.END)
|
1430
|
+
except Exception as e:
|
1431
|
+
self.builder_output.insert(tk.END, f"Error checking dependencies: {e}\n")
|
1432
|
+
self.builder_output.see(tk.END)
|
1433
|
+
|
1434
|
+
def fetch_package_info(self):
|
1435
|
+
pkg = self.builder_pkg_entry.get().strip()
|
1436
|
+
if not pkg:
|
1437
|
+
messagebox.showwarning("Input Required", "Enter a package name.")
|
1438
|
+
return
|
1439
|
+
if self.builder_offline_var.get():
|
1440
|
+
self.builder_output.insert(tk.END, "Offline Mode enabled. Cannot fetch package info.\n")
|
1441
|
+
self.builder_output.see(tk.END)
|
1442
|
+
return
|
1443
|
+
try:
|
1444
|
+
response = requests.get(f"https://pypi.org/pypi/{pkg}/json")
|
1445
|
+
if response.status_code == 200:
|
1446
|
+
data = response.json()
|
1447
|
+
info = data.get("info", {})
|
1448
|
+
summary = info.get("summary", "No description available")
|
1449
|
+
version = info.get("version", "Unknown")
|
1450
|
+
self.builder_output.insert(tk.END,
|
1451
|
+
f"Package Info:\nName: {pkg}\nVersion: {version}\nSummary: {summary}\n\n")
|
1452
|
+
else:
|
1453
|
+
self.builder_output.insert(tk.END, f"Package {pkg} not found on PyPI.\n")
|
1454
|
+
self.builder_output.see(tk.END)
|
1455
|
+
except Exception as e:
|
1456
|
+
self.builder_output.insert(tk.END, f"Error fetching package info: {e}\n")
|
1457
|
+
self.builder_output.see(tk.END)
|
1458
|
+
|
1459
|
+
def suggest_packages(self):
|
1460
|
+
partial = self.builder_pkg_entry.get().strip().lower()
|
1461
|
+
if not partial:
|
1462
|
+
messagebox.showwarning("Input Required", "Enter part of a package name for suggestions.")
|
1463
|
+
return
|
1464
|
+
|
1465
|
+
popular = ["numpy", "pandas", "requests", "flask", "django", "scipy", "matplotlib", "tensorflow", "pytest", "beautifulsoup4"]
|
1466
|
+
suggestions = [pkg for pkg in popular if pkg.startswith(partial)]
|
1467
|
+
sugg_text = "Suggestions:\n" + "\n".join(suggestions) if suggestions else "No suggestions found."
|
1468
|
+
self.builder_output.insert(tk.END, sugg_text + "\n")
|
1469
|
+
self.builder_output.see(tk.END)
|
1470
|
+
|
1471
|
+
def show_usage_stats(self):
|
1472
|
+
stats = {}
|
1473
|
+
for entry in getattr(self, 'command_history', []):
|
1474
|
+
cmd_str = entry["command"]
|
1475
|
+
stats[cmd_str] = stats.get(cmd_str, 0) + 1
|
1476
|
+
if stats:
|
1477
|
+
stats_text = "Usage Statistics:\n"
|
1478
|
+
for cmd, count in stats.items():
|
1479
|
+
stats_text += f"{cmd} : {count} times\n"
|
1480
|
+
else:
|
1481
|
+
stats_text = "No usage statistics available."
|
1482
|
+
self.builder_output.insert(tk.END, stats_text + "\n")
|
1483
|
+
self.builder_output.see(tk.END)
|
1484
|
+
|
1485
|
+
def save_config(self):
|
1486
|
+
config = {
|
1487
|
+
"install": self.builder_install_var.get(),
|
1488
|
+
"upgrade": self.builder_upgrade_var.get(),
|
1489
|
+
"no_deps": self.builder_no_deps_var.get(),
|
1490
|
+
"user": self.builder_user_var.get(),
|
1491
|
+
"dry_run": self.builder_dry_run_var.get(),
|
1492
|
+
"offline": self.builder_offline_var.get(),
|
1493
|
+
"index_url": self.builder_index_entry.get().strip()
|
1494
|
+
}
|
1495
|
+
try:
|
1496
|
+
with open("pip_config.json", "w") as f:
|
1497
|
+
json.dump(config, f)
|
1498
|
+
self.builder_output.insert(tk.END, "Configuration saved.\n")
|
1499
|
+
self.builder_output.see(tk.END)
|
1500
|
+
except Exception as e:
|
1501
|
+
self.builder_output.insert(tk.END, f"Error saving config: {e}\n")
|
1502
|
+
self.builder_output.see(tk.END)
|
1503
|
+
|
1504
|
+
def load_config(self):
|
1505
|
+
try:
|
1506
|
+
if not os.path.exists("pip_config.json"):
|
1507
|
+
self.builder_output.insert(tk.END, "No saved configuration found.\n")
|
1508
|
+
self.builder_output.see(tk.END)
|
1509
|
+
return
|
1510
|
+
with open("pip_config.json", "r") as f:
|
1511
|
+
config = json.load(f)
|
1512
|
+
self.builder_install_var.set(config.get("install", False))
|
1513
|
+
self.builder_upgrade_var.set(config.get("upgrade", False))
|
1514
|
+
self.builder_no_deps_var.set(config.get("no_deps", False))
|
1515
|
+
self.builder_user_var.set(config.get("user", False))
|
1516
|
+
self.builder_dry_run_var.set(config.get("dry_run", False))
|
1517
|
+
self.builder_offline_var.set(config.get("offline", False))
|
1518
|
+
self.builder_index_entry.delete(0, tk.END)
|
1519
|
+
self.builder_index_entry.insert(0, config.get("index_url", ""))
|
1520
|
+
self.builder_output.insert(tk.END, "Configuration loaded.\n")
|
1521
|
+
self.builder_output.see(tk.END)
|
1522
|
+
except Exception as e:
|
1523
|
+
self.builder_output.insert(tk.END, f"Error loading config: {e}\n")
|
1524
|
+
self.builder_output.see(tk.END)
|
1525
|
+
|
1526
|
+
def create_virtual_env(self):
|
1527
|
+
venv_name = self.builder_venv_entry.get().strip()
|
1528
|
+
if not venv_name:
|
1529
|
+
messagebox.showwarning("Input Required", "Enter a virtual environment name.")
|
1530
|
+
return
|
1531
|
+
cmd = ["python", "-m", "venv", venv_name]
|
1532
|
+
try:
|
1533
|
+
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
1534
|
+
stdout, stderr = process.communicate()
|
1535
|
+
output = stdout + "\n" + stderr
|
1536
|
+
self.builder_output.insert(tk.END, f"Virtual environment creation output:\n{output}\n")
|
1537
|
+
self.builder_output.see(tk.END)
|
1538
|
+
except Exception as e:
|
1539
|
+
self.builder_output.insert(tk.END, f"Error creating virtual environment: {e}\n")
|
1540
|
+
self.builder_output.see(tk.END)
|
1541
|
+
|
1542
|
+
def clear_output(self):
|
1543
|
+
self.builder_output.delete("1.0", tk.END)
|
1544
|
+
|
1545
|
+
def get_error_diagnostics(self, output):
|
1546
|
+
diagnostics = []
|
1547
|
+
lower = output.lower()
|
1548
|
+
if "permission denied" in lower:
|
1549
|
+
diagnostics.append("Permission issue detected. Try using '--user' or run as administrator.")
|
1550
|
+
if "no matching distribution found" in lower or "could not find a version" in lower:
|
1551
|
+
diagnostics.append("Package not found. Verify package name or check its availability on PyPI.")
|
1552
|
+
return " ".join(diagnostics)
|
1553
|
+
|
1554
|
+
def add_to_command_history(self, cmd, output):
|
1555
|
+
if not hasattr(self, 'command_history'):
|
1556
|
+
self.command_history = []
|
1557
|
+
self.command_history.append({"command": " ".join(cmd), "output": output})
|
1558
|
+
|
1559
|
+
|
1560
|
+
|
1561
|
+
def create_placeholder_tab(self, title, message):
|
1562
|
+
frame = ttk.Frame(self.notebook)
|
1563
|
+
self.notebook.add(frame, text=title)
|
1564
|
+
ttk.Label(frame, text=message, foreground="red").pack(padx=10, pady=10)
|
1565
|
+
|
1566
|
+
def main():
|
1567
|
+
try:
|
1568
|
+
root = tk.Tk()
|
1569
|
+
app = AdvancedPipManager(root)
|
1570
|
+
root.mainloop()
|
1571
|
+
except Exception as e:
|
1572
|
+
print(f"Application stopped due to error: {e}")
|
1573
|
+
|
1574
|
+
if __name__ == "__main__":
|
1575
|
+
main()
|