Coverage for python/lsst/daf/butler/cli/butler.py : 22%

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/>.
22import click
23import logging
24import os
26from . import cmd as butlerCommands
27from .utils import to_upper
28from lsst.utils import doImport
30log = logging.getLogger(__name__)
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)
40def funcNameToCmdName(functionName):
41 """Change underscores, used in functions, to dashes, used in commands."""
42 return functionName.replace("_", "-")
45def cmdNameToFuncName(commandName):
46 """Change dashes, used in commands, to underscores, used in functions."""
47 return commandName.replace("-", "_")
50class LoaderCLI(click.MultiCommand):
52 @staticmethod
53 def _getPluginList():
54 pluginModules = os.environ.get("DAF_BUTLER_PLUGINS")
55 if not pluginModules:
56 return []
57 return pluginModules.split(":")
59 @staticmethod
60 def _importPlugin(pluginName):
61 """Import a plugin that contains Click commands.
63 Parameters
64 ----------
65 pluginName : string
66 An importable module whose __all__ parameter contains the commands
67 that can be called.
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
81 @staticmethod
82 def _getLocalCommand(commandName):
83 """Search the commands provided by this package and return the command
84 if found.
86 Parameters
87 ----------
88 commandName : string
89 The name of the command to search for.
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
102 @staticmethod
103 def _getLocalCommands():
104 """Get the commands offered by daf_butler.
106 Returns
107 -------
108 list of string
109 The names of the commands.
110 """
111 return [funcNameToCmdName(f) for f in butlerCommands.__all__]
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.
117 Parameters
118 ----------
119 ctx : click.Context
120 The current Click context.
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
136 def get_command(self, context, name):
137 """Used by Click to get a single command for execution.
139 Parameters
140 ----------
141 ctx : click.Context
142 The current Click context.
143 name : string
144 The name of the command to return.
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
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)
182def main():
183 return cli()