summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArmin Ronacher <armin.ronacher@active-4.com>2014-05-06 13:44:42 +0200
committerArmin Ronacher <armin.ronacher@active-4.com>2014-05-06 13:44:42 +0200
commite77783dcd9b9b74f0f53c3b5857407cef07fba87 (patch)
tree8ab1a3c8cced1e842581eb409fbe50ea7409a7e6
parent065ce06128f678e91ba0022ce565b9ee52c5adf5 (diff)
downloadclick-e77783dcd9b9b74f0f53c3b5857407cef07fba87.tar.gz
Added support for default overrides. This fixes #42
-rw-r--r--click/core.py23
-rw-r--r--docs/clickdoctools.py5
-rw-r--r--docs/commands.rst47
-rw-r--r--tests/conftest.py5
-rw-r--r--tests/test_commands.py18
5 files changed, 92 insertions, 6 deletions
diff --git a/click/core.py b/click/core.py
index 3d1238b..1cc1898 100644
--- a/click/core.py
+++ b/click/core.py
@@ -47,10 +47,12 @@ class Context(object):
from environment variables is disabled. This
does not affect manually set environment
variables which are always read.
+ :param default_map: a dictionary (like object) with default values
+ for parameters.
"""
def __init__(self, command, parent=None, info_name=None, obj=None,
- auto_envvar_prefix=None):
+ auto_envvar_prefix=None, default_map=None):
#: the parent context or `None` if none exists.
self.parent = parent
#: the :class:`Command` for this context.
@@ -72,6 +74,8 @@ class Context(object):
obj = parent.obj
#: the user object stored.
self.obj = obj
+ #: A dictionary (like object) with defaults for parameters.
+ self.default_map = default_map
# If there is no envvar prefix yet, but the parent has one and
# the command on this level has a name, we can expand the envvar
@@ -149,6 +153,16 @@ class Context(object):
self.obj = rv = object_type()
return rv
+ def lookup_default(self, name):
+ """Looks up the default for a parameter name. This by default
+ looks into the :attr:`default_map` if available.
+ """
+ if self.default_map is not None:
+ rv = self.default_map.get(name)
+ if callable(rv):
+ rv = rv()
+ return rv
+
def fail(self, message):
"""Aborts the execution of the program with a specific error
message.
@@ -362,6 +376,11 @@ class Command(object):
:param extra: extra keyword arguments forwarded to the context
constructor.
"""
+ if 'default_map' not in extra:
+ default_map = None
+ if parent is not None and parent.default_map is not None:
+ default_map = parent.default_map.get(info_name)
+ extra['default_map'] = default_map
ctx = Context(self, info_name=info_name, parent=parent, **extra)
parser = self.make_parser(ctx)
opts, args = parser.parse_args(args=args)
@@ -702,6 +721,8 @@ class Parameter(object):
def consume_value(self, ctx, opts):
value = opts.get(self.name)
if value is None:
+ value = ctx.lookup_default(self.name)
+ if value is None:
value = self.value_from_envvar(ctx)
return value
diff --git a/docs/clickdoctools.py b/docs/clickdoctools.py
index 11488f8..b46841e 100644
--- a/docs/clickdoctools.py
+++ b/docs/clickdoctools.py
@@ -132,7 +132,7 @@ class ExampleRunner(object):
def invoke(cmd, args=None, prog_name=None, prog_prefix='python ',
input=None, terminate_input=False, env=None,
- auto_envvar_prefix=None):
+ **extra):
if env:
for key, value in sorted(env.items()):
if ' ' in value:
@@ -152,8 +152,7 @@ class ExampleRunner(object):
input += '\xff'
with isolation(input=input, env=env) as output:
try:
- cmd.main(args=args, prog_name=prog_name,
- auto_envvar_prefix=auto_envvar_prefix)
+ cmd.main(args=args, prog_name=prog_name, **extra)
except SystemExit:
pass
buffer.extend(output.getvalue().splitlines())
diff --git a/docs/commands.rst b/docs/commands.rst
index 38e37b1..e4a12cd 100644
--- a/docs/commands.rst
+++ b/docs/commands.rst
@@ -254,3 +254,50 @@ And what it looks like:
invoke(cli, prog_name='cli', args=['--help'])
In case a command exists on more than one source, the first source wins.
+
+Overriding Defaults
+-------------------
+
+By default the default value for a parameter is pulled from the
+``default`` flag that is provided when it's defined. But that's not the
+only place defaults can be loaded from. The other place is the
+:attr:`Context.default_map` (a dictionary) on the context. This allows
+defaults to be loaded from a config file to override the regular defaults.
+
+This is useful if you plug in some commands from another package but
+you're not satisfied with the defaults.
+
+The default map can be nested arbitrarily for each subcommand and be
+provided when the script is invoked. Alternatively it can also be
+overriden at any point by commands. For instance a toplevel command could
+load the defaults from a config file.
+
+Example usage:
+
+.. click:example::
+
+ import click
+
+ @click.group()
+ def cli():
+ pass
+
+ @cli.command()
+ @click.option('--port', default=8000)
+ def runserver(port):
+ click.echo('Serving on http://127.0.0.1:%d/' % port)
+
+ if __name__ == '__main__':
+ cli(default_map={
+ 'runserver': {
+ 'port': 5000
+ }
+ })
+
+.. click:run::
+
+ invoke(cli, prog_name='cli', args=['runserver'], default_map={
+ 'runserver': {
+ 'port': 5000
+ }
+ })
diff --git a/tests/conftest.py b/tests/conftest.py
index 1d2e0b9..c46b982 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -118,13 +118,14 @@ class CliRunner(object):
click.termui.visible_prompt_func = old_visible_prompt_func
click.termui.hidden_prompt_func = old_hidden_prompt_func
- def invoke(self, cli, args):
+ def invoke(self, cli, args, **extra):
with self.isolation() as out:
exception = None
exit_code = 0
try:
- cli.main(args=args, prog_name=cli.name or 'root')
+ cli.main(args=args, prog_name=cli.name or 'root',
+ **extra)
except SystemExit as e:
if e.code != 0:
exception = e
diff --git a/tests/test_commands.py b/tests/test_commands.py
index 03a4e10..9194991 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -38,3 +38,21 @@ def test_auto_shorthelp(runner):
r'Commands:\n\s+'
r'long\s+This is a long text that is too long to show\.\.\.\n\s+'
r'short\s+This is a short text\.', result.output) is not None
+
+
+def test_default_maps(runner):
+ @click.group()
+ def cli():
+ pass
+
+ @cli.command()
+ @click.option('--name', default='normal')
+ def foo(name):
+ click.echo(name)
+
+ result = runner.invoke(cli, ['foo'], default_map={
+ 'foo': {'name': 'changed'}
+ })
+
+ assert not result.exception
+ assert result.output == 'changed\n'