Beispiel #1
0
 def testInteractVariables(self, mock_interact_method):
   self.assertFalse(mock_interact_method.called)
   interact.Embed({
       'count': 10,
       'mock': mock,
   })
   self.assertTrue(mock_interact_method.called)
Beispiel #2
0
 def testInteract(self, mock_interact_method):
   self.assertFalse(mock_interact_method.called)
   interact.Embed({})
   self.assertTrue(mock_interact_method.called)
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
 def testInteract(self, mock_ipython):
     self.assertFalse(mock_ipython.called)
     interact.Embed({})
     self.assertTrue(mock_ipython.called)