Search code examples
pythonpython-importrelative-path

Relative imports: subpackage importing subpackage


Directory structure:

root/
   script.py
   package/
      __init__.py
      module1.py
      module2.py

In script.py I need to import a method from module1.py and run it.

#contents of script.py 
from package.module1 import func1

func1()

The method in module1.py requires a method from module2.py.

#contents of module1.py 
from package.module2 import func2

def func1():
    func2()

if __name__ == "__main__":
     func1()

The above code works if I run script.py directly. There are some situations, however, where I need to run module1.py directly via crontab and this is where the problem lies.

When the cronjob runs, I get an error saying func2 can't be found. I can run module1.py if I modify the import to the following:

#contents of module1.py 
from module2 import func2

def func1():
    func2()

if __name__ == "__main__":
     func1()

I can make both situations work if I add an else statement in module1.py but it looks so hacky:

#contents of module1.py 

def func1():
    func2()

if __name__ == "__main__":
    from module2 import func2 
    func1()
else:
    from package.module2 import func2
    func1()

How can I do this more pythonically? Maybe I need to utilize __init__.py somehow? It's just an empty file at the moment.


Solution

  • When running a file with python file.py python does not look at the directory containing file for __init__.py. This means you cannot use import package.module nor from .module import x imports.

    The way to fix this is to tell python that file.py is part of a package. This is done by using the -m switch:

    python -m package.module1
    

    So you don't have to fix your code, you have to fix your crontab command!


    This said: I personally never like to run modules. I'd rather write a run_module1.py script outside the package that simply contains:

    from package import module1
    module1.func1()
    

    Then run run_module1.py instead of running module1.py directly.

    I don't think there is any good reason to want to run module1.py as you are doing and expecting it to work without hacky imports as you tried. AFAIK there is no non-hackish solution to that.