Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UnknownTimezoneError Exception Raised with Python Application Compiled with Py2Exe

I'm having a problem distributing an application that utilizes pytz. I'm using Py2Exe to create an executable from my Python source.

For a simple example of the problem I'm having, I have: pytz_test.py:

import pytz

tz_au = pytz.timezone("Australia/Sydney")
print tz_au

and in setup.py:

from distutils.core import setup
import py2exe

setup(console=['pytz_test.py'], options={"py2exe" : { 'packages': ['pytz'], } })

I then run setup.py:

python setup.py py2exe

Which compiles the executable. Running the created pytz_test.exe I get:

Traceback (most recent call last):
  File "pytz_test.py", line 3, in <module>
    tz_au = pytz.timezone("Australia/Sydney")
  File "pytz\__init__.pyc", line 185, in timezone
pytz.exceptions.UnknownTimeZoneError: 'Australia/Sydney'

I assume it is because the timezone information isn't getting bundled with the executable, but I'm not sure how to make it happen.

EDIT: A simple solution would be to add the zoneinfo directory, from the pytz module in the python site-packages directory, to the library.zip.

To do this automatically, I followed the solution in that project Google Transit Data Feed used, from: http://code.google.com/p/googletransitdatafeed/source/browse/trunk/python/setup.py

My modified setup.py now looks like:

from distutils.core import setup
import glob
import py2exe

options = {
    "py2exe" : { 
        "compressed": 1, 
        "optimize": 2,
        'packages': ['pytz'], 
     } 
}

setup(console=['pytz_test.py'], options=options)

import pytz
import os 
import zipfile
zipfile_path = os.path.join("dist/" 'library.zip')
z = zipfile.ZipFile(zipfile_path, 'a')
zoneinfo_dir = os.path.join(os.path.dirname(pytz.__file__), 'zoneinfo')
disk_basedir = os.path.dirname(os.path.dirname(pytz.__file__))
for absdir, directories, filenames in os.walk(zoneinfo_dir):
    assert absdir.startswith(disk_basedir), (absdir, disk_basedir)
    zip_dir = absdir[len(disk_basedir):]
    for f in filenames:
      z.write(os.path.join(absdir, f), os.path.join(zip_dir, f))

z.close()
like image 584
Lance Avatar asked Feb 06 '12 10:02

Lance


2 Answers

A simple solution would be to add the zoneinfo directory, from the pytz module in the python site-packages directory, to the library.zip.

To do this automatically, I followed the solution the project Google Transit Data Feed used, from: http://code.google.com/p/googletransitdatafeed/source/browse/trunk/python/setup.py

My modified setup.py now looks like:

from distutils.core import setup
import glob
import py2exe

options = {
    "py2exe" : { 
        "compressed": 1, 
        "optimize": 2,
        'packages': ['pytz'], 
     } 
}

setup(console=['pytz_test.py'], options=options)

import pytz
import os 
import zipfile
zipfile_path = os.path.join("dist/" 'library.zip')
z = zipfile.ZipFile(zipfile_path, 'a')
zoneinfo_dir = os.path.join(os.path.dirname(pytz.__file__), 'zoneinfo')
disk_basedir = os.path.dirname(os.path.dirname(pytz.__file__))
for absdir, directories, filenames in os.walk(zoneinfo_dir):
    assert absdir.startswith(disk_basedir), (absdir, disk_basedir)
    zip_dir = absdir[len(disk_basedir):]
    for f in filenames:
      z.write(os.path.join(absdir, f), os.path.join(zip_dir, f))

z.close()

(Answered by asker)

like image 179
Jason S Avatar answered Oct 11 '22 02:10

Jason S


Zipping the zoneinfo manually (as described by Jason S) helped indeed for building the package on one of my computers. However, when I built the package on another computer - the error was back! Finding the reason took me a while - so I'd better share.

The proposed solution doesn't work with new pytz-versions (at least with 2014.7)! Digging why this is revealed that pytz changed the format of the zoneinfo files from pyc to some binary format. To me it looks like, with this change they "broke" the option to pack pytz into a zip, since the builtin zipimport mechanism of python doesn't work for loading binary files. Actually, this problem should be fixed by pytz but for now I found another solution:

  • Just copy the whole pytz directory directly into your dist directory
  • within your program, add the path of your main-exectutable to python's search path

Practically, this means within your setup.py replace the pytz-zipping with

import pytz, os, shutil
srcDir = os.path.dirname( pytz.__file__ )
dstDir = os.path.join( 'dist', 'pytz' )
shutil.copytree( srcDir, dstDir, ignore = shutil.ignore_patterns('*.py') )

and move pytz from the "packages"-option to "excludes":

options = {
    "py2exe" : { 
        "compressed": 1, 
        "optimize": 2,
        "packages": [],
        "excludes": ['pytz']
     } 
}

At the main entry of your program (to make sure it is executed before importing pytz), you would have to add something like:

import os, sys
basePath = os.path.dirname( os.path.abspath( sys.argv[0] ) )
sys.path.insert( 0, basePath )
like image 21
Zappotek Avatar answered Oct 11 '22 02:10

Zappotek