Recipe pytz zip file

remdragon edited this page Jul 22, 2016 · 2 revisions

Scenario: Building an one-folder executable when using the pytz module results in pytz subfolder in your app with over 500 tiny files.

Solution: This set of files can be used to package up all of pytz's executables and timezone files into a zip file without requiring any gross hacks in your source code.

First, here is a sample spec file showing how to use this. Notice the reference to pytz_zip between Analysis() and PYZ()

# -*- mode: python -*-

block_cipher = None

a = Analysis(['pytz_test.py'],
             pathex=['C:\\code\\pytz_test'],
             binaries=None,
             datas=None,
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)

from pytz_zip import pytz_zip
pytz_zip ( a )

pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          exclude_binaries=True,
          name='pytz_test',
          debug=False,
          strip=False,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               name='pytz_test')

Here is pytz_zip.py:

def pytz_zip ( a ):
    import os, pytz, zipfile, glob

    # hack a.scripts to include our runtime hook
    pytz_zip_hook = os.path.join ( os.path.dirname(__file__), 'pytz_zip_hook.py' )
    a.scripts.insert ( 0, ( 'pytz_zip_hook', pytz_zip_hook, 'PYSOURCE' ) )

    # remove all the timezone files from being included raw
    a.datas = [ (x,y,z) for x,y,z in a.datas if x[:5].rstrip('/\\') != 'pytz' ]

    # remove all pytz source files from a.pure because we want them loaded from our zip
    a.pure = [ (x,y,z) for x,y,z in a.pure if x[:5].rstrip('/\\') != 'pytz' ]

    # build our zip file...
    build_folder = os.path.dirname(a.tocfilename)
    zip_path = os.path.join ( build_folder, 'pytz.zip' )
    with zipfile.ZipFile(zip_path,'w') as zip:
        pytz_path = pytz.__path__[0]
        for root, dirs, files in os.walk(pytz_path):
            for file in files:
                file_path = os.path.join(root,file)
                arc_path = 'pytz/'+file_path[len(pytz_path):].lstrip('/\\')
                zip.write ( file_path, arc_path )

    # add our zip file to the compilation
    a.zipfiles.append ( ( 'pytz.zip', zip_path, '' ) )

and here is pytz_zip_hook.py, which needs to be in the same place as pytz_zip.py:

import os, sys

# get the path to the application, because it may not be the current directory
if getattr(sys, 'frozen', False):
    application_path = os.path.dirname(sys.executable)
elif __file__:
    application_path = os.path.dirname(__file__)

# tell python to use pytz.zip as a source for loading modules
pytz_zip_path = os.path.join(application_path, 'pytz.zip')
sys.path.insert ( 0, pytz_zip_path )