如何将参数传递给Python Fabric自定义任务

1 投票
3 回答
5799 浏览
提问于 2025-04-17 20:39

我搞不清楚怎么把参数传给一个fabric自定义任务。

我有一堆任务都需要做相同的准备工作,所以我想创建一个基类来处理这些准备工作,然后再运行具体的子任务。准备代码和子任务都需要访问一些从命令行传入的参数。我还需要能够为这些参数设置默认值。

最初的尝试

我最初的尝试是没有使用任何子类,下面的代码是正确的。这个代码在文件tmp1.py中:

from fabric.api import task

def do_setup(myarg):
    ''' common setup for all tasks '''
    print "in do_setup(myarg=%s)" % myarg
    # do setup using myarg for something important

@task
def actual_task1(myarg='default_value', alias='at'):
    print "In actual_task1(myarg=%s)" % myarg
    do_setup(myarg)
    # do rest of work ...

@task
def actual_task2(myarg='default_value', alias='at'):
    print "In actual_task2(myarg=%s)" % myarg
    do_setup(myarg)
    # do rest of work ...

我从命令行运行它,没有传入任何参数,正确地看到了myarg的默认值是'default_value'

fab -f ./tmp1.py actual_task1

输出:

In actual_task1(myarg=default_value)
in do_setup(myarg=default_value)
Done.

然后我用myarg='hello'来调用它,看到'hello'被正确传递了

fab -f ./tmp1.py actual_task1:myarg='hello'

它输出:

In actual_task1(myarg=hello)
in do_setup(myarg=hello)
Done.

使用自定义任务的尝试

我接下来的尝试是创建一个公共任务来封装准备部分。这段代码是从http://docs.fabfile.org/en/1.5/usage/tasks.html复制过来的。下面的代码在文件tmp2.py中:

from fabric.api import task
from fabric.tasks import Task

def do_setup(myarg):
    ''' common setup for all tasks '''
    print "in do_setup(myarg=%s)" % myarg
    # do setup using myarg for something important

'''
Attempt to make a common task to encapsulate the setup part
copied from http://docs.fabfile.org/en/1.5/usage/tasks.html
'''
class CustomTask(Task):
    def init(self, func, myarg, args, *kwargs):
        super(CustomTask, self).init(args, *kwargs)
        print("=> init(myarg=%s, args=%s, kwargs=%s" % (myarg, args, kwargs))
        self.func = func
        self.myarg = myarg
        print "in init: self.func=",self.func,"self.myarg=",self.myarg

    def run(self, *args, **kwargs):
        return self.func(self.myarg, *args, **kwargs)

@task(task_class=CustomTask, myarg='default_value', alias='at')
def actual_task1():
    print "In actual_task1(myarg=%s)" % myarg
    # do rest of work ...

运行时遇到了两个问题:

  1. __init__得到了"default_value"而不是"Hello"

  2. 它抱怨actual_task1()期望0个参数

我这样运行它:

fab -f ./tmp2.py actual_task1:myarg="Hello"

输出:

=> init(myarg=default_value, args=(), kwargs={'alias': 'at'}
in init: self.func= self.myarg= default_value
Traceback (most recent call last):
File "/home/xxx/Documents/pyenvs/xxx/local/lib/python2.7/site-packages/fabric/main.py", line 743,      in main args, *kwargs
File "/home/xxx/Documents/pyenvs/xxx/local/lib/python2.7/site-packages/fabric/tasks.py", line 405,  in execute results[''] = task.run(args, *new_kwargs)
File "/home/xxx/test_fab/tmp2.py", line 21, in run
return self.func(self.myarg, args, *kwargs)
TypeError: actual_task1() takes no arguments (1 given)

我花了不少时间想让这个工作,但似乎无法解决default_value的问题。我一定是漏掉了什么?

我希望能得到一些帮助,让这个示例程序能够运行。第二个版本的自定义任务需要和我展示的原始版本表现得一样。

谢谢你对这个问题的帮助。

3 个回答

1

这是一个修正后的例子。

# fixed the example from http://docs.fabfile.org/en/1.8/usage/tasks.html
from fabric.api import task
from fabric.tasks import Task

class CustomTask(Task):
    def __init__(self, func, myarg1, *args, **kwargs):
        ''' 
        The special args like hosts and roles do not show up in
        args, and kwargs, they are stripped already.

        args and kwargs may contain task specific special arguments
        (e.g. aliases, alias, default, and name) to customize the
        task. They are set in the @task decorator and cannot be passed
        on the command-line. Note also that these special task
        arguments are not passed to the run method.

        Non-special arguments (in this example myarg1) are set in the task
        decorator. These other arguments are not passed to the run
        method and cannot be overridden from the command-line. 

        Note that if you pass any "task specific special arguments" or
        "non-special arguments declared in the task decorator" from the
        command-line, they are treated as different arguments and the
        command-line values are passed to the run method but not to
        this method.
        '''
        super(CustomTask, self).__init__(*args, **kwargs)
        print "IN __init__(myarg1=%s, args=%s, kwargs=%s)" % \
            (myarg1, args, kwargs)
        self.func = func
        self.myarg1 = myarg1

    def run(self, myarg2='default_value2', *args, **kwargs):
        ''' 
        The arguments to this method will be:
        1) arguments from the actual task (e.g. myarg2). This method
        is where you set a default value for the arguments from the
        actual_task, not on the actual_task.
        2) task specific arguments from the command-line
        (e.g. actual_host:foo='foo'). This example is not expecting
        any, so it strips them and does not pass them to the
        actual_function (e.g. it calls self.func with only myarg2 and
        does not pass args and kwargs)
        '''
        print "IN run(myarg2=%s, args=%s, kwargs=%s)" % \
            (myarg2, args, kwargs)
        return self.func(myarg2)

@task(task_class=CustomTask, myarg1='special_value', alias='RUNME')
def actual_task(myarg2):
    print "IN actual_task(myarg2=%s)" % myarg2

只用命令行上指定的主机运行:

fab -f ./fixed_example actual_task:hosts="hhh"

IN __init__(myarg1=special_value, args=(), kwargs={'alias': 'RUNME'})
[hhh] Executing task 'actual_task'
IN run(myarg2=default_value2, args=(), kwargs={})
IN actual_task(myarg2=default_value2)

在命令行上指定我的参数myarg2运行:

fab -f ./fixed_example actual_task:hosts="hhh",myarg2="good_value"

IN __init__(myarg1=special_value, args=(), kwargs={'alias': 'RUNME'})
[hhh] Executing task 'actual_task'
IN run(myarg2=good_value, args=(), kwargs={})
IN actual_task(myarg2=good_value)

错误的运行方式,命令行上指定了myarg1和别名。注意到init获取的是任务装饰器中指定的值,而不是命令行上的值。再注意,run现在获取了myarg1和别名作为参数。

fab -f ./fixed_example actual_task:hosts="hhh",myarg1="myarg1_from_commandline",alias="alias_from_commandline"

IN __init__(myarg1=special_value, args=(), kwargs={'alias': 'RUNME'})
[hhh] Executing task 'actual_task'
IN run(myarg2=default_value2, args=(), kwargs={'alias': 'alias_from_commandline', 'myarg1': 'myarg1_from_commandline'})
IN actual_task(myarg2=default_value2)
1

在自定义类的部分,函数 actual_task1 实际上并不接受任何参数,所以你调用你的 fabric 文件的唯一有效方式是:

fab -f ./tmp2.py actual_task1

此外,我觉得你在 CustomTask 或者 actual_task1 中并没有真正调用 do_setup

2

这是一个修正后的示例,包含了设置的部分:

from fabric.api import task
from fabric.tasks import Task

def do_setup(foo, verbose):
    ''' common setup for all tasks '''
    print "IN do_setup(foo=%s, verbose=%s)" % (foo, verbose)
    # do setup using foo and verbose...

class CustomTask(Task):
    def __init__(self, func, *args, **kwargs):
        ''' 
        The special args like hosts and roles do not show up in
        args, and kwargs, they are stripped already.

        args and kwargs may contain task specific special arguments
        (e.g. aliases, alias, default, and name) to customize the
        task. They are set in the @task decorator and cannot be passed
        on the command-line. Note also that these special task
        arguments are not passed to the run method.

        Non-special arguments (there are none in this example) are
        set in the task decorator. These other arguments are not
        passed to the run method and cannot be overridden from the
        command-line.

        Note that if you pass any "task specific special arguments" or
        "non-special arguments declared in the task decorator" from the
        command-line, they are treated as different arguments and the
        command-line values are passed to the run method but not to
        this method.
        '''
        super(CustomTask, self).__init__(*args, **kwargs)
        print "IN __init__(args=%s, kwargs=%s)" % (args, kwargs)
        self.func = func

    def run(self, foo='foo_default_val', verbose='verbose_default_val', 
            *args, **kwargs):
        ''' 
        The arguments to this method will be:
        1) arguments from the actual task (e.g. foo and verbose). This method
        is where you set a default value for the arguments from the
        actual_task, not on the actual_task.
        2) task specific arguments from the command-line
        (e.g. actual_task:bar='xxx'). This example is not expecting any,
        so it strips them and does not pass them to the
        actual_function one (e.g. it calls self.func with only foo
        and verbose and does not pass args and kwargs)
        '''
        print "IN run(foo=%s, verbose=%s, args=%s, kwargs=%s)" % \
                  (foo, verbose, args, kwargs)
        do_setup(foo, verbose)
        return self.func(foo, verbose)

@task(task_class=CustomTask, alias="RUNME")
def actual_task(foo, verbose):
    print 'IN task actual_task(foo=%s, verbose=%s)' % (foo, verbose)

在命令行中只指定主机运行:

fab -f ./example_with_setup.py actual_task:host='hhh'

IN __init__(args=(), kwargs={'alias': 'RUNME'})
[hhh] Executing task 'actual_task'
IN run(foo=foo_default_val, verbose=verbose_default_val, args=(), kwargs={})
IN do_setup(foo=foo_default_val, verbose=verbose_default_val)
IN task actual_task(foo=foo_default_val, verbose=verbose_default_val)

在命令行中指定foo运行:

fab -f ./example_with_setup.py actual_task:host='hhh',foo='bar'

IN __init__(args=(), kwargs={'alias': 'RUNME'})
[hhh] Executing task 'actual_task'
IN run(foo=bar, verbose=verbose_default_val, args=(), kwargs={})
IN do_setup(foo=bar, verbose=verbose_default_val)
IN task actual_task(foo=bar, verbose=verbose_default_val)

在命令行中同时指定foo和verbose运行:

fab -f ./example_with_setup.py actual_task:host='hhh',foo='bar',verbose=True

IN __init__(args=(), kwargs={'alias': 'RUNME'})
[hhh] Executing task 'actual_task'
IN run(foo=bar, verbose=True, args=(), kwargs={})
IN do_setup(foo=bar, verbose=True)
IN task actual_task(foo=bar, verbose=True)

撰写回答