Coverage for python/lsst/ctrl/mpexec/cli/utils.py: 34%
49 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-05 02:56 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-05 02:56 -0700
1# This file is part of ctrl_mpexec.
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 software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
29import collections
30import contextlib
31import re
33from lsst.daf.butler.cli.opt import config_file_option, config_option
34from lsst.daf.butler.cli.utils import MWCommand, split_commas
35from lsst.pipe.base.cli.opt import instrument_option
37from .opt import delete_option, task_option
39# Class which determines an action that needs to be performed
40# when building pipeline, its attributes are:
41# action: the name of the action, e.g. "new_task", "delete_task"
42# label: task label, can be None if action does not require label
43# value: argument value excluding task label.
44_PipelineAction = collections.namedtuple("_PipelineAction", "action,label,value")
47class _PipelineActionType:
48 """Class defining a callable type which converts strings into
49 ``_PipelineAction`` instances.
51 Parameters
52 ----------
53 action : `str`
54 Name of the action, will become `action` attribute of instance.
55 regex : `str`
56 Regular expression for argument value, it can define groups 'label'
57 and 'value' which will become corresponding attributes of a
58 returned instance.
59 """
61 def __init__(self, action: str, regex: str = ".*", valueType: type = str):
62 self.action = action
63 self.regex = re.compile(regex)
64 self.valueType = valueType
66 def __call__(self, value: str) -> _PipelineAction:
67 match = self.regex.match(value)
68 if not match:
69 raise TypeError(
70 f"Unrecognized syntax for option {self.action!r}: {value!r} "
71 f"(does not match pattern {self.regex.pattern})"
72 )
73 # get "label" group or use None as label
74 try:
75 label = match.group("label")
76 except IndexError:
77 label = None
78 # if "value" group is not defined use whole string
79 with contextlib.suppress(IndexError):
80 value = match.group("value")
82 value = self.valueType(value)
83 return _PipelineAction(self.action, label, value)
85 def __repr__(self) -> str:
86 """Return string representation of this class."""
87 return f"_PipelineActionType(action={self.action})"
90_ACTION_ADD_TASK = _PipelineActionType("new_task", "(?P<value>[^:]+)(:(?P<label>.+))?")
91_ACTION_DELETE_TASK = _PipelineActionType("delete_task", "(?P<value>)(?P<label>.+)")
92_ACTION_CONFIG = _PipelineActionType("config", "(?P<label>.+):(?P<value>.+=.+)")
93_ACTION_CONFIG_FILE = _PipelineActionType("configfile", "(?P<label>.+):(?P<value>.+)")
94_ACTION_ADD_INSTRUMENT = _PipelineActionType("add_instrument", "(?P<value>[^:]+)")
97def makePipelineActions(
98 args: list[str],
99 taskFlags: list[str] = task_option.opts(),
100 deleteFlags: list[str] = delete_option.opts(),
101 configFlags: list[str] = config_option.opts(),
102 configFileFlags: list[str] = config_file_option.opts(),
103 instrumentFlags: list[str] = instrument_option.opts(),
104) -> list[_PipelineAction]:
105 """Make a list of pipeline actions from a list of option flags and
106 values.
108 Parameters
109 ----------
110 args : `list` [`str`]
111 The arguments, option flags, and option values in the order they were
112 passed in on the command line.
113 taskFlags : `list` [`str`], optional
114 The option flags to use to recognize a task action, by default
115 task_option.opts().
116 deleteFlags : `list` [`str`], optional
117 The option flags to use to recognize a delete action, by default
118 delete_option.opts().
119 configFlags : `list` [`str`], optional
120 The option flags to use to recognize a config action, by default
121 config_option.opts().
122 configFileFlags : `list` [`str`], optional
123 The option flags to use to recognize a config-file action, by default
124 config_file_option.opts().
125 instrumentFlags : `list` [`str`], optional
126 The option flags to use to recognize an instrument action, by default
127 instrument_option.opts().
129 Returns
130 -------
131 pipelineActions : `list` [`_PipelineActionType`]
132 A list of pipeline actions constructed form their arguments in args,
133 in the order they appeared in args.
134 """
135 pipelineActions = []
136 # iterate up to the second-to-last element, if the second to last element
137 # is a key we're looking for, the last item will be its value.
138 for i in range(len(args) - 1):
139 if args[i] in taskFlags:
140 pipelineActions.append(_ACTION_ADD_TASK(args[i + 1]))
141 elif args[i] in deleteFlags:
142 pipelineActions.append(_ACTION_DELETE_TASK(args[i + 1]))
143 elif args[i] in configFlags:
144 pipelineActions.append(_ACTION_CONFIG(args[i + 1]))
145 elif args[i] in configFileFlags:
146 # --config-file allows multiple comma-separated values.
147 configfile_args = split_commas(None, None, args[i + 1])
148 pipelineActions.extend(_ACTION_CONFIG_FILE(c) for c in configfile_args)
149 elif args[i] in instrumentFlags:
150 pipelineActions.append(_ACTION_ADD_INSTRUMENT(args[i + 1]))
151 return pipelineActions
154class PipetaskCommand(MWCommand):
155 """Command subclass with pipetask-command specific overrides."""
157 extra_epilog = "See 'pipetask --help' for more options."