whi
whi

Reputation: 2750

How to pass bool argument to fabric command

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

Answers (10)

panni
panni

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

unscannable
unscannable

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

j-a
j-a

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

Engrost
Engrost

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

Craig Citro
Craig Citro

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

katy lavallee
katy lavallee

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

vmalloc
vmalloc

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

charlax
charlax

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

AnalogBrain
AnalogBrain

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

Ari
Ari

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

Related Questions