Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of daf_butler. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <http://www.gnu.org/licenses/>. 

21 

22import click 

23import logging 

24import os 

25 

26from . import cmd as butlerCommands 

27from .utils import to_upper 

28from lsst.utils import doImport 

29 

30log = logging.getLogger(__name__) 

31 

32 

33def _initLogging(logLevel): 

34 numeric_level = getattr(logging, logLevel, None) 

35 if not isinstance(numeric_level, int): 

36 raise click.ClickException(f"Invalid log level: {logLevel}") 

37 logging.basicConfig(level=numeric_level) 

38 

39 

40def funcNameToCmdName(functionName): 

41 """Change underscores, used in functions, to dashes, used in commands.""" 

42 return functionName.replace("_", "-") 

43 

44 

45def cmdNameToFuncName(commandName): 

46 """Change dashes, used in commands, to underscores, used in functions.""" 

47 return commandName.replace("-", "_") 

48 

49 

50class LoaderCLI(click.MultiCommand): 

51 

52 @staticmethod 

53 def _getPluginList(): 

54 pluginModules = os.environ.get("DAF_BUTLER_PLUGINS") 

55 if not pluginModules: 

56 return [] 

57 return pluginModules.split(":") 

58 

59 @staticmethod 

60 def _importPlugin(pluginName): 

61 """Import a plugin that contains Click commands. 

62 

63 Parameters 

64 ---------- 

65 pluginName : string 

66 An importable module whose __all__ parameter contains the commands 

67 that can be called. 

68 

69 Returns 

70 ------- 

71 An imported module or None 

72 The imported module, or None if the module could not be imported. 

73 """ 

74 try: 

75 return doImport(pluginName) 

76 except (TypeError, ModuleNotFoundError, ImportError) as err: 

77 log.warning("Could not import plugin from %s, skipping.", pluginName) 

78 log.debug("Plugin import exception: %s", err) 

79 return None 

80 

81 @staticmethod 

82 def _getLocalCommand(commandName): 

83 """Search the commands provided by this package and return the command 

84 if found. 

85 

86 Parameters 

87 ---------- 

88 commandName : string 

89 The name of the command to search for. 

90 

91 Returns 

92 ------- 

93 Command object 

94 A Click command that can be executed. 

95 """ 

96 try: 

97 return getattr(butlerCommands, commandName) 

98 except AttributeError: 

99 pass 

100 return None 

101 

102 @staticmethod 

103 def _getLocalCommands(): 

104 """Get the commands offered by daf_butler. 

105 

106 Returns 

107 ------- 

108 list of string 

109 The names of the commands. 

110 """ 

111 return [funcNameToCmdName(f) for f in butlerCommands.__all__] 

112 

113 def list_commands(self, ctx): 

114 """Used by Click to get all the commands that can be called by the 

115 butler command, it is used to generate the --help output. 

116 

117 Parameters 

118 ---------- 

119 ctx : click.Context 

120 The current Click context. 

121 

122 Returns 

123 ------- 

124 List of string 

125 The names of the commands that can be called by the butler command. 

126 """ 

127 commands = self._getLocalCommands() 

128 for pluginName in self._getPluginList(): 

129 plugin = self._importPlugin(pluginName) 

130 if plugin is None: 

131 continue 

132 commands.extend([funcNameToCmdName(command) for command in plugin.__all__]) 

133 commands.sort() 

134 return commands 

135 

136 def get_command(self, context, name): 

137 """Used by Click to get a single command for execution. 

138 

139 Parameters 

140 ---------- 

141 ctx : click.Context 

142 The current Click context. 

143 name : string 

144 The name of the command to return. 

145 

146 Returns 

147 ------- 

148 click.Command 

149 A Command that wraps a callable command function. 

150 """ 

151 name = cmdNameToFuncName(name) 

152 localCmd = self._getLocalCommand(name) 

153 if localCmd is not None: 

154 return localCmd 

155 for pluginName in self._getPluginList(): 

156 plugin = self._importPlugin(pluginName) 

157 if plugin is None: 

158 continue 

159 for command in plugin.__all__: 

160 if command == name: 

161 fullCommand = pluginName + "." + command 

162 try: 

163 cmd = doImport(fullCommand) 

164 except Exception as err: 

165 log.debug("Command import exception: %s", err) 

166 context.fail("Could not import command {fullCommand}") 

167 return cmd 

168 return None 

169 

170 

171@click.command(cls=LoaderCLI) 

172@click.option("--log-level", 

173 type=click.Choice(["critical", "error", "warning", "info", "debug", 

174 "CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]), 

175 default="warning", 

176 help="The Python log level to use.", 

177 callback=to_upper) 

178def cli(log_level): 

179 _initLogging(log_level) 

180 

181 

182def main(): 

183 return cli()