Python Forum

Full Version: Show Installed Package detail
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
I have created a ShowInstalledPackages.py program on GitHub (and below).
Works on-line and offline, showing all python packages installed by user, and
detailed information about each.

[Image: InstalledPackages.png?raw=true]
https://github.com/Larz60p/ShowInstalled...ackages.py

Checks if Internet present, if so gets a fresh copy of package info from pip, otherwise loads the last version obtained from a file. Must be run from the internet first time.

Single click on package name will show summary description on bottom frame of GUI.

Double click shows detailed information on the package.

** Note ** The installed version and most recent version are shown at the top of the Detail information. If there is a difference, you should get the updated version.

# Copyright <2016> <Larz60+>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in the
# Software without restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
# Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
import tkinter as tk
import tkinter.ttk as ttk
import pip
import requests
import json
import socket
import getpass


class ShowInstalledPackages:
    def __init__(self, root):
        self.s = ttk.Style()
        self.s.theme_use('classic')
        self.parent = root
        self.userid = getpass.getuser()
        self.package_name = None
        self.pkgs = None
        self.pkg_details = {}
        self.pkglist = None
        self.pkeys = []
        self.pkglist_filename = 'pkglist.json'
        self.PackageData_filename = 'PackageData.json'

        root.title('Installed packages for {}'.format(self.userid))
        self.internet_available = socket.gethostbyname(socket.gethostname()) != '127.0.0.1'

        # GUI prototypes
        self.fmain = tk.Frame
        self.bottom_frame = tk.Frame
        self.blabel = tk.Label
        self.blabeltxt = tk.StringVar()
        self.t1 = ttk.Treeview
        self.textframe = tk.Text
        self.txscroll = tk.Scrollbar

        # Geometry variables
        self.treeheight = 25
        self.mfheight = self.treeheight + 5
        self.bfheight = 5

        self.mfrow = 0
        self.mfcol = 0

        self.trrow = 0
        self.trcol = 0

        self.txrow = 0
        self.txcol = 0

        self.build_gui()

    def build_gui(self):
        self.create_main_frame()
        self.create_bottom_frame()
        self.create_treeframe()
        self.create_textframe()
        self.create_treeframe()
        self.load_treeframe()

    def create_main_frame(self):
        self.fmain = tk.Frame(self.parent, bd=2, relief=tk.RAISED)
        self.fmain.grid(row=self.mfrow, rowspan=self.mfheight, column=self.mfcol,
                        padx=2, pady=2, sticky='nsew')
        self.mfrow += self.mfheight

    def create_bottom_frame(self):
        self.bottom_frame = tk.Frame(self.parent, bd=2, relief=tk.RAISED)
        self.bottom_frame.grid(row=self.mfrow, rowspan=self.bfheight, column=self.mfcol,
                               padx=2, pady=2, sticky='nsew')
        self.mfrow += self.bfheight
        self.blabel = tk.Label(self.bottom_frame, textvariable=self.blabeltxt, bd=2, relief=tk.SUNKEN)
        self.blabel.grid(row=0, rowspan=self.bfheight, column=0, sticky='nsew')

    def create_treeframe(self):
        treestyle = ttk.Style()
        treestyle.configure("Treeview.Heading", foreground='white',
                            background='SteelBlue', height=3)
        self.t1 = ttk.Treeview(self.fmain, height=self.treeheight,
                               padding=(2, 2, 2, 2),
                               columns='Version', style='Treeview',
                               selectmode="extended")
        self.t1.heading('#0', text='Package', anchor=tk.CENTER)
        self.t1.heading('#1', text='Version', anchor=tk.CENTER)
        self.t1.column('#0', stretch=tk.YES, minwidth=20, width=150)
        self.t1.column('#1', stretch=tk.YES, minwidth=10, width=70)

        self.t1.grid(row=0, rowspan=self.treeheight, column=0)
        treescrolly = tk.Scrollbar(self.fmain, orient=tk.VERTICAL,
                                   command=self.t1.yview)
        treescrolly.grid(row=0, rowspan=self.treeheight, column=1, sticky='ns')
        treescrollx = tk.Scrollbar(self.fmain, orient=tk.HORIZONTAL,
                                   command=self.t1.xview)
        treescrollx.grid(row=self.treeheight + 1, column=0, columnspan=2, sticky='ew')
        self.t1.configure(yscroll=treescrolly)
        self.t1.configure(xscroll=treescrollx)
        self.t1.bind('<Double-1>', self.pkg_selected)
        self.t1.bind('<ButtonRelease-1>', self.treeview_summary)

    def create_textframe(self):
        self.textframe = tk.Text(self.fmain, bd=2, bg='#CEF6EC',
                                 relief=tk.RAISED)
        self.txscroll = tk.Scrollbar(self.fmain, orient=tk.VERTICAL,
                                     command=self.textframe.yview)
        self.txscroll.grid(row=1, rowspan=self.treeheight, column=4, sticky='ns')
        self.textframe.configure(yscroll=self.txscroll.set)
        self.textframe.grid(row=0, rowspan=self.treeheight, column=3, padx=2,
                            pady=2, sticky='nsew')

    def load_treeframe(self):
        self.get_pkgs_from_pip()
        self.get_pks_details()
        for key in self.pkeys:
            self.t1.insert('', 'end', text=key, values=self.pkglist[key])

    def treeview_summary(self, event):
        curitem = self.t1.focus()
        x = self.t1.item(curitem)
        self.package_name = x['text']
        sumdata = self.pkg_details[self.package_name]['summary']
        self.blabeltxt.set(sumdata)

    @staticmethod
    def get_package_info(pkgname):
        pypi_info_url = 'https://pypi.python.org/pypi/{}/json'.format(pkgname)
        response = requests.get(pypi_info_url)
        if response.status_code == requests.codes.ok:
            jdata = json.loads(response.text.encode(response.encoding))
            return jdata

    def get_pkgs_from_pip(self):
        if not self.internet_available:
            with open(self.pkglist_filename, 'r') as f:
                self.pkglist = json.load(f)
        else:
            self.pkgs = pip.get_installed_distributions(local_only=True, include_editables=True,
                                                        editables_only=False, user_only=False)
            self.pkglist = dict()
            for item in self.pkgs:
                z = str(item).split()
                self.pkglist[z[0]] = z[1]

        if self.internet_available:
            with open(self.pkglist_filename, 'w') as f:
                json.dump(self.pkglist, f)

    def get_pks_details(self):
        if not self.internet_available:
            with open(self.PackageData_filename, 'r') as f:
                self.pkg_details = json.load(f)
            self.pkeys = list(self.pkglist.keys())
            self.pkeys.sort()
        else:
            with open('ziggy1.txt', 'w') as fp:
                self.pkeys = list(self.pkglist.keys())
                self.pkeys.sort()
                for key in self.pkeys:
                    data = self.get_package_info(key)
                    self.pkg_details[key] = data['info']
                wpkg = str('self.pkg_details: {}\n'.format(self.pkg_details).encode('utf-8'))
                fp.write(wpkg)

        if self.internet_available:
            with open(self.PackageData_filename, 'w') as f:
                json.dump(self.pkg_details, f)

    def pkg_selected(self, event):
        curitem = self.t1.focus()
        x = self.t1.item(curitem)
        self.package_name = x['text']
        pn = self.pkglist[self.package_name]
        data = self.pkg_details[self.package_name]
        dkeys = list(data.keys())
        dkeys.sort()
        self.textframe.delete('1.0', tk.END)
        self.textframe.tag_configure('tag-center', justify='center')
        # Insert Title as first line
        self.textframe.insert('end', 'Package Name: {} -- Installed Version: {}\n'
                                     'Latest Version: {}\n'
                              .format(self.package_name, pn, data['version']), 'tag-center')
        self.display_data(data, dkeys)

    def display_data(self, data, dkeys):
        for key in dkeys:
            if key == 'version' or key == 'description':
                continue
            if isinstance(data[key], list):
                for element in data[key]:
                    displ = '    {} -- {}\n'.format(key, element)
                    self.textframe.insert(tk.END, displ)
                self.textframe.insert(tk.END, '\n')
                # print(f'Type List: {key} -- {data[key]}\n\n')
            elif isinstance(data[key], dict):
                for element in data[key].items():
                    displ = '    {} -- {}\n'.format(key, element)
                    self.textframe.insert(tk.END, displ)
                self.textframe.insert(tk.END, '\n')
                # print(f'Type Dict: {key} -- {data[key]}\n\n')
            else:
                displ = '{} -- {}\n\n'.format(key, data[key])
                self.textframe.insert(tk.END, displ)
        displ = '\n=========================================================\n' \
                'Description -- {}\n\n'.format(data['description'])
        self.textframe.insert(tk.END, displ)

    @staticmethod
    def show_widget_values(target_widget):
        wkeys = list(target_widget.keys())
        wkeys.sort()
        for key in wkeys:
            print('key: {} value: {}'.format(key, target_widget[key]))


def main():
    root = tk.Tk()
    ShowInstalledPackages(root)
    root.mainloop()

if __name__ == '__main__':
    main()
Looks like a very useful and professional application.  If I might ask, is it cross platform and which Python version(s) does one need?
I should have stated that.

I have only tested it on windows 7 pro 64 bit. Also, I used python 3.6.

I don't believe there are any system specific commands,
but can't be sure.

Would appreciate someone trying it on Linux and OS-X and let me know where problems
(if any) are found.

You may have to change the theme for it to look ok on another os.
To do so, first run the following snippet to see what's available:
import tkinter.ttk as ttk

s = ttk.Style()
print(s.theme_names())
On windows 7 this returns:
Output:
('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
change the line (in __init__):
self.s.theme_use('...')
Good job this @Larz60+.
I have tested it and it work.

You should look more into setup/packaging then it will be easier for end user to use.
Could eg be pip install ShowInstalledPackages.
Then your own program will show up in your in ShowInstalledPackages Think

Here is my run with cmder.
E:\1
λ git clone https://github.com/Larz60p/ShowInstalledPackages.git
Cloning into 'ShowInstalledPackages'...
remote: Counting objects: 16, done.
remote: Compressing objects: 100% (16/16), done.
remote: Total 16 (delta 7), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (16/16), done.
Checking connectivity... done.

E:\1
λ cd ShowInstalledPackages

E:\1\ShowInstalledPackages (master)
λ ls
InstalledPackages.png  LICENSE  README.md  ShowInstalledPackages.py

# Run ShowInstalledPackages
E:\1\ShowInstalledPackages (master)
λ python ShowInstalledPackages.py
Quote:You should look more into setup/packaging then it will be easier for end user to use.

I have never gone through this process before. I guess it's time to learn how.
I have never used cmder either.
This is my task for tomorrow before I move on to my next project, which I haven't
identified yet.

I just noticed that there ate two calls to create_treeframe.
I'll also make that change on github (and here tomorrow)

One more error, 'ziggy.tst' was a text file name, and should be something eles.
You can at look this.
Quote:I have never used cmder either
I could have done the same in cmd,
it's just a much better shell with color coding and more functionallty.
So booth ls(give color to folders) and dir work.
Look like this.
Downloaded and installed (into path) cmder and like it.
And follow also this.
When run command .\cmder.exe /REGISTER ALL
Then you can right click in file explorer,and cmder straight into that folder.
While trying to create a package for this module, got the following error
(using cmder, and have .pypirc file setup
Error:
λ python setup.py register -r pypitest running register running check Registering ShowInstalledPackages to https://testpypi.python.org/pypi Server response (401): Incomplete registration; check your email
I never got an email
Have made pretty good progress in getting this thing published on PyPi.
I am attempting to have a standalone executable in the package.
I am able to create the .exe, but when I run it, i get the following error,
which has something to do with expanding eggs.

Error:
ShowInstalledPackages.exe Traceback (most recent call last):   File "ShowInstalledPackages.py", line 22, in <module>   File "c:\Python34\lib\site-packages\pip\__init__.py", line 26, in <module>     from pip.utils import get_installed_distributions, get_prog   File "c:\Python34\lib\site-packages\pip\utils\__init__.py", line 23, in <module>     from pip.locations import (   File "c:\Python34\lib\site-packages\pip\locations.py", line 6, in <module>     import site ImportError: No module named 'site'
Perhaps tomorrow.

Next project: either something to do with midi, or a big project -- yet another PDF library.
Pages: 1 2