Krzysieqq
Krzysieqq

Reputation: 1121

PyInvoke execution task from other python script

I'm reading pyinvoke docs and I'm searching any easy way to execute invoke tasks from other python script. Tasks don't have method run so I can't import them and simply .run(). I found that there is Executor Class but how I understand I need to first declare Collection of tasks and then I can run one of tasks from script. Maybe there is another way to do it easiest from other python script which isn't task?

Upvotes: 3

Views: 1119

Answers (2)

Joe Jasinski
Joe Jasinski

Reputation: 10801

The one downside of the existing answer is that it tasks that are invoked by calling them do not have their "pre" or "post" tasks called as well. If you want to do that, the link that @florisla posted has an example. Here's how I've gotten it to work:

Say you have some tasks defined in some "library tasks" module somewhere outside of your tasks.py that you want to include and call in your tasks.py via a task named taskC. In this example, taskB calls taskA as a pre task. So we want taskC to import and call taskB, and the "pre" definition of taskB should call taskA.

Here's what that "library tasks" module might look like:

# task_lib/core_tasks.py
from invoke import task

@task
def taskA(ctx):
    print("A")

@task(pre=[taskA])
def taskB(ctx):
    print("B")

Next, we define a module where we can place some boilerplate tasks code. That module will contain the definition for the discover_tasks function. This is from the github issue link.

# task_lib/common.py
from invoke import Collection
from invoke import task
from invoke.executor import Executor
from invoke.main import program

def discover_tasks(tasks):
    namespace = Collection(
        *tasks
    )

    def invoke_execute(context, command_name, **kwargs):
        results = Executor(namespace, config=context.config, core=program.core).execute((command_name, kwargs))
        target_task = context.root_namespace[command_name]
        return results[target_task]

    namespace.configure({
        'root_namespace': namespace,
        'invoke_execute': invoke_execute,
    })
    return namespace

Finally, in our tasks.py, we put everything together.

# tasks.py 
from invoke import task
from task_lib.core_tasks import taskB
from task_lib.common import discover_tasks


@task
def taskC(ctx):
    ctx.invoke_execute(ctx, 'taskB')
    print("C")

# this function call must inform invoke about "ALL" the tasks that
# should be exposed in tasks.py (i.e. everything that will show with 'inv --list'),
# AND any imported tasks
namespace = discover_tasks(tasks=[
    taskB,
    taskC,
])

Now, when you invoke taskC and it calls taskB, then taskB will also call its pre-task taskA.

$ inv taskC
A
B
C

It's hacky solution, but it works.

If you DON'T use this boilerplate, and define the tasks.py as follows...

from invoke import task
from task_lib.core_tasks import taskB

@task
def taskC(ctx):
    taskB(ctx)
    print("C")

... then taskB does NOT call its dependencies.

$ inv taskC
B
C

Upvotes: 1

Fhope Cc
Fhope Cc

Reputation: 45

The point is "task is also a python function", so you can invoke task by calling task function as following:

Define a task function test in a.py.

#a.py
from invoke import task
@task
def test(c):
    print("hello, I'm a-test!")

Import module a and execute task by calling a.test() function.

#tasks.py
from invoke import task
import a
@task
def executeatest(c):
    print('executing [a-test] task defined in "a.py"')
    a.test(c)

When you execute inv executeatest, it results:

executing [a-test] task defined in "a.py"
hello, I'm a-test!

Upvotes: 1

Related Questions