How to create an application with auto-update using Python and Esky

This is the 2nd part of  Distributing Python Apps for Windows Desktops series. The 1st part is here: How to create a Python .exe with MSI Installer and CX_freeze

Every time a program has to be updated is a burden. Remember Java! It feels so uncomfortable, even if you’re an IT guy. You don’t like it, nor does your users. So be nice to them and make an auto-update on your program so they don’t have to download new versions.

To show how to create an application with auto-update using Python and Esky I’ll use the boneca app from part 1. The program was written and compiled but it doesn’t have an auto-update yet. You just generated an installer and the clients using that version will never have updates again. So, now we’re creating a new version using Esky:

pip install esky

 

Let’s modify the boneca.py script to import esky and find for updates on the internet when they’re available.

# right after import win32con
import esky

if hasattr(sys,"frozen"):
    app = esky.Esky(sys.executable,"http://teenspirit.com.br/exemplo_boneca/")
    app.auto_update()

When the program initializes it will look up the given URL for some update to download. It does that based on version number. You just have to create a folder and enable it on Apache. I’m using one of my sites: http://teenspirit.com.br/exemplo_boneca/.

Now, instead of using the boneca.jpg I’ll use this image (chuck.jpg):

chuck


#replace boneca.jpg to chuck.jpg
os.startfile(os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),"chuck.jpg"))

Now, let’s update setup.py to use esky:

#setup.py
import esky.bdist_esky
from esky.bdist_esky import Executable as Executable_Esky
from cx_Freeze import setup, Executable

include_files = ['boneca.jpg','chuck.jpg']

setup(
    name = 'boneca',
    version = '1.0.1',
    options = {
        'build_exe': {
            'packages': ['os','sys','ctypes','win32con'],
            'excludes': ['tkinter','tcl','ttk'],
            'include_files': include_files,
            'include_msvcr': True,
        },
        'bdist_esky': {
            'freezer_module': 'cx_freeze',
        }
    },
    data_files = include_files,
    scripts = [
        Executable_Esky(
            "boneca.py",
            gui_only = True,
            #icon = XPTO  # Use an icon if you want.
            ),
    ],
    executables = [Executable('boneca.py',base='Win32GUI')]
    )

As you can see, I’ve changed the version number to 1.0.1 and from this version, our program will have an auto-update. This file is different from the previous one, so I’ll try to explain everything that is happening here.

1. Importing bdist_esky and an Executable Esky.

import esky.bdist_esky
from esky.bdist_esky import Executable as Executable_Esky

2. Defining options for the new argument bdist_esky:

        'bdist_esky': {
            'freezer_module': 'cx_freeze',
        }

3. Adding  data_files cause Esky uses it to include files on the app’s folder.

      data_files = include_files,

4. Adding scripts so Esky will now which files will be the executables.

    scripts = [
        Executable_Esky(
            "boneca.py",
            gui_only = True,
            #icon = XPTO  # Use an icon if you want,
            ),
    ],

Run the setup.py with bdist_esky version to generate the version.

python setup.py bdist_esky

On your dist folder:

boneca_esky
Inside the zip file you will see this:

boneca-esky-conteudo

So what happened? Esky created a program boneca.exe that will be responsible for the update of your app, including itself. When it’s open it’ll look for new Zip Files with New versions on the URL that I’ve specified. If it’s a new version it will download and change the folder content cleaning the last version.

Esky handles issues such as internet connection problems, energy failure or any other problems while downloading the new version.

So from now on, our app has auto-update which is So Cool, isn’t it??? But unfortunately this version doesn’t have MSVCR support, so in the next and last part of this series, I’ll show how to create your own installer with Inno Setup.

To show how the update works I’ll create one more version (1.0.2) and I’ll change the image again from chuck.jpg to seu-boneco.jpg (it means Mr doll in Portuguese):

os.startfile(os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),"seu-boneco.jpg"))

Don’t forget to add seu-boneco.jpg in the include_files of setup.py:

include_files = ['boneca.jpg','chuck.jpg', 'seu-boneco.jpg']

Now, let’s generate the new Esky file:

python setup.py bdist_esky

New file on our dist folder boneca-1.0.2.win32.zip. We just have to put this on the URL that we provided and next time someone uses the boneca-1.0.1 it will be auto-updated.

If you want to test this app, download boneca-1.0.1.win32.zip, unzip the file and open boneca.exe

When you press Print Screen it will show the Chuck Image, but, at this point, it will be looking for updates and the next time you open the program it will show “Seu Boneco” image.

seu-boneco

Code on Github.

Wait for the 3rd Part!

 

Originally published in Portuguese!

How to create a Python .exe with MSI Installer and Cx_freeze

This is the first part of Distributing Python Apps for Windows Desktops. This is the most basic part and this matter was discussed in a lot of websites, but my idea here is to present how I’ve created the sample program and show how to generate a simple MSI installer with the necessary DLLs to run on Windows.
Every Python executable needs C++ Runtime DLLs to run on windows. You must have heard of it as Microsoft Visual C++ Redistributable or you can find it as MSVCR. The version you will need depends on which Python version you are using.

So, what we’re going to do:

  1. Our program in python files
  2. Write a setup file for the executable
  3. Generate the MSI installer

Alright, I’m creating a stupid script that I’ve used to troll one of my friends. This will replace the function of Print Screen keys and every time the key is press a picture of a Doll appears. I called it Boneca (That’s Portuguese for doll).

baby-looking-like-his-doll


#boneca.py (it's portuguese for doll)
import os
import sys
import ctypes
from ctypes import wintypes
import win32con

byref = ctypes.byref
user32 = ctypes.windll.user32

HOTKEYS = {
    1 : (win32con.VK_SNAPSHOT, 0), #  "PRINT SCREEN"
    2 : (win32con.VK_F4, win32con.MOD_WIN)
}

def handle_print_screen ():
    os.startfile(os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),"boneca.jpg"))

def handle_win_f4 ():
    user32.PostQuitMessage (0)

HOTKEY_ACTIONS = {
    1 : handle_print_screen,
    2 : handle_win_f4
}


# Registering the keys without the print
for id, (vk, modifiers) in HOTKEYS.items ():
    #print "Registering id", id, "for key", vk
    pass
    if not user32.RegisterHotKey (None, id, modifiers, vk):
        #print "Unable to register id", id
        pass


# Calling the functions and removing from the register when quitting.
try:
    msg = wintypes.MSG ()
    while user32.GetMessageA (byref (msg), None, 0, 0) != 0:
        if msg.message == win32con.WM_HOTKEY:
            action_to_take = HOTKEY_ACTIONS.get (msg.wParam)
            if action_to_take:
                action_to_take ()

        user32.TranslateMessage (byref (msg))
        user32.DispatchMessageA (byref (msg))

finally:
    for id in HOTKEYS.keys ():
        user32.UnregisterHotKey (None, id)

As a reference to this code, I’ve used Tim Golden’s post[1].

Basically, this code creates two shortcuts on Windows, one for Print Screen that when pressed it calls the handle_print_screen function which loads the boneca.jpg file. The other shortcut calls handle_win_f4 to quit the program. It doesn’t have a GUI so it makes sense.

So far, so good. It’s a very simple script but now we have to freeze our code which means we will compile the script and generate an executable containing the Python interpreter, the modules, and files, everything in the same place. To do that we’ll need a setup file and chose one tool to freeze our apps such as py2exe, py2app, cx_freeze or Pyinstaller. In this case, we’ll use the cx_freeze.


#setup.py
from cx_Freeze import setup, Executable

setup(
    name = "boneca",
    version = "1.0.0",
    options = {"build_exe": {
        'packages': ["os","sys","ctypes","win32con"],
        'include_files': ['boneca.jpg'],
        'include_msvcr': True,
    }},
    executables = [Executable("boneca.py",base="Win32GUI")]
    )

This is very straightforward and documented as disutils. But look, we’re using include_msvcr that will add the Microsoft Visual C++ Redistributable DLLs into your executable. It will copy the existing DLLs of your OS (if you’re using windows). That’s the only way your program will run on another Windows because it needs those DLLs. Also, you can download MSVCR installer and incorporate on your own installer using Inno Setup, for instance. We’ll do this in the 3rd post.

Now, we’ll generate an MSI using the command line:

python setup.py bdist_msi

Fine, now we have a dist folder with a boneca-1.0.0-win32.msi file inside or boneca-1.0.0-amd64.msi (for x64 OS) and now you can install and use the program.

Scripts on my GitHub: https://github.com/ffreitasalves/boneca

It was originally published in Portuguese.

[1]: http://timgolden.me.uk/python/win32_how_do_i/catch_system_wide_hotkeys.html