英文:
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'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 ", ".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
Now, as you can see above, there is some code duplication I'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('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')
And I run either of these tasks I'll get exceptions with both such as:
(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
During handling of the above exception, another exception occurred:
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 "d:\virtual_envs\py364_32\lib\imp.py", line 235, in load_module
return load_source(name, filename, file)
File "d:\virtual_envs\py364_32\lib\imp.py", line 172, in load_source
module = _load(spec)
File "<frozen importlib._bootstrap>", line 684, in _load
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
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
During handling of the above exception, another exception occurred:
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 "d:\virtual_envs\py364_32\lib\imp.py", line 235, in load_module
return load_source(name, filename, file)
File "d:\virtual_envs\py364_32\lib\imp.py", line 172, in load_source
module = _load(spec)
File "<frozen importlib._bootstrap>", line 684, in _load
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
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!
It seems there is already an opened issue related somehow to this https://github.com/pyinvoke/invoke/issues/555 but so far I haven't been able to find a workaround for this.
Could you explain me what I'm doing wrong here? But the most important, could you provide a way to workaround this fabric2 "limitation" and being able to create a decorator that allows me to use inspect to capture the task'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'Begin {func.__name__}')
func(c, *args, **kwargs)
print(f'End {func.__name__}')
return wrapper
@task
@trace
def uname(c):
c.run('uname -a')
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论