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 How to do relative imports in Python?
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: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__.pyfile, 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 insys.pathfrom 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:At the top of the individual file in question, and then create a file
_setup.pywhich does the path munging as necessary. So, something like:If you
import _setupfromsomemodule.py, that setup file can ensure that the top level package is insys.pathbefore the rest of the code insomemodule.pyis evaluated.