Search code examples
pythonencapsulationfabricpython-import

Fabric import bug: "fab task" vs. "from fabfile import task; task()"


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?


Solution

  • 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