Search code examples
pythonpython-3.6fabric

How to register fabric functions dynamically?


PROBLEM


I'm trying to figure out how to wrap dynamically some of my fabric functions, in order to do I've created a little mcve snippet:

mcve_lib.py:

from fabric.api import run


def foo():
    run('hostname')

fabfile.py:

import sys
import mcve_lib

from fabric.api import settings, task, env, roles

env.roledefs = {
    'servers': ['foo_server']
}


def register_function(name, module, wrapped_func):

    @roles(['servers'])
    @task()
    def callback():
        with settings():
            getattr(module, wrapped_func)()

    setattr(sys.modules[__name__], name, callback)


register_function("wrapped_foo", mcve_lib, "foo")

print(dir())

Problem comes when i try to list the available fabric tasks by doing fab -l, the output will be this:

(py362_32) D:\sources\personal\python\framework\pyfab>fab -l
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'env', 'mcve_lib', 'register_function', 'roles', 'settings', 'sys', 'task', 'wrapped_foo']
Available commands:

    callback

QUESTION


Any idea how to indicate properly that wrapped_foo is a fabric task?

FAILED ATTEMPTS


Attempt1:

from fabric.tasks import WrappedCallableTask

...

def register_function(name, module, wrapped_func):

    @roles(['servers'])
    def callback():
        with settings():
            getattr(module, wrapped_func)()

    setattr(sys.modules[__name__], name, WrappedCallableTask(callback))

...

Solution

  • One possible way to address this problem would be by using functools.wraps, usage example below:

    import mcve_lib
    import sys
    
    from fabric.api import settings, task, env, roles
    from functools import wraps
    
    env.roledefs = {
        'servers': ['foo_server']
    }
    
    
    def register_function(f):
    
        @roles(['servers'])
        @task
        @wraps(f)
        def callback():
            with settings():
                f()
    
        return callback
    
    
    module_funcs = [
        ('foo', mcve_lib.foo)
    ]
    
    for dst_func, src_func in module_funcs:
        setattr(sys.modules[__name__], dst_func, register_function(src_func))
    

    By using the above code fabric will be able to enumerate properly the wrapped functions, ie:

    (py362_32) D:\sources\personal\python\framework\pyfab>fab -l
    Available commands:
    
        foo