How to make setup.py install multiple top-level packages?

Question:

I have a python package that I would like to organize like so:

root/
    setup.py
    package1/
        __init__.py
        package2/
            __init__.py
        package3/
            __init__.py

I would like setup.py to install package1, package2, and package3 into the correct site-packages/ directory as top level packages, so that I could execute

import package1
import package2
import package3

without a problem. Perhaps this needs multiple setup.py files?

But most importantly, I’d like to ultimately be able to install all three packages by executing pip install root. How do I do this? Preferably a distutils-only solution, though setuptools is ok as well.

Asked By: Uri Laserson

||

Answers:

I feel it is cleaner to have your development file/folder structure more closely represent the deployment structure.

What this means is, I’d place all three packages at the same level instead of nesting them.

If you subsequently want to be able to import all three via one of them, you can set up a dependency in your single setup.py file.

Answered By: JosefAssad

You can try

packages=['package1', 'package2', 'package3'],
package_dir={
    'package2': 'package1',
    'package3': 'package1',
},

to tell distutils that packages 2 and 3 are located inside package1.

A full working setup.py could look like this:

#!/usr/bin/env python

from distutils.core import setup

setup(name='root',
      version='1.0',
      description='Root package',
      author='Me',
      author_email='[email protected]',
      url='https://example.org/root',
      packages=['package1', 'package2', 'package3'],
      package_dir={
          'package2': 'package1',
          'package3': 'package1',
      },
)

python -m pip install ./root installs the packages into the structure you look for, such that

import package1
import package2
import package3

work.

As a proof, this is how ls package* looks like in the site-packages dir:

package1:
__init__.py __pycache__

package2:
__init__.py __pycache__

package3:
__init__.py __pycache__

And this is the root-1.0.dist-info/RECORD file:

package1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
package1/__pycache__/__init__.cpython-37.pyc,,
package2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
package2/__pycache__/__init__.cpython-37.pyc,,
package3/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
package3/__pycache__/__init__.cpython-37.pyc,,
root-1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
root-1.0.dist-info/METADATA,sha256=Z1crlNNrRhJwO6O2ujtNsCo9rURrPoZGE0--ZhuBZsU,190
root-1.0.dist-info/RECORD,,
root-1.0.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
root-1.0.dist-info/top_level.txt,sha256=svjaZCiNfClr21KxGb71MTR7bWON1-VZRPiPgM0FjV4,27
Answered By: Sebastian Höffner