Source code for pip_tkinter.manage_installed_modules_page

# coding=utf-8
from __future__ import absolute_import

import queue
import threading
import asyncio
import logging
import multiprocessing
import tkinter as tk
from tkinter import ttk
from io import StringIO

logger = logging.getLogger(__name__)

[docs]class ManageInstalledPage(ttk.Frame): """ Manage already installed packages. Implements GUI for 1. Updating package 2. Uninstalling package """ def __init__(self, root, controller=None): """ Initiate ManageInstalledPage :param self.parent: parent frame for ManageInstalledPage :param self.controller: parent page for ManageInstalledPage :param self.container: a tkinter frame to enclose contents of page """ ttk.Frame.__init__(self, root) self.parent = root self.controller = controller self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) self.container = ttk.Frame(self) self.container.grid(row=0, column=0, sticky='nsew') self.container.rowconfigure(0, weight=1) self.container.columnconfigure(1, weight=1) self.create_message_bar() self.manage_frames() self.create_side_navbar() self.create_process_log_frame() self.container.tkraise()
[docs] def create_process_log_frame(self): """ Method to create last logging frame to keep the user updated with process. Logs real time output of pip process to tkinter text widget :param self.task_frame: a tkinter frame to enclose contents """ self.task_frame = ttk.Frame(self, relief='ridge', padding=0.5) self.task_frame.grid(row=0, column=0, sticky='nsew') self.task_frame.rowconfigure(0, weight=1) self.task_frame.columnconfigure(0, weight=1) self.process_details = tk.Text( self.task_frame, wrap='word', height=5, padx=5) self.process_details.insert(1.0, 'No process started') self.process_details.configure(state='disabled') self.process_details.grid( row=0, column=0, columnspan=2, sticky='nsew') yscrollbar=ttk.Scrollbar( self.task_frame, orient='vertical', command=self.process_details.yview) yscrollbar.grid(row=0, column=1, sticky='nse', in_=self.task_frame) self.process_details['yscrollcommand']=yscrollbar.set #Create control buttons self.go_back_button = ttk.Button( self.task_frame, text='Back', command=lambda: self.navigate_back()) self.go_back_button.grid( row=1, column=0, sticky='w', padx=5, pady=5) self.go_back_button.config(state='disabled') self.abort_process_button = ttk.Button( self.task_frame, text='Abort', command=lambda: self.abort_process()) self.abort_process_button.grid( row=1, column=1, sticky='e', padx=5, pady=5) self.abort_process_button.config(state='disabled')
[docs] def navigate_back(self): self.debug_bar.config(text='No message') self.container.tkraise()
[docs] def abort_process(self): self.abort_process_button.config(state='disabled') self.frames_dict[self.current_frame].abort_process() self.debug_bar.config(text='Installation aborted') self.go_back_button.config(state='normal')
[docs] def create_side_navbar(self): """ Create side navigation bar for providing user with options for selecting different ways of installation """ # Create a navbar frame in which all navbar buttons will lie self.navbar_frame = ttk.Frame( self.container, borderwidth=3, padding=0.5, relief='ridge') self.navbar_frame.grid( row=0, column=0, sticky='nsw', pady=(1,1), padx=(1,1)) # Configure style for navbar frame # Button text update_archive_text = "Update Installed Packages" uninstall_archive_text = "Uninstall Package" freeze_req_text = "Freeze Requirements" # Button style navbar_button_style = ttk.Style() navbar_button_style.configure( 'navbar.TButton', padding=(6, 25) ) self.button_update = ttk.Button( self.navbar_frame, text=update_archive_text, state='active', style='navbar.TButton', command=lambda : self.show_frame('UpdatePackage') ) self.button_update.grid(row=0, column=0, sticky='nwe') self.button_uninstall = ttk.Button( self.navbar_frame, text=uninstall_archive_text, style='navbar.TButton', command=lambda : self.show_frame('UninstallPackage') ) self.button_uninstall.grid(row=1, column=0, sticky='nwe') self.button_freeze = ttk.Button( self.navbar_frame, text=freeze_req_text, style='navbar.TButton', command=lambda : self.show_frame('FreezeRequirementsPage') ) self.button_freeze.grid(row=2, column=0, sticky='nwe')
[docs] def manage_frames(self): """ Manage multiple frames. Creates dictionary of multiple frames to be used for showing user different GUI frames for different ways of installation. """ frames_tuple = ( UpdatePackage, UninstallPackage, FreezeRequirementsPage,) self.frames_dict = {} for F in frames_tuple: frame_name = F.__name__ new_frame = F(self.container, self) new_frame.grid(row=0, column=1, sticky='nsew') self.frames_dict[frame_name] = new_frame self.show_frame('UpdatePackage')
[docs] def show_frame(self, frame_name): self.debug_bar.config(text='No message') self.current_frame = frame_name frame = self.frames_dict[frame_name] frame.tkraise()
[docs] def show_task_frame(self): self.debug_bar.config(text='No message') self.task_frame.tkraise() self.go_back_button.config(state='disabled') self.abort_process_button.config(state='normal')
[docs] def create_message_bar(self): """ Print debug messages """ self.debug_bar = ttk.Label( self, padding=0.5, relief='ridge') self.debug_bar.grid( row=1, column=0, columnspan=2, sticky='swe', padx=(1,1), pady=(1,1)) self.debug_bar.config(text='No message')
[docs]class UpdatePackage(ttk.Frame): def __init__(self, parent, controller): ttk.Frame.__init__( self, parent, borderwidth=3, padding=0.5, relief='ridge') self.grid(row=0, column=0, sticky='nse', pady=(1,1), padx=(1,1)) self.controller = controller self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) self.create_multitem_treeview() self.create_buttons()
[docs] def create_multitem_treeview(self): """ Create multitem treeview to show search results with headers : 1. Python Module 2. Installed version 3. Available versions """ self.headers = ['Python Module','Installed Version','Latest Version'] from pip_tkinter.utils import MultiItemsList self.multi_items_list = MultiItemsList(self, self.headers) self.multi_items_list.myframe.grid( row=0, column=0, columnspan=3, sticky='nsew') self.multi_items_list.scroll_tree.bind( '<<TreeviewSelect>>', lambda x : self.scroll_tree_select()) self.package_subwindow = tk.LabelFrame( self, text="Package Details", padx=5, pady=5) self.package_subwindow.grid(row=1, column=0, columnspan=3, sticky='nswe') self.package_details = tk.Text( self.package_subwindow, wrap='word', height=5) self.package_details.insert(1.0, 'No module selected') self.package_details.configure(state='disabled') self.package_details.pack(side='left', fill='x', expand='yes') yscrollbar=ttk.Scrollbar( self.package_subwindow, orient='vertical', command=self.package_details.yview) yscrollbar.pack(side='right', fill='y') self.package_details["yscrollcommand"]=yscrollbar.set self.multi_items_list.scroll_tree.bind( "<Double-Button-1>", lambda x: self.show_summary())
[docs] def scroll_tree_select(self): """ If treeview is selected, enable update buttons """ self.navigate_next.config(state='normal')
[docs] def show_summary(self): """ Show the details of the selected package """ curr_item = self.multi_items_list.scroll_tree.focus() item_dict = self.multi_items_list.scroll_tree.item(curr_item) selected_module = 'Module Name : {}'.format(item_dict['values'][0]) from pip_tkinter.utils import pip_show_command status_code, selected_package_details, serror = pip_show_command( selected_module) self.package_details.configure(state='normal') self.package_details.delete(1.0, 'end') self.package_details.insert(1.0, selected_package_details) self.package_details.configure(state='disabled')
[docs] def refresh_installed_packages(self): """ Show search results """ #Disable buttons like : self.navigate_back.config(state='disabled') self.navigate_next.config(state='disabled') self.refresh_button.config(state='disabled') #Update the bottom message bar to inform user that the program #is fetching outdated packages list self.after(0, self.controller.debug_bar.config( text='Fetching outdated packages ...')) #Spawn a new thread for getting list of outdated packages self.after(100, self.update_outdated_packages)
[docs] def update_outdated_packages(self): from pip_tkinter.utils import pip_list_outdated_command self.outdated_list = pip_list_outdated_command() results_tuple = self.outdated_list if len(results_tuple) > 0: self.multi_items_list.populate_rows(results_tuple) self.controller.debug_bar.config(text='Found outdated packages') else: self.controller.debug_bar.config(text='Error in fetching list of\ outdated packages. Please check your internet settings') self.navigate_back.config(state='normal') self.navigate_next.config(state='normal') self.refresh_button.config(state='normal')
[docs] def create_buttons(self): """ Create back and next buttons """ self.navigate_back = ttk.Button( self, text="Back", command=lambda: self.navigate_previous_frame()) self.navigate_back.grid(row=3, column=0, sticky='w') self.refresh_button = ttk.Button( self, text="Refresh", command=lambda: self.refresh_installed_packages()) self.refresh_button.grid(row=3, column=1, sticky='e') self.navigate_next = ttk.Button( self, text="Update", command=lambda: self.execute_pip_commands()) self.navigate_next.grid(row=3, column=2, sticky='e')
[docs] def navigate_previous_frame(self): """ Navigate to previous frame """ self.controller.controller.show_frame('WelcomePage')
[docs] def execute_pip_commands(self): """ Execute pip commands """ self.navigate_back.config(state='disabled') self.navigate_next.config(state='disabled') self.refresh_button.config(state='disabled') self.after(100, self.controller.debug_bar.config( text='Updating package. Please wait ...')) self.after(100, self.update_installation_log)
[docs] def update_installation_log(self): from pip_tkinter.utils import pip_install_from_PyPI try: curr_item = self.multi_items_list.scroll_tree.focus() item_dict = self.multi_items_list.scroll_tree.item(curr_item) selected_module = item_dict['values'][0] self.controller.show_task_frame() self.install_queue = multiprocessing.Queue() self.update_thread = multiprocessing.Process( target=pip_install_from_PyPI, kwargs={ 'package_args':selected_module, 'install_queue':self.install_queue}) self.install_log_started = False self.error_log_started = False self.after(100, self.log_from_install_queue) self.controller.process_details.config(state='normal') self.controller.process_details.delete(1.0,'end') self.controller.process_details.config(state='disabled') self.update_thread.start() self.navigate_back.config(state='normal') self.navigate_next.config(state='normal') self.refresh_button.config(state='normal') except IndexError: self.controller.debug_bar.config(text='Select correct package')
[docs] def log_from_install_queue(self): try: self.install_message = self.install_queue.get(0) if ((self.install_message[1]=='process_started') and (self.install_log_started==False)): self.install_log_started = True self.controller.process_details.config(state='normal') self.controller.process_details.delete(1.0,'end') self.controller.process_details.config(state='disabled') elif (self.install_log_started==True): if self.install_message[0]==3: if self.install_message[1]==0: self.controller.debug_bar.config(text='Done') else: self.controller.debug_bar.config( text='Error in updating package') self.install_log_started = False self.controller.go_back_button.config(state='normal') self.controller.abort_process_button.config(state='disabled') return else: self.controller.process_details.config(state='normal') self.controller.process_details.insert( 'end', self.install_message[1]) self.controller.process_details.config(state='disabled') self.after(20, self.log_from_install_queue) except queue.Empty: self.after(100, self.log_from_install_queue)
[docs] def abort_process(self): """ Stop pip process : Currently not sure to provide option for aborting process in between """ self.navigate_back.config(state='normal') self.navigate_next.config(state='normal') self.refresh_button.config(state='normal') self.update_thread.terminate()
[docs]class UninstallPackage(ttk.Frame): def __init__(self, parent, controller): ttk.Frame.__init__( self, parent, borderwidth=3, padding=0.5, relief='ridge') self.grid(row=0, column=0, sticky='nse', pady=(1,1), padx=(1,1)) self.controller = controller self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) self.create_multitem_treeview() self.create_buttons()
[docs] def create_multitem_treeview(self): """ Create multitem treeview to show search results with headers : 1. Python Module 2. Installed version 3. Available versions """ self.headers = ['Python Module','Installed Version'] from pip_tkinter.utils import MultiItemsList self.multi_items_list = MultiItemsList(self, self.headers) self.multi_items_list.myframe.grid( row=0, column=0, columnspan=3, sticky='nswe') self.multi_items_list.scroll_tree.bind( '<<TreeviewSelect>>', lambda x : self.scroll_tree_select()) #self.refresh_installed_packages() self.package_subwindow = tk.LabelFrame( self, text="Package Details", padx=5, pady=5) self.package_subwindow.grid( row=1, column=0, columnspan=3, sticky='nswe') self.package_details = tk.Text( self.package_subwindow, wrap='word', height=5) self.package_details.insert(1.0, 'No module selected') self.package_details.configure(state='disabled') self.package_details.pack(side='left', fill='x', expand='yes') yscrollbar=ttk.Scrollbar( self.package_subwindow, orient='vertical', command=self.package_details.yview) yscrollbar.pack(side='right', fill='y') self.package_details["yscrollcommand"]=yscrollbar.set self.multi_items_list.scroll_tree.bind( "<Double-Button-1>", lambda x: self.show_summary())
[docs] def scroll_tree_select(self): """ If treeview is selected, enable update buttons """ self.navigate_next.config(state='normal')
[docs] def show_summary(self): """ Show the details of the selected package """ curr_item = self.multi_items_list.scroll_tree.focus() item_dict = self.multi_items_list.scroll_tree.item(curr_item) selected_module = 'Module Name : {}'.format(item_dict['values'][0]) from pip_tkinter.utils import pip_show_command status_code, selected_package_details, serror = pip_show_command( selected_module) self.package_details.configure(state='normal') self.package_details.delete(1.0, 'end') self.package_details.insert(1.0, selected_package_details) self.package_details.configure(state='disabled')
[docs] def refresh_installed_packages(self): """ Show search results """ from pip_tkinter.utils import pip_list_command self.installed_packages_list = pip_list_command() results_tuple = self.installed_packages_list self.multi_items_list.populate_rows(results_tuple) self.controller.debug_bar.config(text='Found installed packages')
[docs] def create_buttons(self): """ Create back and next buttons """ self.navigate_back = ttk.Button( self, text="Back", command=lambda: self.navigate_previous_frame()) self.navigate_back.grid(row=3, column=0, sticky='w') self.refresh_button = ttk.Button( self, text="Refresh", command=lambda: self.refresh_installed_packages()) self.refresh_button.grid(row=3, column=1, sticky='e') self.navigate_next = ttk.Button( self, text="Uninstall", command=lambda: self.execute_pip_commands()) self.navigate_next.grid(row=3, column=2, sticky='e')
[docs] def navigate_previous_frame(self): """ Navigate to previous frame """ self.controller.controller.show_frame('WelcomePage')
[docs] def execute_pip_commands(self): """ Execute pip commands """ self.navigate_back.config(state='disabled') self.navigate_next.config(state='disabled') self.refresh_button.config(state='disabled') self.after(100, self.controller.debug_bar.config( text='Uninstalling package. Please wait ...')) self.after(100, self.update_uninstallation_log)
[docs] def update_uninstallation_log(self): from pip_tkinter.utils import pip_uninstall try: curr_item = self.multi_items_list.scroll_tree.focus() item_dict = self.multi_items_list.scroll_tree.item(curr_item) selected_module = item_dict['values'][0] self.controller.show_task_frame() self.uninstall_queue = multiprocessing.Queue() self.update_thread = multiprocessing.Process( target=pip_uninstall, kwargs={ 'package_args':selected_module, 'uninstall_queue':self.uninstall_queue}) self.controller.process_details.config(state='normal') self.controller.process_details.delete(1.0,'end') self.controller.process_details.config(state='disabled') self.uninstall_log_started = False self.error_log_started = False self.after(100, self.log_from_uninstall_queue) self.update_thread.start() self.navigate_back.config(state='normal') self.navigate_next.config(state='normal') self.refresh_button.config(state='normal') except IndexError: self.controller.debug_bar.config(text='Select correct package')
[docs] def log_from_uninstall_queue(self): try: self.uninstall_message = self.uninstall_queue.get(0) if ((self.uninstall_message[1]=='process_started') and (self.uninstall_log_started==False)): self.uninstall_log_started = True self.controller.process_details.config(state='normal') self.controller.process_details.delete(1.0,'end') self.controller.process_details.config(state='disabled') elif (self.uninstall_log_started==True): if self.uninstall_message[0]==3: if self.uninstall_message[1]==0: self.controller.debug_bar.config(text='Done') else: self.controller.debug_bar.config( text='Error in uninstalling package') self.uninstall_log_started = False self.controller.go_back_button.config(state='normal') self.controller.abort_process_button.config(state='disabled') return else: self.controller.process_details.config(state='normal') self.controller.process_details.insert( 'end', self.uninstall_message[1]) self.controller.process_details.config(state='disabled') self.after(20, self.log_from_uninstall_queue) except queue.Empty: self.after(100, self.log_from_uninstall_queue)
[docs] def abort_process(self): """ Stop pip process : Currently not sure to provide option for aborting process in between """ self.navigate_back.config(state='normal') self.navigate_next.config(state='normal') self.refresh_button.config(state='normal') self.update_thread.terminate()
[docs]class FreezeRequirementsPage(ttk.Frame): """ Page for providing options for 'pip freeze'. User can generate requirement file and save it to desired location """ def __init__(self, parent, controller): ttk.Frame.__init__( self, parent, borderwidth=3, padding=0.5, relief='ridge') self.grid(row=0, column=0, sticky='nse', pady=(1,1), padx=(1,1)) self.controller = controller self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) self.create_multitem_treeview() self.create_buttons()
[docs] def create_multitem_treeview(self): """ Create multitem treeview to show search results with headers : 1. Python Module 2. Installed version """ self.headers = ['Python Module','Installed Version'] from pip_tkinter.utils import MultiItemsList self.multi_items_list = MultiItemsList(self, self.headers) self.multi_items_list.myframe.grid( row=0, column=0, columnspan=4, sticky='nswe') self.multi_items_list.scroll_tree.bind( '<<TreeviewSelect>>', lambda x : self.scroll_tree_select()) self.multi_items_list.scroll_tree.config(selectmode='extended')
[docs] def scroll_tree_select(self): """ Update debug bar with number of packages selected """ self.controller.debug_bar.config( text="No. of packages selected {}".format( str(len(self.multi_items_list.scroll_tree.selection()))))
def refresh_installed_packages(self): """ Show search results """ from pip_tkinter.utils import pip_list_command self.installed_packages_list = pip_list_command() results_tuple = self.installed_packages_list self.multi_items_list.populate_rows(results_tuple) self.controller.debug_bar.config(text='Found installed packages')
[docs] def create_buttons(self): """ Create nav and control buttons """ self.navigate_back = ttk.Button( self, text="Back", command=lambda: self.navigate_previous_frame()) self.navigate_back.grid(row=3, column=0, sticky='w', padx=1, pady=1) self.refresh_button = ttk.Button( self, text="Refresh", command=lambda: self.refresh_installed_packages()) self.refresh_button.grid(row=3, column=1, sticky='e', padx=1, pady=1) self.navigate_next = ttk.Button( self, text="Generate Requirements", command=lambda: self.execute_pip_commands()) self.navigate_next.grid(row=3, column=2, sticky='e', padx=1, pady=1)
[docs] def navigate_previous_frame(self): """ Navigate to previous frame """ self.controller.controller.show_frame('WelcomePage')
[docs] def refresh_installed_packages(self): """ Update list of installed packages """ from pip_tkinter.utils import pip_freeze_command self.installed_packages_list = pip_freeze_command() results_tuple = self.installed_packages_list self.multi_items_list.populate_rows(results_tuple) self.controller.debug_bar.config(text='Found installed packages')
[docs] def execute_pip_commands(self): """ Execute the command for generating requirements file """ requirements_text = '' for item in self.multi_items_list.scroll_tree.selection(): item_dict = self.multi_items_list.scroll_tree.item(item) requirements_text += '{}=={}\n'.format( item_dict['values'][0], item_dict['values'][1]) from tkinter.filedialog import asksaveasfile from os.path import expanduser file_pt = asksaveasfile( mode='w', defaultextension='.txt', initialdir=expanduser('~')) if file_pt is None: return file_pt.write(requirements_text) file_pt.close()