From ab6b3110377077e96a150fd563f6f064e56ebbd8 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Thu, 21 Aug 2014 18:54:10 +0200 Subject: [PATCH] Use different method for propagating ctx.obj d88d0a73eb97abcd11fa5bcffee23f1980acfb75 introduced #205. Fix #205 Fix #200 --- click/core.py | 48 +++++++++++++++++++++++++++--------------- tests/test_commands.py | 23 ++++++++++++++++++++ 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/click/core.py b/click/core.py index cb2b8cd15..20a8280d4 100644 --- a/click/core.py +++ b/click/core.py @@ -162,8 +162,10 @@ def __init__(self, command, parent=None, info_name=None, obj=None, self.params = {} #: the leftover arguments. self.args = [] + #: subcontexts whose objects need to be updated. + self.subcontexts = [] if obj is None and parent is not None: - obj = parent.obj + parent.subcontexts.append(self) #: the user object stored. self.obj = obj #: A dictionary (-like object) with defaults for parameters. @@ -239,6 +241,18 @@ def __init__(self, command, parent=None, info_name=None, obj=None, self._close_callbacks = [] self._depth = 0 + _obj = None + + def _get_obj(self): + return self._obj + def _set_obj(self, obj): + self._obj = obj + for ctx in self.subcontexts: + if ctx.obj is None: + ctx.obj = obj + obj = property(_get_obj, _set_obj) + del _get_obj, _set_obj + def __enter__(self): self._depth += 1 return self @@ -925,33 +939,33 @@ def _process_result(value): # single command but we also inform the current context about the # name of the command to invoke. if not self.chain: + sub_ctx = self.handle_subcommand(ctx, args) + ctx.invoked_subcommands = [sub_ctx.info_name] + # Make sure the context is entered so we do not clean up # resources until the result processor has worked. with ctx: Command.invoke(self, ctx) - sub_ctx = self.handle_subcommand(ctx, args) - ctx.invoked_subcommands = [sub_ctx.info_name] with sub_ctx: return _process_result(sub_ctx.command.invoke(sub_ctx)) + # Otherwise we make every single context and invoke them in a + # chain. In that case the return value to the result processor + # is the list of all invoked subcommand's results. + contexts = [] + while args: + sub_ctx = self.handle_subcommand(ctx, args, allow_extra_args=True, + allow_interspersed_args=False) + contexts.append(sub_ctx) + args = sub_ctx.args + + # Now that we have all contexts, we can invoke them. + ctx.invoked_subcommands = [x.info_name for x in contexts] + # Make sure the context is entered so we do not clean up # resources until the result processor has worked. with ctx: Command.invoke(self, ctx) - - # Otherwise we make every single context and invoke them in a - # chain. In that case the return value to the result processor - # is the list of all invoked subcommand's results. - contexts = [] - while args: - sub_ctx = self.handle_subcommand(ctx, args, allow_extra_args=True, - allow_interspersed_args=False) - contexts.append(sub_ctx) - args = sub_ctx.args - - # Now that we have all contexts, we can invoke them. - ctx.invoked_subcommands = [x.info_name for x in contexts] - rv = [] for sub_ctx in contexts: with sub_ctx: diff --git a/tests/test_commands.py b/tests/test_commands.py index 24118ed1e..7c8b453d6 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -229,3 +229,26 @@ def other_cmd(ctx, foo): result = runner.invoke(cli, []) assert not result.exception assert result.output == '42\n' + + +def test_invoked_subcommand(runner): + @click.group(invoke_without_command=True) + @click.pass_context + def cli(ctx): + if ctx.invoked_subcommand is None: + click.echo('no subcommand, use default') + ctx.invoke(sync) + else: + click.echo('invoke subcommand') + + @cli.command() + def sync(): + click.echo('in subcommand') + + result = runner.invoke(cli, ['sync']) + assert not result.exception + assert result.output == 'invoke subcommand\nin subcommand\n' + + result = runner.invoke(cli) + assert not result.exception + assert result.output == 'no subcommand, use default\nin subcommand\n'