This also has to do with the Python's import mechanism, and specifically with using import
inside a function.
Using Python 2.7.9 and Fabric 1.10.0, create the following three files:
fabfile.py:
from another import another_hello
def hello():
print 'hello, world'
another_hello()
another.py:
def another_hello():
from secret import TEXT
print 'Hello, world!'
print 'text: ' + TEXT
secret/__init__.py:
(also create a folder secret/
)
TEXT = 'secret'
Now try fab hello
. It complains:
File "/home/sergey/projects/Bask/service/t/fabfile.py", line 4, in hello
another_hello()
File "/home/sergey/projects/Bask/service/t/another.py", line 2, in another_hello
from secret import TEXT
ImportError: No module named secret
At the same time, you can easily start an interpreter and type
from fab import hello; hello()
. Works perfectly:
In [2]: from fabfile import hello; hello()
hello, world
Hello, world!
text: secret
Why this difference?
Now, I have found a hack that makes this work. Just add an import secret
to the beginning of fabfile.py
. I think what happens is that the fab
tool only works with a proper PYTHONPATH
when it opens fabfile.py
to find a particular task, but once it has imported the task and started actually running it, then something changes, so it doesn't have access to the original folder any longer.
Is my hack the way to go? But doesn't it break encapsulation, to say the last, since fabfile.py
is supposed to know all indirect dependencies of any function or method that it invokes? Perhaps it's an argument against import
statements inside functions?
This is a known problem in Fabric. There are several issues regarding it in Fabric's issue tracker on Github. See issue #256 for example.
Workarounds
You can put
from secret import TEXT
on the first line of another.py
or add current directory into module search path.
def another_hello():
import sys
sys.path.insert(0, '')
from secret import TEXT
print 'Hello, world!'
print 'text: ' + TEXT