def testInteractVariables(self, mock_interact_method): self.assertFalse(mock_interact_method.called) interact.Embed({ 'count': 10, 'mock': mock, }) self.assertTrue(mock_interact_method.called)
def testInteract(self, mock_interact_method): self.assertFalse(mock_interact_method.called) interact.Embed({}) self.assertTrue(mock_interact_method.called)
def _Fire(component, args, parsed_flag_args, context, name=None): """Execute a Fire command on a target component using the args supplied. Arguments that come after a final isolated '--' are treated as Flags, eg for interactive mode or completion script generation. Other arguments are consumed by the execution of the Fire command, eg in the traversal of the members of the component, or in calling a function or instantiating a class found during the traversal. The steps performed by this method are: 1. Parse any Flag args (the args after the final --) 2. Start with component as the current component. 2a. If the current component is a class, instantiate it using args from args. 2b. If the component is a routine, call it using args from args. 2c. If the component is a sequence, index into it using an arg from args. 2d. If possible, access a member from the component using an arg from args. 2e. If the component is a callable object, call it using args from args. 2f. Repeat 2a-2e until no args remain. Note: Only the first applicable rule from 2a-2e is applied in each iteration. After each iteration of step 2a-2e, the current component is updated to be the result of the applied rule. 3a. Embed into ipython REPL if interactive mode is selected. 3b. Generate a completion script if that flag is provided. In step 2, arguments will only ever be consumed up to a separator; a single step will never consume arguments from both sides of a separator. The separator defaults to a hyphen (-), and can be overwritten with the --separator Fire argument. Args: component: The target component for Fire. args: A list of args to consume in Firing on the component, usually from the command line. parsed_flag_args: The values of the flag args (e.g. --verbose, --separator) that are part of every Fire CLI. context: A dict with the local and global variables available at the call to Fire. name: Optional. The name of the command. Used in interactive mode and in the tab completion script. Returns: FireTrace of components starting with component, tracing Fire's execution path as it consumes args. Raises: ValueError: If there are arguments that cannot be consumed. ValueError: If --completion is specified but no name available. """ verbose = parsed_flag_args.verbose interactive = parsed_flag_args.interactive separator = parsed_flag_args.separator show_completion = parsed_flag_args.completion show_help = parsed_flag_args.help show_trace = parsed_flag_args.trace # component can be a module, class, routine, object, etc. if component is None: component = context initial_component = component component_trace = trace.FireTrace(initial_component=initial_component, name=name, separator=separator, verbose=verbose, show_help=show_help, show_trace=show_trace) instance = None remaining_args = args while True: last_component = component initial_args = remaining_args if not remaining_args and (show_help or interactive or show_trace or show_completion is not None): # Don't initialize the final class or call the final function unless # there's a separator after it, and instead process the current component. break if _IsHelpShortcut(component_trace, remaining_args): remaining_args = [] break 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:] remaining_args = remaining_args[:separator_index] used_separator = True assert separator not in remaining_args 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 # call it. is_class = inspect.isclass(component) try: component, remaining_args = _CallAndUpdateTrace( component, remaining_args, component_trace, treatment='class' if is_class else 'routine', target=component.__name__) handled = True except FireError as error: candidate_errors.append((error, initial_args)) if handled and last_component is initial_component: # If the initial component is a class, keep an instance for use with -i. instance = component if not handled and is_sequence and remaining_args: # The component is a tuple or list; we'll try to access a member. arg = remaining_args[0] try: index = int(arg) component = component[index] handled = True except (ValueError, IndexError): error = FireError( 'Unable to index into component with argument:', arg) candidate_errors.append((error, initial_args)) if handled: remaining_args = remaining_args[1:] filename = None lineno = None component_trace.AddAccessedProperty(component, index, [arg], filename, lineno) if not handled and is_map and remaining_args: # The component is a dict or other key-value map; try to access a member. target = remaining_args[0] # Treat namedtuples as dicts when handling them as a map. if inspectutils.IsNamedTuple(component): component_dict = component._asdict() # pytype: disable=attribute-error else: component_dict = component if target in component_dict: component = component_dict[target] handled = True elif target.replace('-', '_') in component_dict: component = component_dict[target.replace('-', '_')] handled = True else: # The target isn't present in the dict as a string key, but maybe it is # a key as another type. # TODO(dbieber): Consider alternatives for accessing non-string keys. for key, value in component_dict.items(): if target == str(key): component = value handled = True break if handled: remaining_args = remaining_args[1:] filename = None lineno = None component_trace.AddAccessedProperty(component, target, [target], filename, lineno) else: error = FireError('Cannot find key:', target) candidate_errors.append((error, initial_args)) if not handled and remaining_args: # Object handler. We'll try to access a member of the component. try: target = remaining_args[0] component, consumed_args, remaining_args = _GetMember( component, remaining_args) handled = True filename, lineno = inspectutils.GetFileAndLine(component) component_trace.AddAccessedProperty(component, target, consumed_args, filename, lineno) except FireError as error: # Couldn't access member. candidate_errors.append((error, initial_args)) if not handled and is_callable_object: # The component is a callable object; we'll try to call it. try: component, remaining_args = _CallAndUpdateTrace( component, remaining_args, component_trace, treatment='callable') handled = True except FireError as error: candidate_errors.append((error, initial_args)) if not handled and candidate_errors: error, initial_args = candidate_errors[0] component_trace.AddError(error, initial_args) return component_trace if used_separator: # Add back in the arguments from after the separator. if remaining_args: remaining_args = remaining_args + [separator] + saved_args elif (inspect.isclass(last_component) or inspect.isroutine(last_component)): remaining_args = saved_args component_trace.AddSeparator() elif component is not last_component: remaining_args = [separator] + saved_args else: # It was an unnecessary separator. remaining_args = saved_args if component is last_component and remaining_args == initial_args: # We're making no progress. break if remaining_args: component_trace.AddError( FireError('Could not consume arguments:', remaining_args), initial_args) return component_trace if show_completion is not None: if name is None: raise ValueError( 'Cannot make completion script without command name') script = CompletionScript(name, initial_component, shell=show_completion) component_trace.AddCompletionScript(script) if interactive: variables = context.copy() if name is not None: variables[name] = initial_component variables['component'] = initial_component variables['result'] = component variables['trace'] = component_trace if instance is not None: variables['self'] = instance interact.Embed(variables, verbose) component_trace.AddInteractiveMode() return component_trace
def _Fire(component, args, context, name=None): """Execute a Fire command on a target component using the args supplied. Arguments that come after a final isolated '--' are treated as Flags, eg for interactive mode or completion script generation. Other arguments are consumed by the execution of the Fire command, eg in the traversal of the members of the component, or in calling a function or instantiating a class found during the traversal. The steps performed by this method are: 1. Parse any Flag args (the args after the final --) 2. Start with component as the current component. 2a. If the current component is a class, instantiate it using args from args. 2b. If the current component is a routine, call it using args from args. 2c. Otherwise access a member from component using an arg from args. 2d. Repeat 2a-2c until no args remain. 3a. Embed into ipython REPL if interactive mode is selected. 3b. Generate a completion script if that flag is provided. In step 2, arguments will only ever be consumed up to a separator; a single step will never consume arguments from both sides of a separator. The separator defaults to a hyphen (-), and can be overwritten with the --separator Fire argument. Args: component: The target component for Fire. args: A list of args to consume in Firing on the component, usually from the command line. context: A dict with the local and global variables available at the call to Fire. name: Optional. The name of the command. Used in interactive mode and in the tab completion script. Returns: FireTrace of components starting with component, tracing Fire's execution path as it consumes args. Raises: ValueError: If there are arguments that cannot be consumed. ValueError: If --completion is specified but no name available. """ args, flag_args = parser.SeparateFlagArgs(args) argparser = parser.CreateParser() parsed_flag_args, unused_args = argparser.parse_known_args(flag_args) verbose = parsed_flag_args.verbose interactive = parsed_flag_args.interactive separator = parsed_flag_args.separator show_completion = parsed_flag_args.completion show_help = parsed_flag_args.help show_trace = parsed_flag_args.trace # component can be a module, class, routine, object, etc. if component is None: component = context initial_component = component component_trace = trace.FireTrace(initial_component=initial_component, name=name, separator=separator, verbose=verbose, show_help=show_help, show_trace=show_trace) instance = None remaining_args = args while True: last_component = component initial_args = remaining_args if not remaining_args and (show_help or interactive or show_trace or show_completion): # Don't initialize the final class or call the final function unless # there's a separator after it, and instead process the current component. break 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:] remaining_args = remaining_args[:separator_index] used_separator = True assert separator not in remaining_args if inspect.isclass(component) or inspect.isroutine(component): # The component is a class or a routine; we'll try to initialize it or # call it. isclass = inspect.isclass(component) try: target = component.__name__ filename, lineno = _GetFileAndLine(component) component, consumed_args, remaining_args, capacity = _CallCallable( component, remaining_args) # Update the trace. if isclass: component_trace.AddInstantiatedClass( component, target, consumed_args, filename, lineno, capacity) else: component_trace.AddCalledRoutine(component, target, consumed_args, filename, lineno, capacity) except FireError as error: component_trace.AddError(error, initial_args) return component_trace if last_component is initial_component: # If the initial component is a class, keep an instance for use with -i. instance = component elif isinstance(component, (list, tuple)) and remaining_args: # The component is a tuple or list; we'll try to access a member. arg = remaining_args[0] try: index = int(arg) component = component[index] except (ValueError, IndexError): error = FireError( 'Unable to index into component with argument:', arg) component_trace.AddError(error, initial_args) return component_trace remaining_args = remaining_args[1:] filename = None lineno = None component_trace.AddAccessedProperty(component, index, [arg], filename, lineno) elif isinstance(component, dict) and remaining_args: # The component is a dict; we'll try to access a member. target = remaining_args[0] if target in component: component = component[target] elif target.replace('-', '_') in component: component = component[target.replace('-', '_')] else: # The target isn't present in the dict as a string, but maybe it is as # another type. # TODO: Consider alternatives for accessing non-string keys. found_target = False for key, value in component.items(): if target == str(key): component = value found_target = True break if not found_target: error = FireError('Cannot find target in dict:', target, component) component_trace.AddError(error, initial_args) return component_trace remaining_args = remaining_args[1:] filename = None lineno = None component_trace.AddAccessedProperty(component, target, [target], filename, lineno) elif remaining_args: # We'll try to access a member of the component. try: target = remaining_args[0] component, consumed_args, remaining_args = _GetMember( component, remaining_args) filename, lineno = _GetFileAndLine(component) component_trace.AddAccessedProperty(component, target, consumed_args, filename, lineno) except FireError as error: component_trace.AddError(error, initial_args) return component_trace if used_separator: # Add back in the arguments from after the separator. if remaining_args: remaining_args = remaining_args + [separator] + saved_args elif (inspect.isclass(last_component) or inspect.isroutine(last_component)): remaining_args = saved_args component_trace.AddSeparator() elif component is not last_component: remaining_args = [separator] + saved_args else: # It was an unnecessary separator. remaining_args = saved_args if component is last_component and remaining_args == initial_args: # We're making no progress. break if remaining_args: component_trace.AddError( FireError('Could not consume arguments:', remaining_args), initial_args) return component_trace if show_completion: if name is None: raise ValueError( 'Cannot make completion script without command name') script = CompletionScript(name, initial_component) component_trace.AddCompletionScript(script) if interactive: variables = context.copy() if name is not None: variables[name] = initial_component variables['component'] = initial_component variables['result'] = component variables['trace'] = component_trace if instance is not None: variables['self'] = instance interact.Embed(variables, verbose) component_trace.AddInteractiveMode() return component_trace
def testInteract(self, mock_ipython): self.assertFalse(mock_ipython.called) interact.Embed({}) self.assertTrue(mock_ipython.called)