Setting up agile development with Python/Komodo (part 2)

This is the second part in a series of tutorials to setup up agile development in Python/Komodo.

In the first part, we created a project, set up unit testing, and started our build script. In this part, we're going to start developing a simple wxpython application, and add building an executable via py2exe and a setup program via Inno Setup to our build script.

First, open the project that you created in part 1, and add a file named "main.py." Your project window should look like this now:

Here's the code for main.py:

#coding: UTF8
"""
A very simple GUI program."
""

__version__ = "0.1"

import wx
class Frame(wx.Frame):
    pass

class App(wx.App):
    def OnInit(self):
        self.frame = Frame(parent=None, title='Agile Development')
        self.frame.Show()
        self.SetTopWindow(self.frame)
        return True

if __name__ == '__main__':
    app = App()
    app.MainLoop()
 

See my article on mocking wxpython for unit testing for tips on getting this into a test harness.

Next, we'll create our py2exe setup file for building a stand-alone executable. Add a file "setup.py" to your project, as follows:

#coding: UTF8
"""
Builds an executable for our main script.

Uses py2exe to build a stand-alone executable."""

from distutils.core import setup
import py2exe
import sys
import os
import time
import main

PROG_NAME = "Agile App"
VERSION = main.__version__
SCRIPT_NAME = "main.py"
WX_DIR = os.path.abspath("/Python25/lib/site-packages/wx-2.8-msw-unicode/wx")

MANIFEST_TEMPLATE = "'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
     version="5.0.0.0"
     processorArchitecture="x86"
     name="%(prog)s"
     type="win32"
/>
<description>%(prog)s Program</description>
<dependency>
     <dependentAssembly>
         <assemblyIdentity
             type="win32"
             name="Microsoft.Windows.Common-Controls"
             version="6.0.0.0"
             processorArchitecture="X86"
             publicKeyToken="6595b64144ccf1df"
             language="*"
         />
     </dependentAssembly>
</dependency>
</assembly>
'
"

RT_MANIFEST = 24
"""Resource ID in Windows executables"""

def clean_dir(directory):
    """Remove all prior fields from the directory"""

    print "Cleaning directory", directory
    from os.path import join
    for root, dirs, files in os.walk(directory, topdown=False):
        for name in files:
            os.remove(join(root, name))
        for name in dirs:
            os.rmdir(join(root, name))

def setup_main():
    """Create a dictionary to pass to setup"""

    start_time = time.time()

    os.chdir(os.path.dirname(__file__))

    manifest = MANIFEST_TEMPLATE % dict(prog=PROG_NAME)
    ugly_name = PROG_NAME.replace(" ", "")

    app_info = dict(version=VERSION,
                    company_name = "My Company",
                    copyright = "(C) My Company",
                    author = "My Company",
                    author_email = "software@my.company.com",
                    description="Demonstrates agile development",
                    script="./%s" % SCRIPT_NAME,
                    #icon_resources = [(1, "./res/%s.ico" % ugly_name)],
                    other_resources = [(RT_MANIFEST, 1, manifest)],
                    dest_base= ugly_name,
                    name= "%s Application v %s" % (PROG_NAME,
                                                   VERSION)
       )

    windows_apps = [app_info]
    console_apps = []

    setup_dict = {}
    setup_dict['windows'] = windows_apps
    setup_dict['console'] = console_apps

    excludes = ["pywin", "pywin.debugger", "pywin.debugger.dbgcon",
            "pywin.dialogs", "pywin.dialogs.list", "win32com.server",
            "email"]

    options = dict(optimize=2,
               dist_dir=ugly_name,
               excludes=excludes)

    setup_dict['options'] = {"py2exe":options}

    setup_dict['data_files'] = [
                (".", ["%s/gdiplus.dll" % WX_DIR,
                       "%s/MSVCP71.dll" % WX_DIR]),
           ]

    clean_dir(ugly_name)

    print "Creating exe…"
    setup(
          **setup_dict
        )

    clean_dir('build')

    print
    print "=" * 30
    print "exe created in", time.time() – start_time, "seconds"
    print "=" * 30
    print

if __name__ == '__main__':
    # If run without args, build executables in quiet mode.
    if len(sys.argv) == 1:
        sys.argv.append("py2exe")
        sys.argv.append("-q")
    setup_main()
 

Make sure that WX_DIR is correct for your wxpython install.

Run the script with your "run this script" command, and make sure that a whole mess of files are created in the folder AgileApp, including "AgileApp.exe." Try running it, and make sure you see the Frame window.

Now, in your build.py file, modify your build function as follows, and test it.

def build():
    """Performs the automated build"""

    curr_dir = os.path.dirname(__file__)
    os.chdir(curr_dir)

    # Unit tests
    run_command('nosetests -w "%s"' % curr_dir)

    # Build executable
    run_command('setup.py')

Once that's running correctly, let's make our setup script. Since we're using Inno Setup, add the file "setup.iss" to your project, as follows:

; Source: http://www.fredshack.com/docs/inno.html

#define AppName "AgileApp"
#define Version "0.1"
; CHANGE THIS TO THE ACTUAL SOURCE DIRECTORY
#define SourceDir "C:\Documents and Settings\Ryan Ginstrom\workspace\TestProject\AgileApp"

[Setup]
AppName={#AppName}
AppVerName={#AppName} {#Version}
AppPublisher=My Company, Inc.
AppPublisherURL=http://www.mycompany.com
AppSupportURL=http://www.mycompany.com
AppUpdatesURL=http://www.mycompany.com
DefaultDirName={pf}\{#AppName}
DefaultGroupName={#AppName}
OutputBaseFilename={#AppName}_Setup_{#Version}
OutputDir=Setup

[Tasks]
Name: "desktopicon"; Description: "Create a &desktop icon"; GroupDescription: "Additional icons:"

[Files]
Source: {#SourceDir}\*;     DestDir: {app}\;      Flags: ignoreversion recursesubdirs

[Icons]
Name: "{group}\{#AppName}"; Filename: "{app}\{#AppName}.exe"
Name: "{userdesktop}\{#AppName}"; Filename: "{app}\{#AppName}.exe"; Tasks: desktopicon

[Run]
Filename: "{app}\{#AppName}.exe"; Description: "Launch {#AppName}"; Flags: nowait postinstall skipifsilent

Make sure that you change the SourceDir define to the actual source directory.

Note that there are a bunch of DRY violations here (and to a lesser extent, in the setup.py script): we are repeating the app name, version, etc. Ideally, we'd be generating these parts of the files automatically from our project metadata. I'll include that step in a later tutorial if there's time.

Now, change your build function again, as follows, and test it:

def build():
    """Performs the automated build"""

    curr_dir = os.path.dirname(__file__)
    os.chdir(curr_dir)

    # Unit tests
    run_command('nosetests -w "%s"' % curr_dir)

    # Build executable
    run_command('setup.py')

    # Build installer
    base = "/program files/inno setup 5/iscc.exe"
    inno = os.path.abspath(base)
    run_command(r'"%s"  setup.iss' % inno)
 

Make sure you have a setup file in the Setup directory, and make sure it installs "AgileApp" correctly.

Once it does, give yourself a pat on the back — you're halfway there to setting up your agile project! You're now automatically unit testing and building your project. In the next part, we'll use pywinauto to automatically install and function test our app.

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>