State handling: the context¶
A common problem task runners face is transmission or storage of values which are “global” for the current session - values loaded from configuration files (or other configuration vectors), CLI flags, values set by ‘setup’ tasks, etc.
Some Python libraries (such as Fabric 1.x) implement this via global module state. That approach works in the base case but makes testing difficult and error prone, limits concurrency, and makes the software more complex to use and extend.
Invoke encapsulates its state in an explicit Context
object, handed to
tasks when they execute or instantiated and used by hand. The context is the
primary API endpoint, offering methods which honor the current state (such as
Context.run
) as well as access to that state itself.
Using contexts in your tasks¶
To use Invoke’s context-aware API, make the following changes to the task definition style seen in the tutorial:
Tell
@task
that you want your task to be contextualized - given aContext
object - by sayingcontextualized=True
.Note
See Boilerplate reduction below; this API is mostly for cleanness’ sake.
Define your task with an initial context argument; this argument is ignored during command-line parsing and is solely for context handling.
- You can name it anything you want; Invoke uses it positionally, not via
keyword. The convention used in the documentation is typically
context
orctx
.
- You can name it anything you want; Invoke uses it positionally, not via
keyword. The convention used in the documentation is typically
Replace any mentions of
run
withctx.run
(or whatever your context argument’s name was).
Here’s a simple example:
from invoke import task
@task(contextualized=True)
def restart(ctx):
ctx.run("restart apache2")
We’re using slightly more boilerplate than before (though see below), but your
ctx.run
calls will now honor command-line flags, config files and so forth.
Boilerplate reduction¶
Specifying contextualized=True
for every task in your collection would get
old fast. Invoke offers a convenience API call, @ctask
, which is
effectively @task
with contextualized
set to True
by default.
A common convention is to import it “as” task
so things still look neat and
tidy:
from invoke import ctask as task
@task
def restart(ctx):
ctx.run("restart apache2")