Invoke: @task doesn't play nice with other decorators

Created on 13 Jul 2018  路  5Comments  路  Source: pyinvoke/invoke

The @task decorator doesn't appear to play nicely with other decorators, unlike Fabric. I may not have read this part of the documentation yet, but I wouldn't expect @task to undermine python norms.

For example:

def capture_error(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        try:
            return fn(*args, **kwargs)
        except AssertionError as err:
            print(err)
    return wrapper

@task
@capture_error
def foo(c):
    print('bar')
    raise AssertionError('baz')

this looks fine:

inv foo
>>> bar
>>> baz

but when I add a parameter:

@task
@capture_error
def foo(c, p1):
    print(p1)
    raise AssertionError('baz')

I get:

inv foo bar
>>> No idea what 'bar' is!

The parameter handling docs offer no insight into what the problem is.

Bug Needs investigation Needs patch Task organization Task signatures

All 5 comments

This is likely an oversight / lack of porting those extra careful parts of Fabric 1's decorator. #246 seems related and may fix it (though it's now kinda old so may need a revisit). Gonna leave this open and in a milestone since it's a regression (which is arguably worse than features which just aren't ported yet). Thanks!

hey, thanks for your attention on this. I've been using Fabric for so many years now I'm trialing Invoke on a personal project to unblock some work projects that need the Python3 upgrade as it doesn't look like Fabric will get there, which is frustrating, but I do appreciate your efforts and those of the other contributors.

@lsh-0 FWIW, Fabric 2 is technically out now (though it needs a few more feature releases to get up to par with 1.x) and supports Python 3. But it's also built heavily on top of Invoke so working with plain Invoke is a fine place to start; can always use fabric 2's API directly or switch later.

For future reference, I found a workaround:

def decorator(fn):
    @functools.wraps(fn)
    def _decorated(ctx, *args, **kwargs):
        return fn(ctx, *args, **kwargs)
    return _decorated

@invoke.task
def some_task(ctx, config="Something"):
    @decorator
    def _inner(ctx):
        print(config)
        ...

    return _inner(ctx)

This allows invoke.task to find the task and inspect the args

After finding your answer on stackoverflow I also found it here!

I was able to solve my problem by using the @functools.wraps decorator as well :

old decorator:

def ask(task_to_execute):
    """Decorator to ask if the user really wants to perform a task.
    """

    def wrapper(*args, **kwargs):

        y = ["y", "yes", "o", "oui", "Yes", "Oui", "go"]
        if input("Do you want to execute " + task_to_execute.__name__ + "? [y/d/N] ") in y:
            task_to_execute(*args, **kwargs)

    return wrapper

new decorator:

from functools import wraps

def ask(task_to_execute):
    """Decorator to ask if the user really wants to perform a task.
    """

    @wraps(task_to_execute)
    def wrapped_wrapper(*args, **kwargs):

        y = ["y", "yes", "o", "oui", "Yes", "Oui", "go"]
        if input("Do you want to execute " + task_to_execute.__name__ + "? [y/d/N] ") in y:
            task_to_execute(*args, **kwargs)

    return wrapped_wrapper

And so tab-completion works again :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

zaiste picture zaiste  路  9Comments

lukeorland picture lukeorland  路  7Comments

PatrickMassot picture PatrickMassot  路  6Comments

frol picture frol  路  13Comments

cyounkins picture cyounkins  路  6Comments