Discussion:
[Fab-user] Task.run() and task decorators
Rich Andrews
2013-12-15 19:55:32 UTC
Permalink
Hello, some guidance would be greatly appreciated. My difficulty is
understanding the design intention with fab.api.Task subclasses. It
appears that a reasonable pattern is to subclass Task, and implement
task-oriented logic in Task.run(). One would then be left with a reusable
Task subclass that may be used with a variety of @tasks.

For example, should a Task subclass be made that implements some
task-oriented logic in run(), it is believed that the Task would then be
utilized as follows:

@task(task_class=CustomTask, myarg='value_a', alias='ata')
def wrapped_function_a(instance):
# logic is in CustomTask.run()
pass

@task(task_class=CustomTask, myarg='value_b', alias='ata')
def wrapped_function_b(instance):
# logic is in CustomTask.run()
pass

Then, CustomTask is instantiated once for each declared @task decorator,
and customtask.run() is called by the fab runtime with the custom args in
@task and then for each host in the specified role. For example, fab ...
-H a,b,c,d would result in four runs of customtask.run()

The desire is that a decorator such as @runs_once could then be stacked.
But what happens in this case is that the customtask.run() is invoked for
each host specified in the role, and the wrapped function is what runs once.

Is there another preferred way to invoke Task subclasses? Or does the
task-oriented logic belong in the function wrapped by @task?

If the behavior described ins't clear, a runnable code example and output
is available below.

Thanks!

--


Python 2.7.4
Fabric 1.8.0
Paramiko 1.12.0

fab_busybox1.py:

#!/bin/env python

from fabric.api import *
from fabric.tasks import Task

class CustomTask(Task):
# init once for all tasks name this class via task_class
def __init__(self, func, myarg, *args, **kwargs):
super(CustomTask, self).__init__(*args, **kwargs)
self.func = func
self.myarg = myarg

# run multiple times for all hosts in roles
def run(self, *args, **kwargs):
print("%s run()" % self)
return self.func(self, *args, **kwargs)

@task(task_class=CustomTask, myarg='value', alias='at')
@runs_once
def wrapped_custom_task(instance):
print("wrapped_custom_task()")


$ fab -f fab_busybox1.py -H a,b,c,d at
[a] Executing task 'at'
<fab_busybox1.CustomTask object at 0x99fdccc> run()
wrapped_custom_task()
[b] Executing task 'at'
<fab_busybox1.CustomTask object at 0x99fdccc> run()
[c] Executing task 'at'
<fab_busybox1.CustomTask object at 0x99fdccc> run()
[d] Executing task 'at'
<fab_busybox1.CustomTask object at 0x99fdccc> run()
Michael Gliwinski
2013-12-16 13:01:26 UTC
Permalink
On Sunday 15 Dec 2013 14:55:32 Rich Andrews wrote:
...
Post by Rich Andrews
But what happens in this case is that the customtask.run() is invoked for
each host specified in the role, and the wrapped function is what runs once.
Off top of my head, this may have something to do with the order of
decorators. Have you tried ordering them differently?


**********************************************************************************************
The information in this email is confidential and may be legally privileged. It is intended solely for the addressee and access to the email by anyone else is unauthorised.
If you are not the intended recipient, any disclosure, copying, distribution or any action taken or omitted to be taken in reliance on it, is prohibited and may be unlawful.
When addressed to our clients, any opinions or advice contained in this e-mail are subject to the terms and conditions expressed in the governing client engagement leter or contract.
If you have received this email in error please notify ***@henderson-group.com

John Henderson (Holdings) Ltd
Registered office: 9 Hightown Avenue, Mallusk, County Antrim, Northern Ireland, BT36 4RT.
Registered in Northern Ireland
Registration Number NI010588
Vat No.: 814 6399 12
*********************************************************************************
Rich Andrews
2013-12-16 18:28:50 UTC
Permalink
Michael, thanks for responding to my question about, "What is the best way
to implement Task subclass task-oriented logic: In Task.run() or in the
wrapped function?"

I believe that the fab task function decorators must be declared in
order,@task()first, with the remainder following. Should
@runs_once precede @task() as follows, the following exception will occur:

@runs_once
@task(task_class=CustomTask, myarg='value', alias='ata')
def wrapped_custom_taska(instance):
print("wrapped_custom_taska()")

File "xxxxxx/local/lib/python2.7/site-packages/fabric/decorators.py",
line 136, in runs_once
@wraps(func)
File "/usr/lib/python2.7/functools.py", line 33, in update_wrapper
setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'CustomTask' object has no attribute '__name__'

I believe this is expected behavior.

What is notable, is that if @task() does not have a task_class specified,
WrappedCallableTask is used. When given the two fab task functions:

@task(task_class=CustomTask, myarg='value', alias='ata')
@runs_once
def wrapped_custom_taska(instance):
print("wrapped_custom_taska()")

# WrappedCallableTask
@task()
@runs_once
def wrapped_custom_taskc():
print("wrapped_custom_task()")

The following runs occur:

$ fab -f fab_busybox1.py -H a,b,c,d wrapped_custom_taskc
[a] Executing task 'wrapped_custom_taskc'
WrappedCallableTask run()
wrapped_custom_task()
WrappedCallableTask run()
WrappedCallableTask run()
WrappedCallableTask run()
Done.

$ fab -f fab_busybox1.py -H a,b,c,d wrapped_custom_taska
[a] Executing task 'wrapped_custom_taska'
<fab_busybox1.CustomTask object at 0x15cd2d0> run()
wrapped_custom_taska()
[b] Executing task 'wrapped_custom_taska'
<fab_busybox1.CustomTask object at 0x15cd2d0> run()
[c] Executing task 'wrapped_custom_taska'
<fab_busybox1.CustomTask object at 0x15cd2d0> run()
[d] Executing task 'wrapped_custom_taska'
<fab_busybox1.CustomTask object at 0x15cd2d0> run()
Done.

We might be closer to answering question with this example. It appears both
WrappedCallableTask and the Task subclass are run() for each host present
in the fab role being treated, though the @runs_once decorator is present.

So then a more articulate question may be, "How does a Task subclass know
the intentions of the decorators following its @task(task_class=)
declaration?"


On Mon, Dec 16, 2013 at 8:01 AM, Michael Gliwinski <
Post by Michael Gliwinski
...
Post by Rich Andrews
But what happens in this case is that the customtask.run() is invoked
for
Post by Rich Andrews
each host specified in the role, and the wrapped function is what runs
once.
Off top of my head, this may have something to do with the order of
decorators. Have you tried ordering them differently?
Michael Gliwinski
2013-12-17 11:29:51 UTC
Permalink
On Monday 16 Dec 2013 13:28:50 Rich Andrews wrote:
...
Post by Rich Andrews
We might be closer to answering question with this example. It appears both
WrappedCallableTask and the Task subclass are run() for each host present
So then a more articulate question may be, "How does a Task subclass know
declaration?"
Well, what I was going by is that runs_once is implemented as a memoizing
function, i.e. it just saves result of the wrapped callable in an attribute of
that callable, and avoids calling it when that attribute is already present.

So the Task subclass doesn't need to know anything, it's just if you wanted
the run() method to execute only once, *it* would have to be the callable
wrapped in runs_once (or be implemented in a similar way).


**********************************************************************************************
The information in this email is confidential and may be legally privileged. It is intended solely for the addressee and access to the email by anyone else is unauthorised.
If you are not the intended recipient, any disclosure, copying, distribution or any action taken or omitted to be taken in reliance on it, is prohibited and may be unlawful.
When addressed to our clients, any opinions or advice contained in this e-mail are subject to the terms and conditions expressed in the governing client engagement leter or contract.
If you have received this email in error please notify ***@henderson-group.com

John Henderson (Holdings) Ltd
Registered office: 9 Hightown Avenue, Mallusk, County Antrim, Northern Ireland, BT36 4RT.
Registered in Northern Ireland
Registration Number NI010588
Vat No.: 814 6399 12
*********************************************************************************
Rich Andrews
2013-12-17 15:13:01 UTC
Permalink
Michael,

Wrapping TaskSubclass.run() with runs_once works well.

Best,
Rich





On Tue, Dec 17, 2013 at 6:29 AM, Michael Gliwinski <
Post by Rich Andrews
...
Post by Rich Andrews
We might be closer to answering question with this example. It appears
both
Post by Rich Andrews
WrappedCallableTask and the Task subclass are run() for each host present
present.
Post by Rich Andrews
So then a more articulate question may be, "How does a Task subclass know
declaration?"
Well, what I was going by is that runs_once is implemented as a memoizing
function, i.e. it just saves result of the wrapped callable in an attribute of
that callable, and avoids calling it when that attribute is already present.
So the Task subclass doesn't need to know anything, it's just if you wanted
the run() method to execute only once, *it* would have to be the callable
wrapped in runs_once (or be implemented in a similar way).
**********************************************************************************************
The information in this email is confidential and may be legally
privileged. It is intended solely for the addressee and access to the
email by anyone else is unauthorised.
If you are not the intended recipient, any disclosure, copying,
distribution or any action taken or omitted to be taken in reliance on it,
is prohibited and may be unlawful.
When addressed to our clients, any opinions or advice contained in this
e-mail are subject to the terms and conditions expressed in the governing
client engagement leter or contract.
If you have received this email in error please notify
John Henderson (Holdings) Ltd
Registered office: 9 Hightown Avenue, Mallusk, County Antrim, Northern Ireland, BT36 4RT.
Registered in Northern Ireland
Registration Number NI010588
Vat No.: 814 6399 12
*********************************************************************************
Loading...