Reputation: 2750
Currently I'm using fab -f check_remote.py func:"arg1","arg2"...
to run fab remote.
Now I need to send a bool arg, but True/False become a string arg, how to set it as bool type?
Upvotes: 23
Views: 17189
Reputation: 46
This is a working version based on https://gist.github.com/mgedmin/f832eed2ac0f3ce31edf. Unlike the old version this actually honors all possible decorator parameters and the task alias(es):
from functools import wraps
from fabric import tasks
def fix_boolean(f):
true_values = ("yes", "true", "1")
false_values = ("no", "false", "0")
def fix_bool(value):
if isinstance(value, basestring):
if value.lower() in false_values:
return False
if value.lower() in true_values:
return True
return value
@wraps(f)
def wrapper(*args, **kwargs):
args_ = [fix_bool(arg) for arg in args]
kwargs_ = {k: fix_bool(v) for k,v in kwargs.iteritems()}
return f(*args_, **kwargs_)
return wrapper
def task(*args, **kwargs):
"""
The fabric.decorators.task decorator which automatically converts command line task arguments
to a boolean representation if applicable.
:param args:
:param kwargs:
:return: wrapped
"""
invoked = bool(not args or kwargs)
task_class = kwargs.pop("task_class", tasks.WrappedCallableTask)
def wrapper(f):
return task_class(fix_boolean(f), *args, **kwargs)
return wrapper if invoked else wrapper(args[0])
Gist: https://gist.github.com/eltismerino/a8ec8584034c8a7d087e
Upvotes: 0
Reputation: 11
I have solved this using decorators. I like the flexibility and explicitness you get from using a decorator.
Here is the meat of the code:
import ast
from fabric import utils
from fabric.api import task
from functools import wraps
def params(*types, **kwtypes):
def decorator(function):
@wraps(function)
def wrapper(*args, **kwargs):
new_args = ()
for index, arg in enumerate(args):
new_args += __cast(arg, types[index]),
for kwarg in kwargs:
kwargs[kwarg] = __cast(kwargs[kwarg], kwtypes[kwarg])
return function(*new_args, **kwargs)
return wrapper
return decorator
def __evaluate(arg):
try:
return ast.literal_eval(arg)
except:
return str(arg)
def __cast(arg, arg_type):
try:
return arg_type(__evaluate(arg))
except:
utils.abort("Unable to cast '{}' to {}".format(arg, arg_type))
Here is what it looks like to use it in code:
@task
@params(int, bool, arg1=int, arg2=bool)
def test(arg1, arg2):
print type(arg1), arg1
print type(arg2), arg2
Here is what it looks like to call it via fab with good params:
fab test:0.1,1
<type 'int'> 0
<type 'bool'> True
fab test:5,arg2=False
<type 'int'> 5
<type 'bool'> False
fab test:arg1=0,arg2=false
<type 'int'> 5
<type 'bool'> True
NOTE: In the last example, "false" is True, this is expected behavior in python, however it may be naturally counter intuitive. Similar with passing False as an int, it will cast to 0 as int(False) == 0 in python
Here is what it looks like to call it via fab with bad params:
fab test:Test,False
Fatal error: Unable to cast 'Test' to <type 'int'>
Aborting.
Upvotes: 1
Reputation: 1810
If you use the pattern consistently ('false','true' is boolean) on all your tasks, you can just wrap fabric task and apply it on all
You can use this package (written by me): https://pypi.python.org/pypi/boolfab/
Here is (essentially) the source:
from fabric.api import task as _task
def fix_boolean(f):
def fix_bool(value):
if isinstance(value, basestring):
if value.lower() == 'false':
return False
if value.lower() == 'true':
return True
return value
@wraps(f)
def wrapper(*args, **kwargs):
args_ = [fix_bool(arg) for arg in args]
kwargs_ = {k: fix_bool(v) for k,v in kwargs.iteritems()}
return f(*args_, **kwargs_)
return wrapper
def task(f):
return _task(fix_boolean(f))
So that it becomes:
@task
def my_task(flag_a, flag_b, flag_c)
if flag_a:
....
without polluting each task with 'booleanizing' args.
Upvotes: 5
Reputation: 799
I'm using this:
from distutils.util import strtobool
def func(arg1="default", arg2=False):
if arg2:
arg2 = bool(strtobool(arg2))
So far works for me. it will parse values (ignoring case):
'y', 'yes', 't', 'true', 'on', '1'
'n', 'no', 'f', 'false', 'off', '0'
strtobool returns 0 or 1 that's why bool is needed to convert to True/False boolean.
For completeness, here's strtobool
's implementation:
def strtobool (val):
"""Convert a string representation of truth to true (1) or false (0).
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
'val' is anything else.
"""
val = val.lower()
if val in ('y', 'yes', 't', 'true', 'on', '1'):
return 1
elif val in ('n', 'no', 'f', 'false', 'off', '0'):
return 0
else:
raise ValueError("invalid truth value %r" % (val,))
Slightly better version (thanks for comments mVChr)
from distutils.util import strtobool
def _prep_bool_arg(arg):
return bool(strtobool(str(arg)))
def func(arg1="default", arg2=False):
arg2 = _prep_bool_arg(arg2)
Upvotes: 40
Reputation: 6625
As mentioned in the fabric docs, all the arguments end up as strings. The simplest thing to do here would just check the argument:
def myfunc(arg1, arg2):
arg1 = (arg1 == 'True')
The parentheses aren't required, but help with readability.
Edit: Apparently I didn't actually try my previous answer; updated. (Two years later.)
Upvotes: -3
Reputation: 2771
In my fabfiles I just do:
TRUTHY = [True, 1, '1', 'true', 't', 'yes', 'y']
@task
def my_task(my_arg=True):
if my_arg in TRUTHY:
# do stuff
else:
# do other stuff
Of course, this means any value not in TRUTHY is effectively False
, but so far I haven't needed anything more complicated.
Upvotes: 1
Reputation: 841
A better way would be to use ast.literal_eval
:
from ast import literal_eval
def my_task(flag):
if isinstance(flag, basestring): # also supports calling from other tasks
flag = literal_eval(flag)
Although this doesn't take into consideration values like 'yes' or 'no', it is slightly cleaner and safer than eval
...
Upvotes: 3
Reputation: 26059
I would use a function:
def booleanize(value):
"""Return value as a boolean."""
true_values = ("yes", "true", "1")
false_values = ("no", "false", "0")
if isinstance(value, bool):
return value
if value.lower() in true_values:
return True
elif value.lower() in false_values:
return False
raise TypeError("Cannot booleanize ambiguous value '%s'" % value)
Then in the task:
@task
def mytask(arg):
arg = booleanize(arg)
Upvotes: 14
Reputation: 31
Craig and Ari's answers will result in a True value if the user passes "False" (Ari's answer is clearer about this)
If you use eval() the strings "True" and "False" will evaluate to their correct boolean values, but if you're using default values you'll need to make sure they're strings and not Booleans.
def myfunc(arg1="True", arg2=False):
arg1 = eval(arg1)
arg2 = eval(arg2) #error
Upvotes: 1
Reputation: 2378
If the func in question uses "if argN:" instead of "if argN is True:" to test if a boolean value is true, you could use "" for False and "anything" for True.
See also: http://docs.python.org/library/stdtypes.html#truth-value-testing
Upvotes: 5