Posts Tagged ‘python’

Extend py2exe to copy files to the zipfile where pkg_resources can load them

Nov 3, 2008

In creating a Windows PDF generation app for a client of mine, I ran into a limitation of py2exe which prevents resource loading with pkg_resources. The module attempts to load resources from py2exe’s library.zip. As the zipfile is reserved for compiled bytecode, there’s no option to copy files into it. You can copy files using py2exe’s data_files, but not into the zipfile, and package_data isn’t honoured. You can, however, extend the py2exe class, and that’s what I did.

The best entry point I could find was copy_extensions(). You can override that method to copy files into the build directory. You’ll want to create the necessary directories if they don’t already exist and then register the copied files for copying to dist. Here’s an example with images in a foo module’s media directory:

import os
import glob
from py2exe.build_exe import py2exe as build_exe

class MediaCollector(build_exe):
    def copy_extensions(self, extensions):
        super(MediaCollector, self).copy_extensions(extensions)

        # Create the media subdir where the
        # Python files are collected.
        media = os.path.join('foo', 'media')
        full = os.path.join(self.collect_dir, media)
        if not os.path.exists(full):
            self.mkpath(full)

        # Copy the media files to the collection dir.
        # Also add the copied file to the list of compiled
        # files so it will be included in zipfile.
        for f in glob.glob('foo/media/*'):
            name = os.path.basename(f)
            self.copy_file(f, os.path.join(full, name))
            self.compiled_files.append(os.path.join(media, name))

Strictly speaking, resources are not compiled files but treating them as such was the only way I could find to force them to be included in the zipfile. To use this new collector, just specify it in your py2exe options:

py2exe_options = {
    'cmdclass': {'py2exe': MediaCollector},
    # [...] Other py2exe options here.
}

setup(
    # [...] Other setup options here.
    **py2exe_options
)