Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global configuration for e.g. auto positionals / shortflags #27

Closed
bitprophet opened this issue Oct 19, 2012 · 8 comments
Closed

Global configuration for e.g. auto positionals / shortflags #27

bitprophet opened this issue Oct 19, 2012 · 8 comments

Comments

@bitprophet
Copy link
Member

Would be nice to globally/temporarily set certain default-but-configurable behaviors to specific values.

E.g. in our own test suite, when #23 landed I had to add a bunch of positional=[] args to @task. Would be nice to be able to set that at the test suite level or something.

Main hurdle is simply that we want to do away with module level bullshit so this needs to be effected some other way; possibly nothing we support ourselves besides "well, just do this:"

from functools import partial
from invoke.task import task

task = partial(task, positional=[])

In which case this would just become a "apply that technique to our own tests."

@bitprophet
Copy link
Member Author

After thinking a bit I only see the following options:

  • Use partial as in description, either per-file (which could get annoying for folks with nontrivial task modules) or some sort of silly at-runtime mutation of the "real" callables (really don't want to do this and it didn't even work when I tested it).
  • Use a Runner class that can be instantiated with config options, then e.g. runner.run(xxx).
    • Probably the cleanest approach
    • Makes the "DSL" more verbose, requires both setup/pass-around (of instance) boilerplate, plus just a longer call
    • May pave the way for nigh inevitable Fabric API of e.g. Host().run("remote cmd")
  • Suck it up, use threadlocal globals or whatever.

Still need to research state of the art tho. But suspect the class is the way to go. Bye bye cutesy DSL.

@bitprophet
Copy link
Member Author

Hadn't been considering the specifics of how the config data gets into Runner and friends, but:

  • Something needs to load the conf file from disk
    • And merge it with CLI options, if invoked by CLI
  • This loaded result has to be stored somewhere
  • Then referenced anytime one creates one of these classes

My suspicion is that in the CLI driven use case, we would build the config, then pass it into each task when the task is executed (thus requiring tasks to always have some initial argument, at which point it's effectively just like a method on a class where that argument is self).

In library use users would be responsible for loading their own config and then doing the same handoff to tasks whenever they get executed.

@bitprophet
Copy link
Member Author

Kenneth mentioned how Requests has a similar setup, albeit via a dual API:

  • get()
  • Session(defaults).get()

This is a lot like what I had in mind originally for the Fabric2 level stuff, except the "unqualified" API would still be able to refer to a threadlocal or similar (probably not by default tho) whereas in Requests it's largely about ability to set defaults.

Which, honestly, is kind of the same here; we could say "if you want the ability to globally tickle defaults, you must call methods on an instantiated class. Users with simple needs can still use the API calls directly if they prefer it, users who want to "hook in" can call the method version.

@bitprophet
Copy link
Member Author

MOAR COMMENTZ

Think the way to go is really what I had in mind before:

  • Core API is method calls on objects, so for the run-echo use case, maybe runner = Runner(config); runner.run() as above.
    • Figure out what sort of class/entity name would be extensible for Fab stuff too, i.e. what object like this would Host or HostCollection inherit from? Use that name.
  • Secondary API is module-level, e.g. runner.run / invoke.run, has default-None context/options kwarg.
    • If context object is None, refer to threadlocal context obj
    • Can optionally pass in a context object directly
    • That object's run method is invoked with our *args/**kwargs`

@bitprophet
Copy link
Member Author

Thought about this a bunch more recently and started documenting it too.

The tl;dr is that it should look like this:

  • Lowest level and "default" (re: trivial base case) API is wholly functional and simple and totally state-unaware. E.g. the current implementation of run. It offers kwargs controlling all aspects of behavior.
  • To obtain a "modified" run call that can honor configuration state (such as whether to echo commands executed, see Optionally echo commands executed #32), call a run method on a settings or context object.
  • Such methods would understand their own, internal state (obtained from the CLI, conf files, or user manipulation) and use that as a guide re: default kwarg values, e.g.:
    • run(cmd): no echo
    • run(cmd, echo=True): echo
    • Context().run(cmd): no echo
    • Context().run(cmd, echo=True): echo
    • Context(echo=True).run(cmd): echo
  • Tasks can opt to be handed a Context object as their 1st argument (which is not taken into account when determining CLI flags) and the CLI module will create and hand one in.
    • This is the key: CLI sees echo flag, creates core Context for statekeeping, with echo on. That Context is what is passed into every "contextualized" task, they all use ctx.run() instead of run(), and presto, echoing occurs.
  • Specific API for how Context holds state related to run and any other methods that pop up, is TBD.
  • Should account for config files too -- some sort of override hierarchy such that it probably boils down to a single namespace
    • Though preserving the various sources for posterity.
  • One question is whether Contexts should be allowed to mutate (thus allowing one task to alter it, then pass the altered copy into another task it calls internally) or if we should try to be functional-like and prefer it be cloned instead.

@bitprophet
Copy link
Member Author

Have context object being handed around successfully now, omitted from arg parsing, etc etc.

It's an empty object right now, but I will flesh it out as I implement #32. Leaving this open so I ensure it applies to auto-positional + auto-shortflag as subject suggests, once I am done.

@bitprophet
Copy link
Member Author

#32 implemented, also the rest of the run related arguments. Hooray.

Right now the context object's inner API is very simply, just a .config attribute dict which holds a run subdict whose key/val pairs map to run() kwargs.

It suffices to enable CLI-flag-driven config, but should get cleaned up for code-level config, and documented.

@bitprophet
Copy link
Member Author

Documented things a bit better, not bothering with anything deeper than the __init__ API (it's not like giving Context(run={'default': 'kwargs'}) is that ugly an API) for the time being.

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

No branches or pull requests

1 participant