Lik Doyaya
Lik Doyaya

Reputation: 53

Define imports and Python project structure to allow running locally and when installed

I am working on a Python project and started using a setup.py file to install it. I followed some guides on how to structure the project and everything is working great after installing it.

My problem is that I "lost" the ability to run my application from inside the project's directory, locally so to speak without installing it, for testing purposes. Here's how the directory layout looks like:

project_dir/
├── bin
│   └── app.py
├── data
│   └── data.conf
├── app_module
│   └── __init__.py
├── LICENSE
├── README.md
└── setup.py

The problem has to do with the imports. In app.py I have import app_module and it works as intended after it gets installed to the proper directory by running python setup.py install. If I want to run it by doing python bin/app.py it obviously doesn't work.

What am I doing wrong and how could I get this to work? Ideally I want to be able to run it from inside the directory and it should still work when installed with the setup.py script.

Upvotes: 0

Views: 61

Answers (2)

bufh
bufh

Reputation: 3420

if your bin/app.py is only doing:

import app_module
app_module.run() # or whatever you called the main() function

you could (should!) use an entry_points (search for console_scripts).

And while running your uninstalled code, you would do either:

python -m app_module or python app_module/__init__.py (assuming you have a __main__ section inside this file, otherwise look at how json.tool is doing.

Edit

Here is a basic example, assuming your library is named "app_module".

setup.py

import imp
import os
import setuptools

module_name = 'app_module'
module = imp.load_module(
    module_name,
    *imp.find_module(module_name, [os.path.dirname(__file__)])
)

base = 'data'
data_files = [
    (
        '/usr/local/share/appdata/' + module_name + root[len(base):],
        [os.path.join(root, f) for f in files]
    ) for root, dirs, files in os.walk(base)
]

setuptools.setup(
    name=module_name,
    version=module.__version__,
    classifiers=(
        'Environment :: Console',
        'Operating System :: POSIX :: Linux',
    ),
    packages=(
        module_name,
    ),
    entry_points={
        # the executable will be called 'app' (not 'app.py')
        'console_scripts': ['app = ' + module_name + ':main'],
        # it does not seems unnecessarily complicated to me, it's just three lines
    },
    data_files=data_files,
)

app_module/__init__.py

import optparse
import sys

__version__ = '1.2.3'

def main():
    parser = optparse.OptionParser(version=__version__)
    parser.add_option('-m', '--man', action='store_true')

    opt, args = parser.parse_args()

    if opt.man:
        help(__name__)
        sys.exit()

    print opt, args


if __name__ == '__main__':
    main()

And now...

To run it:

python /full-or-replative/path/to/app_module/__init__.py
# or
export PYTHONPATH=/full-or-replative/path/to/app_module/
python -m app_module

To install and run it:

# test in a virtualenv
workon test
python setup.py install
app -h

Upvotes: 1

skyking
skyking

Reputation: 14401

What you need to do is either set the python path to include your module (ie run it as PYTHONPATH=project_dir python app.py, or make the app_module be placed in project_dir/bin in order to be included in the builtin path.

Note that not doing this is dangerous as after installed your app.py would otherwise pick up the installed package (app_module) instead of that in your project dir (which can get confusing).

Upvotes: 0

Related Questions