Consider making contextualization the default #114

bitprophet opened this Issue Feb 11, 2014 · 11 comments


None yet

7 participants

bitprophet commented Feb 11, 2014 edited

Occasionally catch myself doing the following:

from invoke import Collection, task

# stuff

def foo(ctx, blah, otherblah):"whatever")

ns = Collection(foo)

Which leads to a nice 'test' did not receive all required positional arguments! error, because task != ctask. Also I think part of an FAQ mentions this (!)

If it's something I do myself cuz I haven't written a new Invoke task in a's gonna bite users. The un-contextualized use case is and should be the minority; therefore I really should:

  • Update docs to describe a "contextualized by default" setup, including removing mention of @ctask.
    • Maybe be hardcore and just don't even bother with uncontextualized tasks? Eh. Leaving the flag in allows people to rebind the name to an inverted-ctask if they really care about not using the context.
  • Update tests to match
  • Add new tests enforcing a useful "Did you forget a leading context argument?" add-on to the above error message, probably.
  • Implement.
  • Update all my stuff (whee! Good thing most of it is in Invocations...)
  • Add big honkin' backwards incompatibility note to teh changelog (maybe a good new Releases feature?)
@bitprophet bitprophet added the Feature label Aug 25, 2014

Actually thinking this pairs well with #147 because I'm having a heck of a time figuring out how to recast the contexts doc page in light of it not being the main config vector now. Part of this problem is because the entire doc is structured as a "here's why you want to use contexts" and if it's going to need a restructure anyway this might be a good time.

OTOH I've already spent so long on #147...heh.

EDIT: Gonna punt on that for now but this is still something I ought to do soonish :)

@bitprophet bitprophet modified the milestone: 1.0 Sep 7, 2015

Working on removing non-contextualized tasks

thebjorn commented Jun 4, 2016

Perhaps a stupid question...? but if all tasks need a first argument of a specific type, and it's a source of error that users forget this argument.. Wouldn't it be sensible to define a task as a subclass of Task, since then the first argument would be self which when missing is much more likely to be picked up by linters/IDEs? I know you can define a class as a task already (e.g. and even though it works well it feels a little hacky..

bitprophet commented Jun 4, 2016 edited

@thebjorn Not a stupid question! It's true that making them class definitions would make that line up a bit better. The problem is, this type of tool wants to be as DSL-like & lightweight as possible, so any additional boilerplate drives it farther from that goal.

The use of a context object is already a moderate boilerplate addition (one I judged worthwhile because of the large gains in explicitness, architectural sanity & debug friendliness). Adding class-def lines & extra indent levels strikes me as not quite worth the tradeoff.

Relatedly, though - I realized the other day that our problem is literally the same problem as method self args - including the fact that you get a newbie-unfriendly error if you forget self. Updating the docs to highlight this fact - hoping that users are already used to writing classes and having that mental model - might be useful.

Finally, having methods-as-tasks is something we want to have as an optional feature - see #347. I just don't think that should be the default mode.


Props to @KuangEleven , this is in now. Bring on the flames! 😒

@bitprophet bitprophet closed this Jun 4, 2016
classam commented Jun 21, 2016 edited

Bring on the flames!

... I mean, I understand the dependency injection value of doing this, but UAAUGH. Not only do I have to change every codebase of mine that uses invoke as a simple task runner, but I also have to change an article I wrote about it.


... thanks for all of your dedicated work on invoke.


The default context variable breaks Invoke on Windows when you go to run shell commands. See #371 for details.

indera commented Jun 30, 2016 edited

I respect the @bitprophet's decision to not implement this, but I expect that this issue will be fixed in the future...

Maybe by a dedicated decorator -- to avoid passing the ctx, or by sub-classing as suggested by @thebjorn

def normal_task_not_requiring_run():

def task_invoking_shell_commands():

Also it would be interesting to know how many users decided to try invoke just because it allows to invoke shell commands :)

code-tree commented Jul 1, 2016 edited

I assume the reason for this change is a good one, but practically speaking, what is the best practice for converting this old code to the new standard?

def do_in_source_dir(cmd):
    """ Do command within source dir """
    with change_to_source_dir():

def gitk():
    """ Open gitk within source dir """
    do_in_source_dir('gitk --all')

I have a great number of calls to tasks within other tasks, as they need to pass on the arg they receive to the other task (not merely run before/after. Any ideas on how to refactor this?


indera commented Jul 1, 2016

Tedious to refactor but passing "ctx" around would do it:

from invoke import task

def do_in_source_dir(ctx, cmd):
    """ Do command within source dir """

def gitk(ctx):
    """ Open gitk within source dir """
    do_in_source_dir(ctx, 'ls -al')

@code-tree The quick way for now is to pass the initial context around as noted by @indera. Once #170 is solved, that'll be the "right" way, and ideally it'll be something like'other-task') (replacing literal other_task() calls as you do now).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment