Example #1
0
  def __ValidateCommandOrGroupInfo(
      self, impl_path, allow_non_existing_modules=False,
      exception_if_present=None):
    """Generates the information necessary to be able to load a command group.

    The group might actually be loaded now if it is the root of the SDK, or the
    information might be saved for later if it is to be lazy loaded.

    Args:
      impl_path: str, The file path to the command implementation for this
        command or group.
      allow_non_existing_modules: True to allow this module directory to not
        exist, False to raise an exception if this module does not exist.
      exception_if_present: Exception, An exception to throw if the module
        actually exists, or None.

    Raises:
      LayoutException: If the module directory does not exist and
      allow_non_existing is False.

    Returns:
      impl_path or None if the module directory does not exist and
      allow_non_existing is True.
    """
    module_root, module = os.path.split(impl_path)
    if not pkg_resources.IsImportable(module, module_root):
      if allow_non_existing_modules:
        return None
      raise command_loading.LayoutException(
          'The given module directory does not exist: {0}'.format(
              impl_path))
    elif exception_if_present:
      # pylint: disable=raising-bad-type, This will be an actual exception.
      raise exception_if_present
    return impl_path
Example #2
0
  def __init__(self, impl_paths, path, release_track, construction_id,
               cli_generator, parser_group, parent_group=None,
               allow_empty=False):
    """Create a new command group.

    Args:
      impl_paths: [str], A list of file paths to the command implementation for
        this group.
      path: [str], A list of group names that got us down to this command group
        with respect to the CLI itself.  This path should be used for things
        like error reporting when a specific element in the tree needs to be
        referenced.
      release_track: base.ReleaseTrack, The release track (ga, beta, alpha) that
        this command group is in.  This will apply to all commands under it.
      construction_id: str, A unique identifier for the CLILoader that is
        being constructed.
      cli_generator: cli.CLILoader, The builder used to generate this CLI.
      parser_group: the current argparse parser, or None if this is the root
        command group.  The root command group will allocate the initial
        top level argparse parser.
      parent_group: CommandGroup, The parent of this group. None if at the
        root.
      allow_empty: bool, True to allow creating this group as empty to start
        with.

    Raises:
      LayoutException: if the module has no sub groups or commands
    """
    common_type = command_loading.LoadCommonType(
        impl_paths, path, release_track, construction_id, is_command=False)
    super(CommandGroup, self).__init__(
        common_type,
        path=path,
        release_track=release_track,
        cli_generator=cli_generator,
        allow_positional_args=False,
        parser_group=parser_group,
        parent_group=parent_group)

    self._construction_id = construction_id

    # find sub groups and commands
    self.groups = {}
    self.commands = {}
    self._groups_to_load = {}
    self._commands_to_load = {}
    self._unloadable_elements = set()

    group_infos, command_infos = command_loading.FindSubElements(impl_paths,
                                                                 path)
    self._groups_to_load.update(group_infos)
    self._commands_to_load.update(command_infos)

    if (not allow_empty and
        not self._groups_to_load and not self._commands_to_load):
      raise command_loading.LayoutException(
          'Group {0} has no subgroups or commands'.format(self.dotted_name))
    # Initialize the sub-parser so sub groups can be found.
    self.SubParser()
Example #3
0
    def __GetCommandOrGroupInfo(self,
                                module_dir_path,
                                name,
                                release_track,
                                allow_non_existing_modules=False,
                                exception_if_present=None):
        """Generates the information necessary to be able to load a command group.

    The group might actually be loaded now if it is the root of the SDK, or the
    information might be saved for later if it is to be lazy loaded.

    Args:
      module_dir_path: str, The path to the location of the module.
      name: str, The name that this group will appear as in the CLI.
      release_track: base.ReleaseTrack, The release track (ga, beta, alpha,
        preview) that this command group is in.  This will apply to all commands
        under it.
      allow_non_existing_modules: True to allow this module directory to not
        exist, False to raise an exception if this module does not exist.
      exception_if_present: Exception, An exception to throw if the module
        actually exists, or None.

    Raises:
      LayoutException: If the module directory does not exist and
      allow_non_existing is False.

    Returns:
      A tuple of (module_dir, module_path, name, release_track) or None if the
      module directory does not exist and allow_non_existing is True.  This
      tuple can be passed to self.__LoadTopGroup() or
      backend.CommandGroup.AddSubGroup().  The module_dir is the directory the
      group is found under.  The module_path is the relative path of the root
      of the command group from the module_dir. name is the user facing name
      this group will appear under wherever this command group is mounted.  The
      release_track is the release track (ga, beta, alpha, preview) that this
      command group is in.
    """
        module_root, module = os.path.split(module_dir_path)
        if not pkg_resources.IsImportable(module, module_root):
            if allow_non_existing_modules:
                return None
            raise command_loading.LayoutException(
                'The given module directory does not exist: {0}'.format(
                    module_dir_path))
        elif exception_if_present:
            # pylint: disable=raising-bad-type, This will be an actual exception.
            raise exception_if_present

        return (module_root, [module], name, release_track)
Example #4
0
    def __init__(self,
                 name,
                 command_root_directory,
                 allow_non_existing_modules=False,
                 logs_dir=config.Paths().logs_dir,
                 version_func=None,
                 known_error_handler=None,
                 yaml_command_translator=None):
        """Initialize Calliope.

    Args:
      name: str, The name of the top level command, used for nice error
        reporting.
      command_root_directory: str, The path to the directory containing the main
        CLI module.
      allow_non_existing_modules: True to allow extra module directories to not
        exist, False to raise an exception if a module does not exist.
      logs_dir: str, The path to the root directory to store logs in, or None
        for no log files.
      version_func: func, A function to call for a top-level -v and
        --version flag. If None, no flags will be available.
      known_error_handler: f(x)->None, A function to call when an known error is
        handled. It takes a single argument that is the exception.
      yaml_command_translator: YamlCommandTranslator, An instance of a
        translator that will be used to load commands written as a yaml spec.

    Raises:
      backend.LayoutException: If no command root directory is given.
    """
        self.__name = name
        self.__command_root_directory = command_root_directory
        if not self.__command_root_directory:
            raise command_loading.LayoutException(
                'You must specify a command root directory.')

        self.__allow_non_existing_modules = allow_non_existing_modules

        self.__logs_dir = logs_dir
        self.__version_func = version_func
        self.__known_error_handler = known_error_handler
        self.__yaml_command_translator = yaml_command_translator

        self.__pre_run_hooks = []
        self.__post_run_hooks = []

        self.__modules = []
        self.__missing_components = {}
        self.__release_tracks = {}
Example #5
0
    def Generate(self):
        """Uses the registered information to generate the CLI tool.

    Returns:
      CLI, The generated CLI tool.
    """
        # The root group of the CLI.
        impl_path = self.__ValidateCommandOrGroupInfo(
            self.__command_root_directory, allow_non_existing_modules=False)
        top_group = backend.CommandGroup([impl_path], [self.__name],
                                         calliope_base.ReleaseTrack.GA,
                                         uuid.uuid4().hex, self, None)
        self.__AddBuiltinGlobalFlags(top_group)

        # Sub groups for each alternate release track.
        loaded_release_tracks = dict([(calliope_base.ReleaseTrack.GA,
                                       top_group)])
        track_names = set(track.prefix
                          for track in self.__release_tracks.keys())
        for track, (module_dir,
                    component) in self.__release_tracks.iteritems():
            impl_path = self.__ValidateCommandOrGroupInfo(
                module_dir,
                allow_non_existing_modules=self.__allow_non_existing_modules)
            if impl_path:
                # Add the release track sub group into the top group.
                # pylint: disable=protected-access
                top_group._groups_to_load[track.prefix] = [impl_path]
                # Override the release track because this is specifically a top level
                # release track group.
                track_group = top_group.LoadSubElement(
                    track.prefix,
                    allow_empty=True,
                    release_track_override=track)
                # Copy all the root elements of the top group into the release group.
                top_group.CopyAllSubElementsTo(track_group, ignore=track_names)
                loaded_release_tracks[track] = track_group
            elif component:
                self.__missing_components[track.prefix] = component

        # Load the normal set of registered sub groups.
        for module_dot_path, module_dir_path, component in self.__modules:
            is_command = module_dir_path.endswith(_COMMAND_SUFFIX)
            if is_command:
                module_dir_path = module_dir_path[:-len(_COMMAND_SUFFIX)]
            match = CLILoader.PATH_RE.match(module_dot_path)
            root, name = match.group(1, 2)
            try:
                # Mount each registered sub group under each release track that exists.
                for track, track_root_group in loaded_release_tracks.iteritems(
                ):
                    parent_group = self.__FindParentGroup(
                        track_root_group, root)
                    exception_if_present = None
                    if not parent_group:
                        if track != calliope_base.ReleaseTrack.GA:
                            # Don't error mounting sub groups if the parent group can't be
                            # found unless this is for the GA group.  The GA should always be
                            # there, but for alternate release channels, the parent group
                            # might not be enabled for that particular release channel, so it
                            # is valid to not exist.
                            continue
                        exception_if_present = command_loading.LayoutException(
                            'Root [{root}] for command group [{group}] does not exist.'
                            .format(root=root, group=name))

                    cmd_or_grp_name = module_dot_path.split('.')[-1]
                    impl_path = self.__ValidateCommandOrGroupInfo(
                        module_dir_path,
                        allow_non_existing_modules=self.
                        __allow_non_existing_modules,
                        exception_if_present=exception_if_present)

                    if impl_path:
                        # pylint: disable=protected-access
                        if is_command:
                            parent_group._commands_to_load[cmd_or_grp_name] = [
                                impl_path
                            ]
                        else:
                            parent_group._groups_to_load[cmd_or_grp_name] = [
                                impl_path
                            ]
                    elif component:
                        prefix = track.prefix + '.' if track.prefix else ''
                        self.__missing_components[prefix +
                                                  module_dot_path] = component
            except command_loading.CommandLoadFailure as e:
                log.exception(e)

        cli = self.__MakeCLI(top_group)

        return cli
Example #6
0
    def Generate(self):
        """Uses the registered information to generate the CLI tool.

    Returns:
      CLI, The generated CLI tool.
    """
        # The root group of the CLI.
        top_group = self.__LoadTopGroup(
            self.__GetCommandOrGroupInfo(
                module_dir_path=self.__command_root_directory,
                name=self.__name,
                release_track=calliope_base.ReleaseTrack.GA,
                allow_non_existing_modules=False,
                exception_if_present=None))
        self.__AddBuiltinGlobalFlags(top_group)

        # Sub groups for each alternate release track.
        loaded_release_tracks = dict([(calliope_base.ReleaseTrack.GA,
                                       top_group)])
        track_names = set(track.prefix
                          for track in self.__release_tracks.keys())
        for track, (module_dir,
                    component) in self.__release_tracks.iteritems():
            group_info = self.__GetCommandOrGroupInfo(
                module_dir_path=module_dir,
                name=track.prefix,
                release_track=track,
                allow_non_existing_modules=self.__allow_non_existing_modules,
                exception_if_present=None)
            if group_info:
                # Add the release track sub group into the top group.
                top_group.AddSubGroup(group_info)
                track_group = top_group.LoadSubElement(track.prefix,
                                                       allow_empty=True)
                # Copy all the root elements of the top group into the release group.
                top_group.CopyAllSubElementsTo(track_group, ignore=track_names)
                loaded_release_tracks[track] = track_group
            elif component:
                self.__missing_components[track.prefix] = component

        # Load the normal set of registered sub groups.
        for module_dot_path, module_dir_path, component in self.__modules:
            is_command = module_dir_path.endswith(_COMMAND_SUFFIX)
            if is_command:
                module_dir_path = module_dir_path[:-len(_COMMAND_SUFFIX)]
            match = CLILoader.PATH_RE.match(module_dot_path)
            root, name = match.group(1, 2)
            try:
                # Mount each registered sub group under each release track that exists.
                for track, track_root_group in loaded_release_tracks.iteritems(
                ):
                    parent_group = self.__FindParentGroup(
                        track_root_group, root)
                    exception_if_present = None
                    if not parent_group:
                        if track != calliope_base.ReleaseTrack.GA:
                            # Don't error mounting sub groups if the parent group can't be
                            # found unless this is for the GA group.  The GA should always be
                            # there, but for alternate release channels, the parent group
                            # might not be enabled for that particular release channel, so it
                            # is valid to not exist.
                            continue
                        exception_if_present = command_loading.LayoutException(
                            'Root [{root}] for command group [{group}] does not exist.'
                            .format(root=root, group=name))

                    cmd_or_grp_name = module_dot_path.split('.')[-1]
                    cmd_or_grp_info = self.__GetCommandOrGroupInfo(
                        module_dir_path=module_dir_path,
                        name=cmd_or_grp_name,
                        release_track=(parent_group.ReleaseTrack()
                                       if parent_group else None),
                        allow_non_existing_modules=self.
                        __allow_non_existing_modules,
                        exception_if_present=exception_if_present)

                    if cmd_or_grp_info:
                        if is_command:
                            parent_group.AddSubCommand(cmd_or_grp_info)
                        else:
                            parent_group.AddSubGroup(cmd_or_grp_info)
                    elif component:
                        prefix = track.prefix + '.' if track.prefix else ''
                        self.__missing_components[prefix +
                                                  module_dot_path] = component
            except command_loading.CommandLoadFailure as e:
                log.exception(e)

        cli = self.__MakeCLI(top_group)

        return cli