UnknownTimezoneError Exception Raised with Python Application Compiled with Py2Exe
Asked Answered
I

2

15

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()
Inappreciative answered 6/2, 2012 at 10:39 Comment(2)
You also need a current version of setuptools. Specifically pkg_resources. 'import pkg_resources' must not fail.Quietus
Where exactly is pkg_resources used? or just by importing it?Definitive
R
3

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)

Roseroseann answered 6/2, 2012 at 10:40 Comment(0)
S
3

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 )
Shufu answered 27/10, 2014 at 13:18 Comment(5)
This is working for me (pytz 2014.10), but I had to remove all ignores from shutil.copytree().Fringe
Yes! It worked! The weirdest thing though, is that my pytz with py2exe was working. Then I modified a few things in my program, completely unrelated to pytz, and suddenly it stopped working and gave the UnkownTimeZoneErrorGunas
wordsforthewise, exactly! That was my problem, too. And since (at least to me) it's not always obvious what py2exe does and how it collects its things, I even had to hack debug prints into pytz to find out what went wrong during py2exe's collection phase. :-)Shufu
Worked for me. Thank you :)Lynd
I had yet to add the following line: os.environ.setdefault('PYTZ_TZDATADIR', f'{basePath}\\pytz\\zoneinfo') for it still wasn't finding the data.Ungrudging

© 2022 - 2024 — McMap. All rights reserved.