Search code examples
pythonsetuptools

Python console_scripts with multi level module


I have the following code in setup.py

from setuptools import setup
setup(
    name='raas',
    version='0.1',
    description='Program to test console_scripts\'s\n.',
    packages=['raas'],
    entry_points={
        'console_scripts': ['raas = raas.cli.entry:main',
                            'raas1 = raas.test:main']
    },
    zip_safe=True,
)

The package structure is

$ --> ls -lR
total 8
drwxr-xr-x  6 noorul  wheel  192 Mar 22 01:37 raas
-rw-r--r--  1 noorul  wheel  349 Mar 22 01:37 setup.py

./raas:
total 16
-rw-r--r--  1 noorul  wheel    0 Mar 22 01:28 __init__.py
-rw-r--r--  1 noorul  wheel  103 Mar 22 01:32 __init__.pyc
drwxr-xr-x  6 noorul  wheel  192 Mar 22 01:32 cli
-rw-r--r--  1 noorul  wheel   29 Mar 22 01:37 test.py

./raas/cli:
total 24
-rw-r--r--  1 noorul  wheel    0 Mar 22 01:28 __init__.py
-rw-r--r--  1 noorul  wheel  107 Mar 22 01:32 __init__.pyc
-rw-r--r--  1 noorul  wheel   37 Mar 22 01:29 entry.py
-rw-r--r--  1 noorul  wheel  234 Mar 22 01:32 entry.pyc

Contents of the entry.py and test.py are

$ --> cat raas/cli/entry.py
def main():
    print "Hellow world"

$ --> cat raas/test.py
def main():
    print "Test"

But after installing the package using setup.py install in a virtual env. I could only run raas1 and not raas

$ --> ./venv/bin/raas
Traceback (most recent call last):
  File "./venv/bin/raas", line 11, in <module>
    load_entry_point('raas==0.1', 'console_scripts', 'raas')()
  File "/private/tmp/raas/venv/lib/python2.7/site-packages/pkg_resources/__init__.py", line 480, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/private/tmp/raas/venv/lib/python2.7/site-packages/pkg_resources/__init__.py", line 2693, in load_entry_point
    return ep.load()
  File "/private/tmp/raas/venv/lib/python2.7/site-packages/pkg_resources/__init__.py", line 2324, in load
    return self.resolve()
  File "/private/tmp/raas/venv/lib/python2.7/site-packages/pkg_resources/__init__.py", line 2330, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
ImportError: No module named cli.entry

$ --> ./venv/bin/raas1
Test

Am I missing something?


Solution

  • Problem

    What you are missing lies in the line

    packages=['raas'],
    

    You might think that this will include raas with all its subpackages, but it doesn't! packages=['raas'] will include only raas/__init__.py and every python module in raas directory - in your case the test.py. This is why raas1 command works.

    However, none of the subpackages of raas will be included, so the raas.cli subpackage won't be installed. You should be able to verify it is missing by listing the installed files with pip (if you have installed the package with it):

    $ pip show -f raas
    Name: raas
    Version: ...
    ...
    Files:
      ...
      raas/__init__.py
      raas/test.py
    

    You should see neither raas/cli/__init__.py nor raas/cli/entry.py in the files list.

    Solution

    The obvious solution is to include raas.cli to the packages list:

    packages=['raas', 'raas.cli'],
    

    However, it's pretty clumsy to maintain: when adding new package or renaming an existing one, you have to include the changes in the packages list in setup.py or they will not be included in the distribution. Since you are already using setuptools, it offers a much better solution:

    from setuptools import setup, find_packages
    
    setup(
        ...
        packages=find_packages(),
    )
    

    The function find_packages() will perform a recursive scan of the current directory, looking for python packages, and generate a list of qualified package names that will be included in the distribution. Read its doc for more details.