From ebcba0a8c7c05057ad8bc2aa17a13559c080a9fe Mon Sep 17 00:00:00 2001 From: szsdk Date: Mon, 8 May 2023 08:51:52 +0200 Subject: [PATCH 1/6] Add new separator @ and ! --- fire/core.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/fire/core.py b/fire/core.py index c1e97367..5167d41f 100644 --- a/fire/core.py +++ b/fire/core.py @@ -458,6 +458,20 @@ def _Fire(component, args, parsed_flag_args, context, name=None): used_separator = True assert separator not in remaining_args + if len(remaining_args) > 0 and isinstance(remaining_args[0], str): + if remaining_args[0] == "@": + if "_" in remaining_args: + result_index = remaining_args.index("_") + remaining_args[result_index] = component + remaining_args.pop(0) + else: + remaining_args[0] = remaining_args[1] + remaining_args[1] = component + component = context.copy() + elif remaining_args[0] == "!": + remaining_args.pop(0) + component = context.copy() + handled = False candidate_errors = [] @@ -860,7 +874,7 @@ def _ParseKeywordArgs(args, fn_spec): skip_argument = False continue - if _IsFlag(argument): + if isinstance(argument, str) and _IsFlag(argument): # This is a named argument. We get its value from this arg or the next. # Terminology: @@ -974,6 +988,8 @@ def _ParseValue(value, index, arg, metadata): Returns: value, parsed into the appropriate type for calling a function. """ + if not isinstance(value, str): + return value parse_fn = parser.DefaultParseValue # We check to see if any parse function from the fn metadata applies here. From a41e8ab3574b080e3f52731636b802f8bf4b16ea Mon Sep 17 00:00:00 2001 From: Shen Zhou Date: Mon, 8 May 2023 10:48:54 +0200 Subject: [PATCH 2/6] add: @variable --- fire/core.py | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/fire/core.py b/fire/core.py index 5167d41f..c39567d2 100644 --- a/fire/core.py +++ b/fire/core.py @@ -434,6 +434,7 @@ def _Fire(component, args, parsed_flag_args, context, name=None): instance = None remaining_args = args + variable_env = dict() while True: last_component = component initial_args = remaining_args @@ -460,17 +461,15 @@ def _Fire(component, args, parsed_flag_args, context, name=None): if len(remaining_args) > 0 and isinstance(remaining_args[0], str): if remaining_args[0] == "@": - if "_" in remaining_args: - result_index = remaining_args.index("_") - remaining_args[result_index] = component - remaining_args.pop(0) - else: - remaining_args[0] = remaining_args[1] - remaining_args[1] = component + variable_env["_"] = component + remaining_args.pop(0) component = context.copy() - elif remaining_args[0] == "!": + component.update(variable_env) + elif remaining_args[0][0] == "@": + variable_env[remaining_args[0][1:]] = component remaining_args.pop(0) component = context.copy() + component.update(variable_env) handled = False candidate_errors = [] @@ -490,6 +489,7 @@ def _Fire(component, args, parsed_flag_args, context, name=None): component, remaining_args, component_trace, + variable_env, treatment='class' if is_class else 'routine', target=component.__name__) handled = True @@ -581,6 +581,7 @@ def _Fire(component, args, parsed_flag_args, context, name=None): component, remaining_args, component_trace, + variable_env, treatment='callable') handled = True except FireError as error: @@ -670,7 +671,7 @@ def _GetMember(component, args): raise FireError('Could not consume arg:', arg) -def _CallAndUpdateTrace(component, args, component_trace, treatment='class', +def _CallAndUpdateTrace(component, args, component_trace, variable_env, treatment='class', target=None): """Call the component by consuming args from args, and update the FireTrace. @@ -694,7 +695,7 @@ def _CallAndUpdateTrace(component, args, component_trace, treatment='class', filename, lineno = inspectutils.GetFileAndLine(component) metadata = decorators.GetMetadata(component) fn = component.__call__ if treatment == 'callable' else component - parse = _MakeParseFn(fn, metadata) + parse = _MakeParseFn(fn, metadata, variable_env) (varargs, kwargs), consumed_args, remaining_args, capacity = parse(args) # Call the function. @@ -717,7 +718,7 @@ def _CallAndUpdateTrace(component, args, component_trace, treatment='class', return component, remaining_args -def _MakeParseFn(fn, metadata): +def _MakeParseFn(fn, metadata, variable_env): """Creates a parse function for fn. Args: @@ -743,7 +744,7 @@ def _ParseFn(args): # Note: _ParseArgs modifies kwargs. parsed_args, kwargs, remaining_args, capacity = _ParseArgs( fn_spec.args, fn_spec.defaults, num_required_args, kwargs, - remaining_args, metadata) + remaining_args, metadata, variable_env) if fn_spec.varargs or fn_spec.varkw: # If we're allowed *varargs or **kwargs, there's always capacity. @@ -764,7 +765,7 @@ def _ParseFn(args): varargs = [] for index, value in enumerate(varargs): - varargs[index] = _ParseValue(value, None, None, metadata) + varargs[index] = _ParseValue(value, None, None, metadata, variable_env) varargs = parsed_args + varargs remaining_args += remaining_kwargs @@ -776,7 +777,7 @@ def _ParseFn(args): def _ParseArgs(fn_args, fn_defaults, num_required_args, kwargs, - remaining_args, metadata): + remaining_args, metadata, variable_env): """Parses the positional and named arguments from the available supplied args. Modifies kwargs, removing args as they are used. @@ -810,13 +811,13 @@ def _ParseArgs(fn_args, fn_defaults, num_required_args, kwargs, for index, arg in enumerate(fn_args): value = kwargs.pop(arg, None) if value is not None: # A value is specified at the command line. - value = _ParseValue(value, index, arg, metadata) + value = _ParseValue(value, index, arg, metadata, variable_env) parsed_args.append(value) else: # No value has been explicitly specified. if remaining_args and accepts_positional_args: # Use a positional arg. value = remaining_args.pop(0) - value = _ParseValue(value, index, arg, metadata) + value = _ParseValue(value, index, arg, metadata, variable_env) parsed_args.append(value) elif index < num_required_args: raise FireError( @@ -829,7 +830,7 @@ def _ParseArgs(fn_args, fn_defaults, num_required_args, kwargs, parsed_args.append(fn_defaults[default_index]) for key, value in kwargs.items(): - kwargs[key] = _ParseValue(value, None, key, metadata) + kwargs[key] = _ParseValue(value, None, key, metadata, variable_env) return parsed_args, kwargs, remaining_args, capacity @@ -975,7 +976,7 @@ def _IsMultiCharFlag(argument): return argument.startswith('--') or re.match('^-[a-zA-Z]', argument) -def _ParseValue(value, index, arg, metadata): +def _ParseValue(value, index, arg, metadata, variable_env): """Parses value, a string, into the appropriate type. The function used to parse value is determined by the remaining arguments. @@ -988,8 +989,8 @@ def _ParseValue(value, index, arg, metadata): Returns: value, parsed into the appropriate type for calling a function. """ - if not isinstance(value, str): - return value + if value in variable_env: + return variable_env[value] parse_fn = parser.DefaultParseValue # We check to see if any parse function from the fn metadata applies here. From 407cd75e771f71e14250853774e20b04cd879fa1 Mon Sep 17 00:00:00 2001 From: Shen Zhou Date: Wed, 10 May 2023 12:33:07 +0200 Subject: [PATCH 3/6] add: variable_env to interactive --- fire/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fire/core.py b/fire/core.py index c39567d2..802d6594 100644 --- a/fire/core.py +++ b/fire/core.py @@ -624,6 +624,7 @@ def _Fire(component, args, parsed_flag_args, context, name=None): if interactive: variables = context.copy() + variables.update(variable_env) if name is not None: variables[name] = initial_component From 88592b1c4504c490962b88b87c25b3427c627ce2 Mon Sep 17 00:00:00 2001 From: Shen Zhou Date: Thu, 11 May 2023 14:02:36 +0200 Subject: [PATCH 4/6] change: regard @ as separator --- fire/core.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/fire/core.py b/fire/core.py index 802d6594..c8089e56 100644 --- a/fire/core.py +++ b/fire/core.py @@ -457,17 +457,26 @@ def _Fire(component, args, parsed_flag_args, context, name=None): saved_args = remaining_args[separator_index + 1:] remaining_args = remaining_args[:separator_index] used_separator = True + else: + at_index = -1 + for ai, ra in enumerate(remaining_args): + if ra[0] == "@": + at_index = ai + break + if at_index != -1: + saved_args = remaining_args[at_index:] + remaining_args = remaining_args[:at_index] + used_separator = True assert separator not in remaining_args - - if len(remaining_args) > 0 and isinstance(remaining_args[0], str): - if remaining_args[0] == "@": + if len(remaining_args) == 0 and len(saved_args) > 0 and isinstance(saved_args[0], str): + if saved_args[0] == "@": variable_env["_"] = component - remaining_args.pop(0) + saved_args.pop(0) component = context.copy() component.update(variable_env) - elif remaining_args[0][0] == "@": - variable_env[remaining_args[0][1:]] = component - remaining_args.pop(0) + elif saved_args[0][0] == "@": + variable_env[saved_args[0][1:]] = component + saved_args.pop(0) component = context.copy() component.update(variable_env) From d144677bb076dfa7f68535aa1dedd0132e1d7baf Mon Sep 17 00:00:00 2001 From: Shen Zhou Date: Thu, 11 May 2023 14:33:16 +0200 Subject: [PATCH 5/6] add: context as arg --- fire/core.py | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/fire/core.py b/fire/core.py index c8089e56..db22ebb1 100644 --- a/fire/core.py +++ b/fire/core.py @@ -78,7 +78,7 @@ def main(argv): import asyncio # pylint: disable=import-error,g-import-not-at-top # pytype: disable=import-error -def Fire(component=None, command=None, name=None, serialize=None): +def Fire(component=None, command=None, name=None, serialize=None, context=None): """This function, Fire, is the main entrypoint for Python Fire. Executes a command either from the `command` argument or from sys.argv by @@ -128,8 +128,9 @@ def Fire(component=None, command=None, name=None, serialize=None): argparser = parser.CreateParser() parsed_flag_args, unused_args = argparser.parse_known_args(flag_args) - context = {} - if parsed_flag_args.interactive or component is None: + if context is None: + context = {} + if parsed_flag_args.interactive or not context: # Determine the calling context. caller = inspect.stack()[1] caller_frame = caller[0] @@ -451,22 +452,18 @@ def _Fire(component, args, parsed_flag_args, context, name=None): saved_args = [] used_separator = False - if separator in remaining_args: - # For the current component, only use arguments up to the separator. - separator_index = remaining_args.index(separator) - saved_args = remaining_args[separator_index + 1:] + separator_index = -1 + for ai, ra in enumerate(remaining_args): + if ra[0] == "@" or ra == "-": + separator_index = ai + break + if separator_index != -1: + if remaining_args[separator_index][0] == "@": + saved_args = remaining_args[separator_index:] + else: + saved_args = remaining_args[separator_index+1:] remaining_args = remaining_args[:separator_index] used_separator = True - else: - at_index = -1 - for ai, ra in enumerate(remaining_args): - if ra[0] == "@": - at_index = ai - break - if at_index != -1: - saved_args = remaining_args[at_index:] - remaining_args = remaining_args[:at_index] - used_separator = True assert separator not in remaining_args if len(remaining_args) == 0 and len(saved_args) > 0 and isinstance(saved_args[0], str): if saved_args[0] == "@": From fa10adb3f6e164979378a77c55005a6c1586c37a Mon Sep 17 00:00:00 2001 From: szsdk Date: Sun, 21 May 2023 08:52:52 +0200 Subject: [PATCH 6/6] change: make sure added components are not callable --- fire/core.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/fire/core.py b/fire/core.py index db22ebb1..0457e181 100644 --- a/fire/core.py +++ b/fire/core.py @@ -465,7 +465,15 @@ def _Fire(component, args, parsed_flag_args, context, name=None): remaining_args = remaining_args[:separator_index] used_separator = True assert separator not in remaining_args - if len(remaining_args) == 0 and len(saved_args) > 0 and isinstance(saved_args[0], str): + handled = False + candidate_errors = [] + + is_callable = inspect.isclass(component) or inspect.isroutine(component) + is_callable_object = callable(component) and not is_callable + is_sequence = isinstance(component, (list, tuple)) + is_map = isinstance(component, dict) or inspectutils.IsNamedTuple(component) + + if not (is_callable or is_callable_object) and len(remaining_args)==0 and len(saved_args) > 0 and isinstance(saved_args[0], str): if saved_args[0] == "@": variable_env["_"] = component saved_args.pop(0) @@ -477,13 +485,6 @@ def _Fire(component, args, parsed_flag_args, context, name=None): component = context.copy() component.update(variable_env) - handled = False - candidate_errors = [] - - is_callable = inspect.isclass(component) or inspect.isroutine(component) - is_callable_object = callable(component) and not is_callable - is_sequence = isinstance(component, (list, tuple)) - is_map = isinstance(component, dict) or inspectutils.IsNamedTuple(component) if not handled and is_callable: # The component is a class or a routine; we'll try to initialize it or