-
Notifications
You must be signed in to change notification settings - Fork 367
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
Solve intersection of CLI/signature default values vs config values #326
Comments
Hi @bitprophet thanks for all your work on the fine invoke library! :-) And sorry for the following lengthy post. I've run into this case and banged my head against it for a while now. My use case: I want to provide a project configuration As you describe, inside the task function body you currently cannot distinguish if
This basically only works for I've found a way to almost achieve what I want by
like so: import os.path
from invoke import Collection, Config, Task, task
# Our poor man's namespace for global names
class G:
project_dir = os.path.dirname(__file__)
# pre-read the project invoke.yaml to be able to use this for task default
# parameters and as a template for runtime configuration files
default_config = Config(project_location=project_dir)
default_config.load_project()
# a handier shortcut
defaults = default_config.hello
# Helper class to tunnel Default instances through the invoke machinery.
class DistinguishableDefaultsTask(Task):
def get_arguments(self):
# Py2 compat syntax
args = super(DistinguishableDefaultsTask, self).get_arguments()
for argument in args:
if isinstance(argument.default, Default):
# Trick invoke machinery into working everything out using the
# original type wrapped by a Default object
argument.kind = argument.default.kind
return args
class Default:
"""A wrapper object for task signature default arguments.
Its intention is to be able to decide wether a task function has been
invoked with the default argument or with a command line-provided argument.
"""
def __init__(self, value=None, kind=None):
self.value = value
self.kind = type(value) if kind is None else kind
def __call__(self):
return self.value
def __bool__(self):
return bool(self.value)
# Py2 compat
__nonzero__ = __bool__
# Better name? I like the shortness but _() is often used for other purposes...
def _(arg_value, ctx_value):
"""Return ctx_value if arg_value is a 'Default' object, arg_value
otherwise.
"""
if isinstance(arg_value, Default):
return ctx_value
return arg_value
@task(
auto_shortflags=False,
klass=DistinguishableDefaultsTask,
)
def hello(ctx,
greet=Default(G.defaults.tasks.hello.opts.greet),
greet_targets=Default(G.defaults.tasks.hello.opts.greet_targets),
):
"""Hello.
"""
if ctx.config.run.echo:
print(f'ctx={ctx}')
print(f'greet={greet}')
print(f'greet_targets={greet_targets}')
print(f'ctx.hello.tasks.hello.opts.greet='
f'{ctx.hello.tasks.hello.opts.greet}')
print(f'ctx.hello.tasks.hello.opts.greet_targets='
f'{ctx.hello.tasks.hello.opts.greet_targets}')
# get values from ctx if defaults
greet = _(greet, ctx.hello.tasks.hello.opts.greet)
greet_targets = _(greet_targets, ctx.hello.tasks.hello.opts.greet_targets)
if ctx.config.run.echo:
print(f'*** applied: greet={greet}')
print(f'*** applied: greet_targets={greet_targets}')
if greet:
ctx.run("echo 'Hello {}'".format(greet_targets))
ns = Collection(hello) With that, we can now override properly:
Apart from one little thing:
The intended
(tests run successfully with this change) I quite like the approach for my (limited) use case but can't judge if it could work on a broader scale. I do think however that a more general solution like changing the executor machinery to call the task function with some kind of "finalized" value (i.e. taken from the parameter default or from a config file (which path x.y.z of the config?) if given or from the command line if given is way more complex and probably an incompatible change. Any chance of the above minimal change to the "bool-arg-inversion" logic getting accepted? I'd provide a PR then... Of course, the original Best regards, |
A simple PR says more than a thousand words :-): #812 |
Ran into a corner case:
def mytask(foo=False):
myapp.foo
- to override that default value offoo
--foo
(to flip it to True) and no--no-foo
.myapp.foo = True
, is thus unable to setfoo
toFalse
at runtime via CLI flags.myapp.foo = True
(e.g. environment variables, or a runtime config file w/-f
) which setmyapp.foo = False
.--no-foo
and wondering why it wasn't working.There's also a more general case, which is simply:
kwarg=None
, then inside a function,kwarg = kwarg if kwarg is not None else <some-default-from-somewhere>
.The above could be its own ticket, but if implemented, it would also neatly solve the more corner-y case at top.
The text was updated successfully, but these errors were encountered: