Tuesday 21 February 2012

Creating standalone windows application with python

Here in this post we will see how to convert a python application into a standalone windows executable. So that, windows users can run them without installing windows binaries of python and its related libraries separately.

In this we will write a small python code which uses the pubsub and pygtk libraries. And we shall see, how to write the setup script for it using py2exe and cx_Freeze in order to produce a standalone windows application.

Introduction

The job of a packaging/distribution tool is to build a list of dependent modules that are needed for the python application and include those modules for packaging. The list of dependent modules are specified via packaging options provided in the conventional setup.py file and/or by recursively finding the modules from the import statements used in the code.

There are certain limitations when a packaging tool tries to find the dependent modules from the import statements. If the python code imports modules using the  __import__ statement or if the python code modifies the sys.path at runtime in order to import certain modules, then those modules will not be found successfully by the packaging tool.

Python pubsub package

The pubsub module supports two different messaging protocols namely args1 and kwargs. Choosing and switching between these protocols are done by modifying the module path dynamically. This can result in import error like this during runtime.

      from listenerimpl import Listener, ListenerValidator
      ImportError: No module named listenerimpl

This is the main reason I choose pubsub for my example. In the following sections we shall see how to package them successfully for windows.

Sample code

Lets consider a sample application consisting of a single file named say testpubsub.py with the following code

from pubsub import pub
import gtk

def listener1(msg):
    ms = gtk.MessageDialog(None, 0, gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE,
                           "The listener received the message : %s" % (msg, ))
    ms.run()
    ms.destroy()

pub.subscribe(listener1, 'test.pubsub')

def sender():
    pub.sendMessage('test.pubsub', msg="Hola! this is a test message")

if __name__ == "__main__":
    sender()

In the above application, the listener displays the message received from the sender using gtk's message dialog popup. Now, we shall see, how to write the conventional setup.py file for it, using py2exe and cx_Freeze.

Setup file using py2exe

The setup.py using py2exe would look something like this

from distutils.core import setup

try:
    import py2exe
except:
    pass

setup (
    name='TestPubSub',
    description="Script to test pubsub for packaging",
    version="0.1",
    windows=[{'script': 'testpubsub.py'}],
    platforms=["any"],
    options={ 'py2exe': {
            'packages': 'encodings, pubsub',
            'includes': 'cairo, pango, pangocairo, atk, gobject, gio'}
              },
    )

Here we can see the packages option specified for py2exe, to include the encodings and pubsub packages explicitly. This will include the entire pubsub (and encodings) modules from the installation location for packaging.

As the final package has the entire pubsub module, the windows executable will have no trouble in finding the dynamic protocol libraries during runtime.

To generate exe file, run the following in the build machine (i.e. a windows machine with python and necessary library dependencies installed)

   $ python setup.py py2exe

this will produce a standalone windows executable file named testpubsub.exe

Setup file using cx_Freeze

The setup.py using cx_Freeze would look something like this

from cx_Freeze import setup, Executable
import platform

if platform.system() == 'Windows':
    bse = 'Win32GUI'
else:
    bse = None

opts = { 'compressed' : True,
         'create_shared_zip' : False,
         'packages' : ['pubsub.core.kwargs', 'pubsub.core.arg1'],
         }

WIN_Target = Executable(
    script='testpubsub.py',
    base=bse,
    targetName='testpubsub.exe',
    compress=True,
    appendScriptToLibrary=False,
    appendScriptToExe=True
    )

setup(
    name='TestPubSub',
    description="Script to test pubsub for packaging",
    version='0.1',
    options={'build_exe' : opts},
    executables=[WIN_Target]
    )

The packages option slightly differs for cx_Freeze. Here we have to explicitly include the sub packages of pubsub library namely, pubsub.core.kwargs and pubsub.core.arg1 so that, all the modules under them gets included for packaging.

Run the following in the build machine to generate the exe file

   $ python setup.py build

We can safely ignore the missing modules warning in the build log

   Missing modules:
   ? core.publisher imported from pubsub.pub
   ? listenerimpl imported from pubsub.core.listener
   ? publishermixin imported from pubsub.core.topicobj
   ? topicargspecimpl imported from pubsub.core.topicargspec
   ? topicmgrimpl imported from pubsub.core.topicmgr

The above listed missing modules are under pubsub.core.kwargs and pubsub.core.arg1. In cx_Freeze, the list of dependent modules are gathered from the import statements used in the code. While constructing this list, cx_Freeze reports missing modules, when a particular module has been imported in the code but not found in sys.path. But, as we have included those modules directly via the packages option, these modules will get safely included in the package. So, there won't be any runtime error.

Voila! We have created a standalone windows application from a python script. :)

No comments:

Post a Comment