如何创建fabric2任务装饰器?

huangapple go评论69阅读模式
英文:

How to create fabric2 tasks decorators?

问题

以下是您要翻译的代码部分:

让我们首先考虑一些使用fabric2的未重构的工作代码片段

import traceback import inspect

from fabric2.tasks import task
from fabric2 import Config, Connection

def get_function_name():
    return traceback.extract_stack(None, 2)[0][2]

def get_function_parameters_and_values():
    frame = inspect.currentframe().f_back
    args, _, _, values = inspect.getargvalues(frame)
    return ", ".join([str(i) for i in args])

@task def foo1(c, arg1):
    print(f'<{get_function_name()} {get_function_parameters_and_values()}>)

        def _inner():
            if int(arg1) < 5:
                print('arg1 is not big enough')
                return
            print('doing foo1 stuff')
        res = _inner()
        print(f'</{get_function_name()}')
        return res

@task def foo2(c, arg1, arg2):
    print(f'<{get_function_name()} {get_function_parameters_and_values()}>)

        def _inner():
            if int(arg1) < 5 and int(arg2) < 5:
                print('arg1 & arg2 are not big enough')
                return
            print('doing foo2 stuff')
        res = _inner()
        print(f'</{get_function_name()}')
        return res

现在如您所见上面存在一些代码重复我想通过包装器/装饰器的方式将其提取出来问题出在如果我尝试像这样做

def wrap(func):
    def wrapped(*args, **kwargs):
        print('begin')
        res = func(*args, **kwargs)
        print('end')
    return wrapped

@task
@wrap
def foo3(c, arg1):
    if int(arg1) < 5:
        print('arg1 is not big enough')
        return
    print('doing foo3 stuff')

@wrap
@task
def foo4(c, arg1):
    if int(arg1) < 5:
        print('arg1 is not big enough')
        return
    print('doing foo4 stuff')

当我运行其中任何一个任务时都会出现异常例如

(py364_32) D:\sources\personal\python\bplfab\examples>fab2 foo3 --arg1=3
Traceback (most recent call last):
  File "d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py", line 160, in argspec
    context_arg = arg_names.pop(0)
IndexError: pop from empty list

在处理上述异常时又出现了另一个异常

Traceback (most recent call last):
  File "d:\software\python364_32\Lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "d:\software\python364_32\Lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "D:\virtual_envs\py364_32\Scripts\fab2.exe\__main__.py", line 7, in <module>
  File "d:\virtual_envs\py364_32\lib\site-packages\invoke\program.py", line 352, in run
    self.parse_collection()
  File "d:\virtual_envs\py364_32\lib\site-packages\invoke\program.py", line 444, in parse_collection
    self.load_collection()
  File "d:\virtual_envs\py364_32\lib\site-packages\fabric2\main.py", line 87, in load_collection
    super(Fab, self).load_collection()
  File "d:\virtual_envs\py364_32\lib\site-packages\invoke\program.py", line 661, in load_collection
    module, parent = loader.load(coll_name)
  File "d:\virtual_envs\py364_32\lib\site-packages\invoke\loader.py", line 76, in load
    module = imp.load_module(name, fd, path, desc)
  File "<frozen importlib._bootstrap>", line 235, in load_module
  File "<frozen importlib._bootstrap>", line 172, in load_source
  File "D:\sources\personal\python\bplfab\examples\fabfile.py", line 59, in <module>
    @wrap
  File "d:\virtual_envs\py364_32\lib\site-packages\fabric2\tasks.py", line 71, in task
    return invoke.task(*args, **kwargs)
  File "d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py", line 313, in task
    return klass(args[0], **kwargs)
  File "d:\virtual_envs\py364_32\lib\site-packages\fabric2\tasks.py", line 21, in __init__
    super(Task, self).__init__(*args, **kwargs)
  File "d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py", line 77, in __init__
    self.positional = self.fill_implicit_positionals(positional)
  File "d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py", line 168, in fill_implicit_positionals
    args, spec_dict = self.argspec(self.body)
  File "d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py", line 163, in argspec
    raise TypeError("Tasks must have an initial Context argument!")
TypeError: Tasks must have an initial Context argument!

(py364_32) D:\sources\personal\python\bplfab\examples>fab2 foo4 --arg1=3
Traceback (most recent call last):
  File "d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py", line 160, in argspec
    context_arg = arg_names.pop(0)
IndexError: pop from empty list

在处理上述异常时又出现了另一个异常

Traceback (most recent call last):
  File "d:\software\python364_32\Lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "d:\software\python364_32\Lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "D:\virtual_envs\py364_32\Scripts\fab2.exe\__main__.py", line 7, in <module>
  File "d:\virtual_envs\py364_32\lib\site-packages\invoke\program.py", line 352, in run
    self.parse_collection()
  File "d:\virtual_envs\py364_32\lib\site-packages\invoke\program

<details>
<summary>英文:</summary>

Let&#39;s start by considering some unrefactored working piece of code that uses fabric2:

    import traceback import inspect
    
    
    from fabric2.tasks import task 
    from fabric2 import Config, Connection
    
    
    def get_function_name():
        return traceback.extract_stack(None, 2)[0][2]
    
    
    def get_function_parameters_and_values():
        frame = inspect.currentframe().f_back
        args, _, _, values = inspect.getargvalues(frame)
        return &quot;, &quot;.join([str(i) for i in args])
    
    
    @task def foo1(c, arg1):
        print(f&#39;&lt;{get_function_name()} {get_function_parameters_and_values()}&gt;&#39;)
    
        def _inner():
            if int(arg1) &lt; 5:
                print(&#39;arg1 is not big enough&#39;)
                return
            print(&#39;doing foo1 stuff&#39;)
        res = _inner()
        print(f&#39;&lt;/{get_function_name}&#39;)
        return res
    
    
    @task def foo2(c, arg1, arg2):
        print(f&#39;&lt;{get_function_name()} {get_function_parameters_and_values()}&gt;&#39;)
    
        def _inner():
            if int(arg1) &lt; 5 and int(arg2) &lt; 5:
                print(&#39;arg1 &amp; arg2 are not big enough&#39;)
                return
            print(&#39;doing foo2 stuff&#39;)
        res = _inner()
        print(f&#39;&lt;/{get_function_name}&#39;)
        return res
    
Now, as you can see above, there is some code duplication I&#39;d like to factor out in the way of a wrapper/decorator... Problem comes if I try something like this:

    def wrap(func):
        def wrapped(*args, **kwargs):
            print(&#39;begin&#39;)
            res = func(*args, **kwargs)
            print(&#39;end&#39;)
        return wrapped
    
    
    @task
    @wrap
    def foo3(c, arg1):
        if int(arg1) &lt; 5:
            print(&#39;arg1 is not big enough&#39;)
            return
        print(&#39;doing foo3 stuff&#39;)
    
    
    @wrap
    @task
    def foo4(c, arg1):
        if int(arg1) &lt; 5:
            print(&#39;arg1 is not big enough&#39;)
            return
        print(&#39;doing foo4 stuff&#39;)

And I run either of these tasks I&#39;ll get exceptions with both such as:

    (py364_32) D:\sources\personal\python\bplfab\examples&gt;fab2 foo3 --arg1=3
    Traceback (most recent call last):
      File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py&quot;, line 160, in argspec
        context_arg = arg_names.pop(0)
    IndexError: pop from empty list

    During handling of the above exception, another exception occurred:

        Traceback (most recent call last):
          File &quot;d:\software\python364_32\Lib\runpy.py&quot;, line 193, in _run_module_as_main
            &quot;__main__&quot;, mod_spec)
          File &quot;d:\software\python364_32\Lib\runpy.py&quot;, line 85, in _run_code
            exec(code, run_globals)
          File &quot;D:\virtual_envs\py364_32\Scripts\fab2.exe\__main__.py&quot;, line 7, in &lt;module&gt;
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\program.py&quot;, line 352, in run
            self.parse_collection()
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\program.py&quot;, line 444, in parse_collection
            self.load_collection()
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\fabric2\main.py&quot;, line 87, in load_collection
            super(Fab, self).load_collection()
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\program.py&quot;, line 661, in load_collection
            module, parent = loader.load(coll_name)
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\loader.py&quot;, line 76, in load
            module = imp.load_module(name, fd, path, desc)
          File &quot;d:\virtual_envs\py364_32\lib\imp.py&quot;, line 235, in load_module
            return load_source(name, filename, file)
          File &quot;d:\virtual_envs\py364_32\lib\imp.py&quot;, line 172, in load_source
            module = _load(spec)
          File &quot;&lt;frozen importlib._bootstrap&gt;&quot;, line 684, in _load
          File &quot;&lt;frozen importlib._bootstrap&gt;&quot;, line 665, in _load_unlocked
          File &quot;&lt;frozen importlib._bootstrap_external&gt;&quot;, line 678, in exec_module
          File &quot;&lt;frozen importlib._bootstrap&gt;&quot;, line 219, in _call_with_frames_removed
          File &quot;D:\sources\personal\python\bplfab\examples\fabfile.py&quot;, line 59, in &lt;module&gt;
            @wrap
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\fabric2\tasks.py&quot;, line 71, in task
            return invoke.task(*args, **kwargs)
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py&quot;, line 313, in task
            return klass(args[0], **kwargs)
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\fabric2\tasks.py&quot;, line 21, in __init__
            super(Task, self).__init__(*args, **kwargs)
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py&quot;, line 77, in __init__
            self.positional = self.fill_implicit_positionals(positional)
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py&quot;, line 168, in fill_implicit_positionals
            args, spec_dict = self.argspec(self.body)
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py&quot;, line 163, in argspec
            raise TypeError(&quot;Tasks must have an initial Context argument!&quot;)
        TypeError: Tasks must have an initial Context argument!
        
        (py364_32) D:\sources\personal\python\bplfab\examples&gt;fab2 foo4 --arg1=3
        Traceback (most recent call last):
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py&quot;, line 160, in argspec
            context_arg = arg_names.pop(0)
        IndexError: pop from empty list
        
        During handling of the above exception, another exception occurred:
        
        Traceback (most recent call last):
          File &quot;d:\software\python364_32\Lib\runpy.py&quot;, line 193, in _run_module_as_main
            &quot;__main__&quot;, mod_spec)
          File &quot;d:\software\python364_32\Lib\runpy.py&quot;, line 85, in _run_code
            exec(code, run_globals)
          File &quot;D:\virtual_envs\py364_32\Scripts\fab2.exe\__main__.py&quot;, line 7, in &lt;module&gt;
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\program.py&quot;, line 352, in run
            self.parse_collection()
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\program.py&quot;, line 444, in parse_collection
            self.load_collection()
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\fabric2\main.py&quot;, line 87, in load_collection
            super(Fab, self).load_collection()
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\program.py&quot;, line 661, in load_collection
            module, parent = loader.load(coll_name)
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\loader.py&quot;, line 76, in load
            module = imp.load_module(name, fd, path, desc)
          File &quot;d:\virtual_envs\py364_32\lib\imp.py&quot;, line 235, in load_module
            return load_source(name, filename, file)
          File &quot;d:\virtual_envs\py364_32\lib\imp.py&quot;, line 172, in load_source
            module = _load(spec)
          File &quot;&lt;frozen importlib._bootstrap&gt;&quot;, line 684, in _load
          File &quot;&lt;frozen importlib._bootstrap&gt;&quot;, line 665, in _load_unlocked
          File &quot;&lt;frozen importlib._bootstrap_external&gt;&quot;, line 678, in exec_module
          File &quot;&lt;frozen importlib._bootstrap&gt;&quot;, line 219, in _call_with_frames_removed
          File &quot;D:\sources\personal\python\bplfab\examples\fabfile.py&quot;, line 59, in &lt;module&gt;
            @wrap
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\fabric2\tasks.py&quot;, line 71, in task
            return invoke.task(*args, **kwargs)
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py&quot;, line 313, in task
            return klass(args[0], **kwargs)
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\fabric2\tasks.py&quot;, line 21, in __init__
            super(Task, self).__init__(*args, **kwargs)
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py&quot;, line 77, in __init__
            self.positional = self.fill_implicit_positionals(positional)
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py&quot;, line 168, in fill_implicit_positionals
            args, spec_dict = self.argspec(self.body)
          File &quot;d:\virtual_envs\py364_32\lib\site-packages\invoke\tasks.py&quot;, line 163, in argspec
            raise TypeError(&quot;Tasks must have an initial Context argument!&quot;)
        TypeError: Tasks must have an initial Context argument!

It seems there is already an opened issue related somehow to this https://github.com/pyinvoke/invoke/issues/555 but so far I haven&#39;t been able to find a workaround for this.

Could you explain me what I&#39;m doing wrong here? But the most important, could you provide a way to workaround this fabric2 &quot;limitation&quot; and being able to create a decorator that allows me to use inspect to capture the task&#39;s name as well as the arguments passed in the command line?

Thanks in advance.

</details>


# 答案1
**得分**: 1

Custom decorators必须具有一个固定的初始上下文参数就像异常所说的那样所以`func(c, *args, **kwargs)`有效`func(*args, **kwargs)`无效

```py
def trace(func):
    @functools.wraps(func)
    def wrapper(c, *args, **kwargs):
        print(f'开始 {func.__name__}')
        func(c, *args, **kwargs)
        print(f'结束 {func.__name__}')
    return wrapper

@task
@trace
def uname(c):
    c.run('uname -a')
英文:

Custom decorators must have a fixed initial context argument as the exception says, so func(c, *args, **kwargs) works while func(*args, **kwargs) doesn't:

def trace(func):
    @functools.wraps(func)
    def wrapper(c, *args, **kwargs):
        print(f&#39;Begin {func.__name__}&#39;)
        func(c, *args, **kwargs)
        print(f&#39;End {func.__name__}&#39;)
    return wrapper

@task
@trace
def uname(c):
    c.run(&#39;uname -a&#39;)

huangapple
  • 本文由 发表于 2020年1月3日 23:00:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/59580785.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定