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()