views:

310

answers:

1

I want to distribute my app on OSX (using py2app) and as a Debian package.

The structure of my app is like:

app/
     debian/
            <lots of debian related stuff>
     scripts/
             app
     app/
         __init__.py
         app.py
         mod1/
              __init__.py
              a.py
         mod2/
              __init__.py
              b.py

My setup.py looks something like:

from setuptools import setup
import os
import os.path

osname = os.uname()[0]

if osname == 'Darwin':
    APP = ['app/app.py']
    DATA_FILES = []
    OPTIONS = {'argv_emulation': True}

    setup(
        app=APP,
        data_files=DATA_FILES,
        options={'py2app': OPTIONS},
        setup_requires=['py2app'],
    )
elif osname == 'Linux':
        setup(
        name = "app",
        version = "0.0.1",
        description = "foo bar",
        packages = ["app", "app.mod1", "app.mod2"],
        scripts = ["scripts/app"],
        data_files = [
            ("/usr/bin", ["scripts/app"]),
       ]
    )

Then, in b.py (this is on OSX):

from app.mod2.b import *

I get:

ImportError: No module named mod2.b

So basically, mod2 can't acccess mod1. On Linux there's no problem, because the python module 'app' is installed globally in /usr/shared/pyshared. But on OSX the app will obviously be a self-contained .app thing built by py2app. I wonder if I approached this totally wrong, are there any best practices when distributing Python apps on OSX?

Edit: I also tried a hack like this in b.py:

from ..mod2.b import *

ValueError: Attempted relative import beyond toplevel package

Edit2: Seems to be related to this http://stackoverflow.com/questions/72852/how-to-do-relative-imports-in-python

+2  A: 

I'm not sure if this is the 'best practice' or not (I've not put much python software into proper distribution), but I would just make sure that the top-level app package was in sys.path. Something like putting the following into the top-level __init__.py:

try:
    import myapp
except ImportError:
    import sys
    from os.path import abspath, dirname, split
    parent_dir = split(dirname(abspath(__file__)))[0]
    sys.path.append(parent_dir)

I think that should do the right thing in a cross platform way.

EDIT: As kaizer.se points out this might not work in the __init__.py file, depending on how the code you're invoking is getting executed. It would only work if that file is evaluated. The key is to make sure that the top-level package is in sys.path from some the code that actually is running.

Often times, so that I an execute individual files inside of a package directly (for testing with the if __name__ eq '__main__' idiom), I'll do something like place a statement:

import _setup

At the top of the individual file in question, and then create a file _setup.py which does the path munging as necessary. So, something like:

package/
    __init__.py
    _setup.py
    mod1/
        __init__.py
        _setup.py
        somemodule.py

If you import _setup from somemodule.py, that setup file can ensure that the top level package is in sys.path before the rest of the code in somemodule.py is evaluated.

Matt Anderson
can that be a solution? How will the application reach the package's __init__ module in the first place, if it's not in the path?
kaizer.se