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
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()
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)
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 = {}
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
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