Packaging with py2exe and cxFreeze

In this section we will see how to package applications that use pubsub, with py2exe and cx_Freeze packaging tools.

Introduction

Packaging tools such as py2exe and cx_Freeze determine the dependencies that have to be included in a package by recursively finding the modules from the import statements used. Recursive finding of modules from the import statements uses straight forward approach i.e., if the python code dynamically imports certain modules by modifying the sys.path at runtime or if the code uses __import__ statements, those modules are likely to be left out by the packaging tool. This can be a problem for some packaged applications.

Packaging modules that uses pubsub

Pubsub supports two different messaging protocols namely args1 and kwargs; choosing and switching between these protocols is done by modifying the module path dynamically. This can result in import error like this at runtime:

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

In the following sections we show an example script that uses pubsub and discuss the setup script to package it using py2exe or cx_Freeze packaging tools.

Example

Consider a sample application which has a single file named say testpubsub.py

from pubsub import pub

def listener1(msg):
    print "The listener received the message : %s" % (msg, )

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

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

if __name__ == "__main__":
    sender()

To package this with py2exe and cx_Freeze you write a conventional setup.py module, but with extra options that the packaging tool uses to create the final distribution.

Setup file using py2exe

The setup.py for this would look something like this

"""
File based on a contribution from Josh Immanuel. Use via 

python setup-py2exe.py py2exe

which will create a dist folder containing the .exe, the python DLL, and a 
few other DLL deemed by py2exe to be critical to the application execution. 

The contents of the dist folder should then be packaged using a tool such 
as NSIS or Inno Setup. The py2exe page has an example for NSIS. 
"""

from distutils.core import setup

import py2exe

setup (
    name='TestPubSub',
    description="Script to test pubsub for packaging",
    version="0.1",
    
    console=[{'script': 'testpubsub.py'}],
    options={ 'py2exe': {
                'packages': 'encodings, pubsub',
                'includes': None}
            },
    )

The line 'packages': 'encodings, pubsub' explicitly tells py2exe to include pubsub as a package so that the entire pubsub folder (from the installation location) including its sub packages are included for packaging. As the package has the entire list of python modules under pubsub, runtime protocol selection is now possible in the generated exe file.

To build, run:

python setup.py py2exe

which will produce a dist folder containing testpubsub.exe and other DLLs and files required to run the application. Interestingly, py2exe command complains about modules that appear to be missing:

The following modules appear to be missing [‘callables’, ‘core’, ‘core.notificationmgr’, … , ‘topicu’, ‘validatedefnargs’]

however, the application runs fine.

Setup file using cx_Freeze

The setup.py for this would look something like this

from cx_Freeze import setup, Executable as cxExecutable
import platform

if platform.system() == 'Windows':
    # base must be set on Windows to either console or gui app
    # testpubsub is currently a console application
    # base = 'Win32GUI'
    base = 'Console'
else:
    base = None

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

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

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

The packages option slightly differs for cx_Freeze: we have to explicitly include the sub packages pubsub.core.kwargs and pubsub.core.arg1 so that all the modules under them get included for packaging.

To build, run:

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

All these modules are under pubsub.core.kwargs and pubsub.core.arg1. In cx_Freeze dependent modules are gathered from the import statements recursively, during this it reports that it can’t find the above modules. But as we have included them directly in the packages option these modules are included for packaging.