Python 3, how to import modules for pip installable package and runnable script?

334 Views Asked by At

I wrote a Python 3 console-application which has the same structure as the example below. I want to know how I should be importing the modules in order to achieve the behavior specified in the desired functionality section.

/foo
  |-/foo
  |   |-__init__.py
  |   |-/bar
  |   |   |-__init__.py
  |   |   |-eggs.py
  |   |-ham.py
  |   |-spam.py
  |-setup.py

Where:

  1. The spam module has the main function
  2. The spam module imports the Ham class in the ham module
  3. The ham module imports the Egg class in the eggs module
  4. Both __init__.py files are empty
  5. Basic setup.py file that imports setuptools

Desired Functionality

  • I want to be able to upload the console app to PyPi. Then it can be downloaded as a package with pip, and be executed in the terminal.

    ~/>> foo
    
  • I also want to be able to run it as a Python script.

    ~/foo>> python3 foo/spam.py
    

I found, what I think is a hacky, way to achieve this.

The (hacky) Solution

In order to get the installed package and Python script functionality, I added a try: ... except: ... which works but it doesn't seem the right way to do this.

try:
    from module import Class # import will work for running as script
except:
    # relative import will work for running as a package installed with pip
    from .module import Class 

If I only leave the relative import, and try to run the code as a Python script, I get:

Traceback (most recent call last):
  File "foo/spam.py", line 6, in <module>
    from .ham import Ham
ModuleNotFoundError: No module named '__main__.ham'; '__main__' is not a package

A similar situation occurs if I leave the regular import and run console application:

 Traceback (most recent call last):
  File ".../foo/spam.py", line 6, in <module>
    from ham import Ham
ModuleNotFoundError: No module named 'ham'

The Files

__init__.py files are empty

spam.py

"""spam module"""

try:
    from ham import Ham
except:
    from .ham import Ham

def main():
    pass

if __name__ == "__main__:
    main()

ham.py

"""ham module"""

try:
    from eggs import Egg
except:
    from .eggs import Egg

class Ham:
    pass

eggs.py

"""eggs module"""

class Egg:
    pass

setup.py

from setuptools import setup, find_packages

setup(
    name="foo",
    version="1.0.0",
    author="Pablo",
    description="Example",
    packages=find_packages(),
    entry_points={
        "console_scripts": ["foo=foo.spam:main"]
    },
)
0

There are 0 best solutions below