- Finestra non ridimensionabile
- Selezione/Deselezione di tutti i pacchetti
- Mostra pacchetti selezionati
- Aggiorna selezionati con possibilità di annullare
- Barra di progresso + stato numerato
- Evidenziazione dei pacchetti obsoleti in rosso
- Compatibilità multipiattaforma
Codice: Seleziona tutto
import tkinter as tk
from tkinter import ttk, messagebox
import subprocess
import json
import threading
# Usa importlib.metadata invece di pkg_resources
try:
from importlib.metadata import distributions
except ImportError:
from importlib_metadata import distributions # Python < 3.8
# Variabili globali per il controllo del processo
interrompi = False
in_aggiornamento = False
# Ottieni la lista dei pacchetti installati
def get_installed_packages():
return sorted([dist.metadata["Name"] for dist in distributions()])
def get_outdated_packages():
result = subprocess.run(
["pip", "list", "--outdated", "--format=json"],
capture_output=True,
text=True,
check=True
)
return json.loads(result.stdout)
# Mostra i pacchetti selezionati
def mostra_selezionati():
selezionati = [nome for nome, var in check_vars.items() if var.get()]
if selezionati:
print("Hai selezionato:")
for nome in selezionati:
print("-", nome)
else:
print("Nessun pacchetto selezionato.")
# Seleziona o deseleziona tutto
def toggle_selezione():
global tutto_selezionato
tutto_selezionato = not tutto_selezionato
for var in check_vars.values():
var.set(tutto_selezionato)
btn_toggle.config(text="Deseleziona tutto" if tutto_selezionato else "Seleziona tutto")
# Aggiorna i pacchetti selezionati con pip (in thread)
def aggiorna_selezionati():
global interrompi, in_aggiornamento
# Se già in aggiornamento, l'utente vuole annullare
if in_aggiornamento:
interrompi = True
btn_aggiorna.config(text="Aggiorna selezionati", state="disabled")
return
def worker():
nonlocal selezionati
totale = len(selezionati)
progress["value"] = 0
progress["maximum"] = totale
errori = []
for i, pkg in enumerate(selezionati, start=1):
if interrompi:
break
root.after(0, status_label.config, {"text": f"[{i}/{totale}] Aggiornamento di: {pkg}"})
root.after(0, progress.config, {"value": i})
root.update_idletasks()
try:
result = subprocess.run(
["python", "-m", "pip", "install", "--upgrade", pkg],
capture_output=True,
text=True,
check=True
)
print(result.stdout)
except subprocess.CalledProcessError as e:
print(f"Errore aggiornando {pkg}:\n{e.stderr}")
errori.append(pkg)
# Ripristina interfaccia
def fine_aggiornamento():
global interrompi, in_aggiornamento
status_label.config(text="Stato: annullato" if interrompi else "Stato: completato")
btn_aggiorna.config(text="Aggiorna selezionati", state="normal")
btn_toggle.config(state="normal")
btn_mostra.config(state="normal")
for widget in scrollable_frame.winfo_children():
widget.config(state="normal")
in_aggiornamento = False
interrompi = False
if not interrompi:
if errori:
messagebox.showwarning("Completato con errori", f"{len(errori)} pacchetti non aggiornati.")
else:
messagebox.showinfo("Aggiornamento completato", "Tutti i pacchetti aggiornati con successo.")
root.after(0, fine_aggiornamento)
# Step iniziale: disabilita GUI, prepara lista
selezionati = [nome for nome, var in check_vars.items() if var.get()]
if not selezionati:
messagebox.showinfo("Aggiornamento", "Nessun pacchetto selezionato.")
return
risposta = messagebox.askyesno(
"Conferma aggiornamento",
f"Vuoi davvero aggiornare {len(selezionati)} pacchetti?"
)
if not risposta:
return
# Blocca UI e avvia thread
interrompi = False
in_aggiornamento = True
btn_aggiorna.config(text="Annulla aggiornamento")
btn_toggle.config(state="disabled")
btn_mostra.config(state="disabled")
for widget in scrollable_frame.winfo_children():
widget.config(state="disabled")
threading.Thread(target=worker, daemon=True).start()
# ----------------- GUI -----------------
root = tk.Tk()
root.title("Pacchetti Python installati")
root.resizable(False, False) # Finestra fissa
# Frame principale a griglia
main_frame = ttk.Frame(root)
main_frame.pack(fill="both", expand=True)
# Canvas + scrollbar sinistra
canvas = tk.Canvas(main_frame, width=300, height=400)
scrollbar = ttk.Scrollbar(main_frame, orient="vertical", command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.grid(row=0, column=0, sticky="nsew")
scrollbar.grid(row=0, column=1, sticky="ns")
# Pannello laterale con pulsanti
side_panel = ttk.Frame(main_frame, padding=10)
side_panel.grid(row=0, column=2, sticky="ns")
btn_toggle = ttk.Button(side_panel, text="Seleziona tutto", command=toggle_selezione)
btn_toggle.pack(pady=5)
btn_mostra = ttk.Button(side_panel, text="Mostra selezionati", command=mostra_selezionati)
btn_mostra.pack(pady=5)
btn_aggiorna = ttk.Button(side_panel, text="Aggiorna selezionati", command=aggiorna_selezionati)
btn_aggiorna.pack(pady=5)
# Etichetta stato e barra di avanzamento
status_label = ttk.Label(side_panel, text="Stato: inattivo")
status_label.pack(pady=(10, 2))
progress = ttk.Progressbar(side_panel, orient="horizontal", length=150, mode="determinate")
progress.pack()
# Configura layout griglia
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(0, weight=1)
# Ottieni pacchetti obsoleti
outdated_pkgs = get_outdated_packages()
check_vars = {}
for pkg in get_installed_packages():
var = tk.BooleanVar()
if pkg in outdated_pkgs:
# Pacchetto obsoleto → evidenziato in rosso
lbl = tk.Label(scrollable_frame, text=pkg, fg="red", anchor="w")
lbl.pack(anchor="w", padx=(2,0))
chk = ttk.Checkbutton(scrollable_frame, variable=var)
chk.pack(anchor="w", padx=(20,0))
else:
# Pacchetto aggiornato
chk = ttk.Checkbutton(scrollable_frame, text=pkg, variable=var)
chk.pack(anchor="w")
check_vars[pkg] = var
# Stato iniziale del toggle
tutto_selezionato = False
# Avvio GUI
root.mainloop()