Using Obspy modules with py2exe

Obspy is a really cool package for seismological observatories. In fact, it’s a super set of packages. They are distributed using eggs and have a nice way of declaring namespaces and entry points.

The disadvantage, in my case, is that the namespace- approach is quite not compatible with py2exe (will maybe/surely change one day)…

 

So, to create a super simple test case to try different approaches suggested in the obspy-users mailing list, I went for this :

import sys, os
file = open('eggs.pth','r')
for path in file.readlines():
    sys.path.append(os.path.join(os.getcwd(),path.replace('\n','')))
sys.path.append(os.getcwd())

from obspy.core import read

st = read(r'test.gse',verify_chksum=False)

for trace in st.traces:
    print trace.stats

and called it ‘gseinfo.py’. We will need to create a eggs.pth file inside the directory to make sure the script does find the eggs.

 

The idea here is to collect the required eggs names from the osbpy.gse2 module, build a complete list of files that will be copied by py2exe as data_files.

Py2exe data_files is  a list of tuples formatted as (relative_destination_folder, array of source files). To do it easily, I append every file to the data_files array using a walk method :

def walk(path):
    names = os.listdir(path)
    for name in names:
        fname = os.path.join(path,name)

        if os.path.isdir(fname):
            walk(fname)
        else:
            destination = os.path.join(path.replace('c:\python27\lib\site-packages\\',''))
            source = os.path.join(path, name)
            global data_files
            print source, "->>", destination
            data_files.append((destination,[source]))

Note the replace part, needed to create the relative destination path.

The required modules are obtained this way :

eggs = pkg_resources.require("obspy.gse2")
eggpacks = set()
eggspth = open("dist/eggs.pth", "w")

for egg in eggs:
    print "EGG:", egg.egg_name()
    try:
        eggspth.write(os.path.basename(egg.location))
        eggspth.write("\n")
        eggpacks.update(egg.get_metadata_lines("top_level.txt"))

        print os.path.basename(egg.location), egg.location
        if egg.egg_name().count('setuptools') != 0 : 
            walk(egg.location+'-info')
            egg.location = egg.location.replace('-0.6c11-py2.7.egg','')

        walk(egg.location)

    except:
        pass

eggspth.close()

In this part, it is important to note that we create a eggs.pth file in the output /dist directory. Then we process the eggs one by one, calling the walk method. There is a special case we need to take care of, indeed, the setuptools egg does not install like a regular egg inside a single folder, but has a setuptools/ folder in addition to a setuptools-06c11-py2.7.egg/ folder. Your installation may vary in this.

One SUPER important note is that, although I’m using obspy.* and setuptools eggs, I don’t want to use numpy eggs (crappy links to scipy eggs that are far less easy to install on windows than the scipy.exe from sf.net) … But, if I leave osbpy as-is, the obspy.gse module will ask for a numpy>=1.1.0 (as stated in obspy.core-0.4.8-py2.7.egg\EGG-INFO\requires.txt).

The solution is simply to remove the line from requires.txt (will be empty), and to include the numpy package in the normal way of py2exe :

includes = []
includes.append('numpy')

and to finally define the setup function :

setup(console=['gseinfo.py'],
    author="ROB Seismology - THOMAS LECOCQ",
    version = "1.0",
    description = "gseinfo",
    name = "gseinfo",
    options = {"py2exe": {    "optimize": 0,
                              "packages": packages,
                              "includes": includes,
                              "dist_dir": 'dist',
                              "bundle_files":2,
                              "xref": False,
                              "skip_archive": True,
                              "ascii": False,
                              "custom_boot_script": '',
                              "compressed":False,
                              "dll_excludes":["MSVCP90.dll", 'libiomp5md.dll',
'libifcoremd.dll','libmmd.dll' , 'svml_dispmd.dll','libifportMD.dll' ]
                             },},data_files=data_files)

Note, I’ve excluded some annoying .dll files, I still have to test whether the .exe is fully portable that way.

 

Running $python setup.py py2exe will build the .exe in a /dist directory BUT will not bundle all files inside a single EXE. As for now, I don’t care about have 100MB of files and folders in the my application directory ; Moreover, my bigger project runs on Enthought Tool Suite and can’t be zipped to a single Library.zip. One could try the last step of this tutorial, but I’m not quite sure it will work.

Please let me know if you find an easier way… Py2exe guys, pleeeeeeeeeeeeease, find a way to support that asap 🙂

Cheers,

(the full setup.py is after the break 🙂

from distutils.core import setup
import py2exe
import os
import sys
import pkg_resources

data_files = []

data_files.append('test.gse')

def walk(path):
    names = os.listdir(path)
    for name in names:
        fname = os.path.join(path,name)

        if os.path.isdir(fname):
            walk(fname)
        else:
            destination = os.path.join(path.replace('c:\python27\lib\site-packages\\',''))
            source = os.path.join(path, name)
            global data_files
            print source, "->>", destination
            data_files.append((destination,[source]))

eggs = pkg_resources.require("obspy.gse2")
eggpacks = set()
eggspth = open("dist/eggs.pth", "w")

for egg in eggs:
    print "EGG:", egg.egg_name()
    try:
        eggspth.write(os.path.basename(egg.location))
        eggspth.write("\n")
        eggpacks.update(egg.get_metadata_lines("top_level.txt"))

        print os.path.basename(egg.location), egg.location
        if egg.egg_name().count('setuptools') != 0 : 
            walk(egg.location+'-info')
            egg.location = egg.location.replace('-0.6c11-py2.7.egg','')

        walk(egg.location)

    except:
        pass

eggspth.close()

includes = []
includes.append('numpy')

packages = []

setup(console=['gseinfo.py'],
    author="ROB Seismology - THOMAS LECOCQ",
    version = "1.0",
    description = "gseinfo",
    name = "gseinfo",
    options = {"py2exe": {    "optimize": 0,
                              "packages": packages,
                              "includes": includes,
                              "dist_dir": 'dist',
                              "bundle_files":2,
                              "xref": False,
                              "skip_archive": True,
                              "ascii": False,
                              "custom_boot_script": '',
                              "compressed":False,
                              "dll_excludes":["MSVCP90.dll", 'libiomp5md.dll',
'libifcoremd.dll','libmmd.dll' , 'svml_dispmd.dll','libifportMD.dll' ]
                             },},data_files=data_files)

 

Leave a Reply

Your email address will not be published. Required fields are marked *

*