LSSTApplications  1.1.2+25,10.0+13,10.0+132,10.0+133,10.0+224,10.0+41,10.0+8,10.0-1-g0f53050+14,10.0-1-g4b7b172+19,10.0-1-g61a5bae+98,10.0-1-g7408a83+3,10.0-1-gc1e0f5a+19,10.0-1-gdb4482e+14,10.0-11-g3947115+2,10.0-12-g8719d8b+2,10.0-15-ga3f480f+1,10.0-2-g4f67435,10.0-2-gcb4bc6c+26,10.0-28-gf7f57a9+1,10.0-3-g1bbe32c+14,10.0-3-g5b46d21,10.0-4-g027f45f+5,10.0-4-g86f66b5+2,10.0-4-gc4fccf3+24,10.0-40-g4349866+2,10.0-5-g766159b,10.0-5-gca2295e+25,10.0-6-g462a451+1
LSSTDataManagementBasePackage
argumentParser.py
Go to the documentation of this file.
1 from __future__ import absolute_import, division
2 #
3 # LSST Data Management System
4 # Copyright 2008, 2009, 2010 LSST Corporation.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
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 LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <http://www.lsstcorp.org/LegalNotices/>.
22 #
23 import argparse
24 import collections
25 import fnmatch
26 import itertools
27 import os
28 import re
29 import shlex
30 import sys
31 import shutil
32 
33 import eups
34 import lsst.pex.config as pexConfig
35 import lsst.pex.logging as pexLog
36 import lsst.daf.persistence as dafPersist
37 
38 __all__ = ["ArgumentParser", "ConfigFileAction", "ConfigValueAction", "DataIdContainer", "DatasetArgument"]
39 
40 DEFAULT_INPUT_NAME = "PIPE_INPUT_ROOT"
41 DEFAULT_CALIB_NAME = "PIPE_CALIB_ROOT"
42 DEFAULT_OUTPUT_NAME = "PIPE_OUTPUT_ROOT"
43 
44 def _fixPath(defName, path):
45  """!Apply environment variable as default root, if present, and abspath
46 
47  @param[in] defName name of environment variable containing default root path;
48  if the environment variable does not exist then the path is relative
49  to the current working directory
50  @param[in] path path relative to default root path
51  @return abspath: path that has been expanded, or None if the environment variable does not exist
52  and path is None
53  """
54  defRoot = os.environ.get(defName)
55  if defRoot is None:
56  if path is None:
57  return None
58  return os.path.abspath(path)
59  return os.path.abspath(os.path.join(defRoot, path or ""))
60 
61 
62 class DataIdContainer(object):
63  """!A container for data IDs and associated data references
64 
65  Override for data IDs that require special handling to be converted to data references,
66  and specify the override class as ContainerClass for add_id_argument.
67  (If you don't want the argument parser to compute data references, you may use this class
68  and specify doMakeDataRefList=False in add_id_argument.)
69  """
70  def __init__(self, level=None):
71  """!Construct a DataIdContainer"""
72  self.datasetType = None # the actual dataset type, as specified on the command line (if dynamic)
73  self.level = level
74  self.idList = []
75  self.refList = []
76 
77  def setDatasetType(self, datasetType):
78  """!Set actual dataset type, once it is known"""
79  self.datasetType = datasetType
80 
81  def castDataIds(self, butler):
82  """!Validate data IDs and cast them to the correct type (modify idList in place).
83 
84  @param[in] butler data butler (a \ref lsst.daf.persistence.butler.Butler
85  "lsst.daf.persistence.Butler")
86  """
87  if self.datasetType is None:
88  raise RuntimeError("Must call setDatasetType first")
89  try:
90  idKeyTypeDict = butler.getKeys(datasetType=self.datasetType, level=self.level)
91  except KeyError:
92  raise KeyError("Cannot get keys for datasetType %s at level %s" % (self.datasetType, self.level))
93 
94  for dataDict in self.idList:
95  for key, strVal in dataDict.iteritems():
96  try:
97  keyType = idKeyTypeDict[key]
98  except KeyError:
99  validKeys = sorted(idKeyTypeDict.keys())
100  raise KeyError("Unrecognized ID key %r; valid keys are: %s" % (key, validKeys))
101  if keyType != str:
102  try:
103  castVal = keyType(strVal)
104  except Exception:
105  raise TypeError("Cannot cast value %r to %s for ID key %r" % (strVal, keyType, key,))
106  dataDict[key] = castVal
107 
108  def makeDataRefList(self, namespace):
109  """!Compute refList based on idList
110 
111  Not called if add_id_argument called with doMakeDataRef=False
112 
113  @param[in] namespace results of parsing command-line (with 'butler' and 'log' elements)
114  """
115  if self.datasetType is None:
116  raise RuntimeError("Must call setDatasetType first")
117  butler = namespace.butler
118  for dataId in self.idList:
119  refList = list(butler.subset(datasetType=self.datasetType, level=self.level, dataId=dataId))
120  # exclude nonexistent data
121  # this is a recursive test, e.g. for the sake of "raw" data
122  refList = [dr for dr in refList if dataExists(butler=butler, datasetType=self.datasetType,
123  dataRef=dr)]
124  if not refList:
125  namespace.log.warn("No data found for dataId=%s" % (dataId,))
126  continue
127  self.refList += refList
128 
129 
130 class DataIdArgument(object):
131  """!Glorified struct for data about id arguments, used by ArgumentParser.add_id_argument"""
132  def __init__(self, name, datasetType, level, doMakeDataRefList=True, ContainerClass=DataIdContainer):
133  """!Constructor
134 
135  @param[in] name name of identifier (argument name without dashes)
136  @param[in] datasetType type of dataset; specify a string for a fixed dataset type
137  or a DatasetArgument for a dynamic dataset type (one specified on the command line),
138  in which case an argument is added by name --<name>_dstype
139  @param[in] level level of dataset, for butler
140  @param[in] doMakeDataRefList construct data references?
141  @param[in] ContainerClass class to contain data IDs and data references;
142  the default class will work for many kinds of data, but you may have to override
143  to compute some kinds of data references.
144  """
145  if name.startswith("-"):
146  raise RuntimeError("Name %s must not start with -" % (name,))
147  self.name = name
148  self.datasetType = datasetType
149  self.level = level
150  self.doMakeDataRefList = bool(doMakeDataRefList)
151  self.ContainerClass = ContainerClass
152  self.argName = name.lstrip("-")
153  if self.isDynamicDatasetType():
154  self.datasetTypeName = datasetType.name if datasetType.name else self.name + "_dstype"
155  else:
156  self.datasetTypeName = None
157 
159  """!Is the dataset type dynamic (specified on the command line)?"""
160  return isinstance(self.datasetType, DatasetArgument)
161 
162  def getDatasetType(self, namespace):
163  """!Get the dataset type
164 
165  @param[in] namespace parsed command created by argparse parse_args;
166  if the dataset type is dynamic then it is read from namespace.<name>_dstype
167  else namespace is ignored
168  """
169  return getattr(namespace, self.datasetTypeName) if self.isDynamicDatasetType() else self.datasetType
170 
171 class DatasetArgument(object):
172  """!Specify that the dataset type should be a command-line option.
173 
174  Somewhat more heavyweight than just using, e.g., None as a signal, but
175  provides the ability to have more informative help and a default. Also
176  more extensible in the future.
177 
178  @param[in] name name of command-line argument (including leading "--", if wanted);
179  if omitted a suitable default is chosen
180  @param[in] help help string for the command-line option
181  @param[in] default default value; if None, then the option is required
182  """
183  def __init__(self,
184  name = None,
185  help="dataset type to process from input data repository",
186  default=None,
187  ):
188  self.name = name
189  self.help = help
190  self.default = default
191 
192  @property
193  def required(self):
194  return self.default is None
195 
196 class ArgumentParser(argparse.ArgumentParser):
197  """!An argument parser for pipeline tasks that is based on argparse.ArgumentParser
198 
199  Users may wish to add additional arguments before calling parse_args.
200 
201  @note
202  - I would prefer to check data ID keys and values as they are parsed,
203  but the required information comes from the butler, so I have to construct a butler
204  before I do this checking. Constructing a butler is slow, so I only want do it once,
205  after parsing the command line, so as to catch syntax errors quickly.
206  """
207  def __init__(self, name, usage = "%(prog)s input [options]", **kwargs):
208  """!Construct an ArgumentParser
209 
210  @param[in] name name of top-level task; used to identify camera-specific override files
211  @param[in] usage usage string
212  @param[in] **kwargs additional keyword arguments for argparse.ArgumentParser
213  """
214  self._name = name
215  self._dataIdArgDict = {} # Dict of data identifier specifications, by argument name
216  argparse.ArgumentParser.__init__(self,
217  usage = usage,
218  fromfile_prefix_chars = '@',
219  epilog = """Notes:
220 * --config, --configfile, --id, --trace and @file may appear multiple times;
221  all values are used, in order left to right
222 * @file reads command-line options from the specified file:
223  * data may be distributed among multiple lines (e.g. one option per line)
224  * data after # is treated as a comment and ignored
225  * blank lines and lines starting with # are ignored
226 * To specify multiple values for an option, do not use = after the option name:
227  * right: --configfile foo bar
228  * wrong: --configfile=foo bar
229 """,
230  formatter_class = argparse.RawDescriptionHelpFormatter,
231  **kwargs)
232  self.add_argument("input",
233  help="path to input data repository, relative to $%s" % (DEFAULT_INPUT_NAME,))
234  self.add_argument("--calib",
235  help="path to input calibration repository, relative to $%s" % (DEFAULT_CALIB_NAME,))
236  self.add_argument("--output",
237  help="path to output data repository (need not exist), relative to $%s" % (DEFAULT_OUTPUT_NAME,))
238  self.add_argument("-c", "--config", nargs="*", action=ConfigValueAction,
239  help="config override(s), e.g. -c foo=newfoo bar.baz=3", metavar="NAME=VALUE")
240  self.add_argument("-C", "--configfile", dest="configfile", nargs="*", action=ConfigFileAction,
241  help="config override file(s)")
242  self.add_argument("-L", "--loglevel", help="logging level")
243  self.add_argument("-T", "--trace", nargs="*", action=TraceLevelAction,
244  help="trace level for component", metavar="COMPONENT=LEVEL")
245  self.add_argument("--debug", action="store_true", help="enable debugging output?")
246  self.add_argument("--doraise", action="store_true",
247  help="raise an exception on error (else log a message and continue)?")
248  self.add_argument("--profile", help="Dump cProfile statistics to filename")
249  self.add_argument("--logdest", help="logging destination")
250  self.add_argument("--show", nargs="+", default=(),
251  help="display the specified information to stdout and quit (unless run is specified).")
252  self.add_argument("-j", "--processes", type=int, default=1, help="Number of processes to use")
253  self.add_argument("-t", "--timeout", type=float,
254  help="Timeout for multiprocessing; maximum wall time (sec)")
255  self.add_argument("--clobber-output", action="store_true", dest="clobberOutput", default=False,
256  help=("remove and re-create the output directory if it already exists "
257  "(safe with -j, but not all other forms of parallel execution)"))
258  self.add_argument("--clobber-config", action="store_true", dest="clobberConfig", default=False,
259  help=("backup and then overwrite existing config files instead of checking them "
260  "(safe with -j, but not all other forms of parallel execution)"))
261 
262  def add_id_argument(self, name, datasetType, help, level=None, doMakeDataRefList=True,
263  ContainerClass=DataIdContainer):
264  """!Add a data ID argument
265 
266  Add an argument to specify data IDs. If datasetType is an instance of DatasetArgument,
267  then add a second argument to specify the dataset type.
268 
269  @param[in] name name of name (including leading dashes, if wanted)
270  @param[in] datasetType type of dataset; supply a string for a fixed dataset type,
271  or a DatasetArgument for a dynamically determined dataset type
272  @param[in] help help string for the argument
273  @param[in] level level of dataset, for butler
274  @param[in] doMakeDataRefList construct data references?
275  @param[in] ContainerClass data ID container class to use to contain results;
276  override the default if you need a special means of computing data references from data IDs
277 
278  The associated data is put into namespace.<dataIdArgument.name> as an instance of ContainerClass;
279  the container includes fields:
280  - idList: a list of data ID dicts
281  - refList: a list of butler data references (empty if doMakeDataRefList false)
282  """
283  argName = name.lstrip("-")
284 
285  if argName in self._dataIdArgDict:
286  raise RuntimeError("Data ID argument %s already exists" % (name,))
287  if argName in set(("camera", "config", "butler", "log", "obsPkg")):
288  raise RuntimeError("Data ID argument %s is a reserved name" % (name,))
289 
290  self.add_argument(name, nargs="*", action=IdValueAction, help=help,
291  metavar="KEY=VALUE1[^VALUE2[^VALUE3...]")
292 
293  dataIdArgument = DataIdArgument(
294  name = argName,
295  datasetType = datasetType,
296  level = level,
297  doMakeDataRefList = doMakeDataRefList,
298  ContainerClass = ContainerClass,
299  )
300 
301  if dataIdArgument.isDynamicDatasetType():
302  datasetType = dataIdArgument.datasetType
303  help = datasetType.help if datasetType.help else "dataset type for %s" % (name,)
304  self.add_argument(
305  "--" + dataIdArgument.datasetTypeName,
306  default = datasetType.default,
307  required = datasetType.required,
308  help = help,
309  )
310  self._dataIdArgDict[argName] = dataIdArgument
311 
312  def parse_args(self, config, args=None, log=None, override=None):
313  """!Parse arguments for a pipeline task
314 
315  @param[in,out] config config for the task being run
316  @param[in] args argument list; if None use sys.argv[1:]
317  @param[in] log log (instance pex_logging Log); if None use the default log
318  @param[in] override a config override function; it must take the root config object
319  as its only argument and must modify the config in place.
320  This function is called after camera-specific overrides files are applied, and before
321  command-line config overrides are applied (thus allowing the user the final word).
322 
323  @return namespace: an argparse.Namespace containing many useful fields including:
324  - camera: camera name
325  - config: the supplied config with all overrides applied, validated and frozen
326  - butler: a butler for the data
327  - an entry for each of the data ID arguments registered by add_id_argument(),
328  the value of which is a DataIdArgument that includes public elements 'idList' and 'refList'
329  - log: a pex_logging log
330  - an entry for each command-line argument, with the following exceptions:
331  - config is Config, not an override
332  - configfile, id, logdest, loglevel are all missing
333  - obsPkg: name of obs_ package for this camera
334  """
335  if args == None:
336  args = sys.argv[1:]
337 
338  if len(args) < 1 or args[0].startswith("-") or args[0].startswith("@"):
339  self.print_help()
340  if len(args) == 1 and args[0] in ("-h", "--help"):
341  self.exit()
342  else:
343  self.exit("%s: error: Must specify input as first argument" % self.prog)
344 
345  # note: don't set namespace.input until after running parse_args, else it will get overwritten
346  inputRoot = _fixPath(DEFAULT_INPUT_NAME, args[0])
347  if not os.path.isdir(inputRoot):
348  self.error("Error: input=%r not found" % (inputRoot,))
349 
350  namespace = argparse.Namespace()
351  namespace.config = config
352  namespace.log = log if log is not None else pexLog.Log.getDefaultLog()
353  mapperClass = dafPersist.Butler.getMapperClass(inputRoot)
354  namespace.camera = mapperClass.getCameraName()
355  namespace.obsPkg = mapperClass.getEupsProductName()
356 
357  self.handleCamera(namespace)
358 
359  self._applyInitialOverrides(namespace)
360  if override is not None:
361  override(namespace.config)
362 
363  # Add data ID containers to namespace
364  for dataIdArgument in self._dataIdArgDict.itervalues():
365  setattr(namespace, dataIdArgument.name, dataIdArgument.ContainerClass(level=dataIdArgument.level))
366 
367  namespace = argparse.ArgumentParser.parse_args(self, args=args, namespace=namespace)
368  namespace.input = inputRoot
369  del namespace.configfile
370 
371  namespace.calib = _fixPath(DEFAULT_CALIB_NAME, namespace.calib)
372  namespace.output = _fixPath(DEFAULT_OUTPUT_NAME, namespace.output)
373 
374  if namespace.clobberOutput:
375  if namespace.output is None:
376  self.error("--clobber-output is only valid with --output")
377  elif namespace.output == namespace.input:
378  self.error("--clobber-output is not valid when the output and input repos are the same")
379  if os.path.exists(namespace.output):
380  namespace.log.info("Removing output repo %s for --clobber-output" % namespace.output)
381  shutil.rmtree(namespace.output)
382 
383  namespace.log.info("input=%s" % (namespace.input,))
384  namespace.log.info("calib=%s" % (namespace.calib,))
385  namespace.log.info("output=%s" % (namespace.output,))
386 
387  obeyShowArgument(namespace.show, namespace.config, exit=False)
388 
389  namespace.butler = dafPersist.Butler(
390  root = namespace.input,
391  calibRoot = namespace.calib,
392  outputRoot = namespace.output,
393  )
394 
395  # convert data in each of the identifier lists to proper types
396  # this is done after constructing the butler, hence after parsing the command line,
397  # because it takes a long time to construct a butler
398  self._processDataIds(namespace)
399  if "data" in namespace.show:
400  for dataIdName in self._dataIdArgDict.iterkeys():
401  for dataRef in getattr(namespace, dataIdName).refList:
402  print dataIdName + " dataRef.dataId =", dataRef.dataId
403 
404  if namespace.show and "run" not in namespace.show:
405  sys.exit(0)
406 
407  if namespace.debug:
408  try:
409  import debug
410  assert debug # silence pyflakes
411  except ImportError:
412  sys.stderr.write("Warning: no 'debug' module found\n")
413  namespace.debug = False
414 
415  if namespace.logdest:
416  namespace.log.addDestination(namespace.logdest)
417  del namespace.logdest
418 
419  if namespace.loglevel:
420  permitted = ('DEBUG', 'INFO', 'WARN', 'FATAL')
421  if namespace.loglevel.upper() in permitted:
422  value = getattr(pexLog.Log, namespace.loglevel.upper())
423  else:
424  try:
425  value = int(namespace.loglevel)
426  except ValueError:
427  self.error("log-level=%s not int or one of %s" % (namespace.loglevel, permitted))
428  namespace.log.setThreshold(value)
429  del namespace.loglevel
430 
431  namespace.config.validate()
432  namespace.config.freeze()
433 
434  return namespace
435 
436  def _processDataIds(self, namespace):
437  """!Process the parsed data for each data ID argument
438 
439  Processing includes:
440  - Validate data ID keys
441  - Cast the data ID values to the correct type
442  - Compute data references from data IDs
443 
444  @param[in,out] namespace parsed namespace (an argparse.Namespace);
445  reads these attributes:
446  - butler
447  - log
448  - <name_dstype> for each data ID argument with a dynamic dataset type registered using
449  add_id_argument
450  and modifies these attributes:
451  - <name> for each data ID argument registered using add_id_argument
452  """
453  for dataIdArgument in self._dataIdArgDict.itervalues():
454  dataIdContainer = getattr(namespace, dataIdArgument.name)
455  dataIdContainer.setDatasetType(dataIdArgument.getDatasetType(namespace))
456  try:
457  dataIdContainer.castDataIds(butler = namespace.butler)
458  except (KeyError, TypeError) as e:
459  # failure of castDataIds indicates invalid command args
460  self.error(e)
461  # failure of makeDataRefList indicates a bug that wants a traceback
462  if dataIdArgument.doMakeDataRefList:
463  dataIdContainer.makeDataRefList(namespace)
464 
465  def _applyInitialOverrides(self, namespace):
466  """!Apply obs-package-specific and camera-specific config override files, if found
467 
468  @param[in] namespace parsed namespace (an argparse.Namespace);
469  reads these attributes:
470  - obsPkg
471 
472  Look in the package namespace.obsPkg for files:
473  - config/<task_name>.py
474  - config/<camera_name>/<task_name>.py
475  and load if found
476  """
477  obsPkgDir = eups.productDir(namespace.obsPkg)
478  fileName = self._name + ".py"
479  if not obsPkgDir:
480  raise RuntimeError("Must set up %r" % (namespace.obsPkg,))
481  for filePath in (
482  os.path.join(obsPkgDir, "config", fileName),
483  os.path.join(obsPkgDir, "config", namespace.camera, fileName),
484  ):
485  if os.path.exists(filePath):
486  namespace.log.info("Loading config overrride file %r" % (filePath,))
487  namespace.config.load(filePath)
488  else:
489  namespace.log.info("Config override file does not exist: %r" % (filePath,))
490 
491  def handleCamera(self, namespace):
492  """!Perform camera-specific operations before parsing the command line.
493 
494  The default implementation does nothing.
495 
496  @param[in,out] namespace namespace (an argparse.Namespace) with the following fields:
497  - camera: the camera name
498  - config: the config passed to parse_args, with no overrides applied
499  - obsPkg: the obs_ package for this camera
500  - log: a pex_logging log
501  """
502  pass
503 
504  def convert_arg_line_to_args(self, arg_line):
505  """!Allow files of arguments referenced by `@<path>` to contain multiple values on each line
506 
507  @param[in] arg_line line of text read from an argument file
508  """
509  arg_line = arg_line.strip()
510  if not arg_line or arg_line.startswith("#"):
511  return
512  for arg in shlex.split(arg_line, comments=True, posix=True):
513  if not arg.strip():
514  continue
515  yield arg
516 
517 def getTaskDict(config, taskDict=None, baseName=""):
518  """!Get a dictionary of task info for all subtasks in a config
519 
520  Designed to be called recursively; the user should call with only a config
521  (leaving taskDict and baseName at their default values).
522 
523  @param[in] config configuration to process, an instance of lsst.pex.config.Config
524  @param[in,out] taskDict users should not specify this argument;
525  (supports recursion; if provided, taskDict is updated in place, else a new dict is started)
526  @param[in] baseName users should not specify this argument.
527  (supports recursion: if a non-empty string then a period is appended and the result is used
528  as a prefix for additional entries in taskDict; otherwise no prefix is used)
529  @return taskDict: a dict of config field name: task name
530  """
531  if taskDict is None:
532  taskDict = dict()
533  for fieldName, field in config.iteritems():
534  if hasattr(field, "value") and hasattr(field, "target"):
535  subConfig = field.value
536  if isinstance(subConfig, pexConfig.Config):
537  subBaseName = "%s.%s" % (baseName, fieldName) if baseName else fieldName
538  try:
539  taskName = "%s.%s" % (field.target.__module__, field.target.__name__)
540  except Exception:
541  taskName = repr(field.target)
542  taskDict[subBaseName] = taskName
543  getTaskDict(config=subConfig, taskDict=taskDict, baseName=subBaseName)
544  return taskDict
545 
546 def obeyShowArgument(showOpts, config=None, exit=False):
547  """!Process arguments specified with --show (but ignores "data")
548 
549  @param showOpts List of options passed to --show
550  @param config The provided config
551  @param exit Exit if "run" isn't included in showOpts
552 
553  Supports the following options in showOpts:
554  - config[=PAT] Dump all the config entries, or just the ones that match the glob pattern
555  - tasks Show task hierarchy
556  - data Ignored; to be processed by caller
557  - run Keep going (the default behaviour is to exit if --show is specified)
558 
559  Calls sys.exit(1) if any other option found.
560  """
561  if not showOpts:
562  return
563 
564  for what in showOpts:
565  mat = re.search(r"^config(?:=(.+))?", what)
566  if mat:
567  pattern = mat.group(1)
568  if pattern:
569  class FilteredStream(object):
570  """A file object that only prints lines that match the glob "pattern"
571 
572  N.b. Newlines are silently discarded and reinserted; crude but effective.
573  """
574  def __init__(self, pattern):
575  self._pattern = pattern
576 
577  def write(self, str):
578  str = str.rstrip()
579  if str and fnmatch.fnmatch(str, self._pattern):
580  print str
581 
582  fd = FilteredStream(pattern)
583  else:
584  fd = sys.stdout
585 
586  config.saveToStream(fd, "config")
587  elif what == "data":
588  pass
589  elif what == "run":
590  pass
591  elif what == "tasks":
592  showTaskHierarchy(config)
593  else:
594  print >> sys.stderr, "Unknown value for show: %s (choose from '%s')" % \
595  (what, "', '".join("config[=XXX] data tasks run".split()))
596  sys.exit(1)
597 
598  if exit and "run" not in showOpts:
599  sys.exit(0)
600 
601 def showTaskHierarchy(config):
602  """!Print task hierarchy to stdout
603 
604  @param[in] config: configuration to process (an lsst.pex.config.Config)
605  """
606  print "Subtasks:"
607  taskDict = getTaskDict(config=config)
608 
609  fieldNameList = sorted(taskDict.keys())
610  for fieldName in fieldNameList:
611  taskName = taskDict[fieldName]
612  print "%s: %s" % (fieldName, taskName)
613 
614 class ConfigValueAction(argparse.Action):
615  """!argparse action callback to override config parameters using name=value pairs from the command line
616  """
617  def __call__(self, parser, namespace, values, option_string):
618  """!Override one or more config name value pairs
619 
620  @param[in] parser argument parser (instance of ArgumentParser)
621  @param[in,out] namespace parsed command (an instance of argparse.Namespace);
622  updated values:
623  - namespace.config
624  @param[in] values a list of configItemName=value pairs
625  @param[in] option_string option value specified by the user (a str)
626  """
627  if namespace.config is None:
628  return
629  for nameValue in values:
630  name, sep, valueStr = nameValue.partition("=")
631  if not valueStr:
632  parser.error("%s value %s must be in form name=value" % (option_string, nameValue))
633 
634  # see if setting the string value works; if not, try eval
635  try:
636  setDottedAttr(namespace.config, name, valueStr)
637  except AttributeError:
638  parser.error("no config field: %s" % (name,))
639  except Exception:
640  try:
641  value = eval(valueStr, {})
642  except Exception:
643  parser.error("cannot parse %r as a value for %s" % (valueStr, name))
644  try:
645  setDottedAttr(namespace.config, name, value)
646  except Exception, e:
647  parser.error("cannot set config.%s=%r: %s" % (name, value, e))
648 
649 class ConfigFileAction(argparse.Action):
650  """!argparse action to load config overrides from one or more files
651  """
652  def __call__(self, parser, namespace, values, option_string=None):
653  """!Load one or more files of config overrides
654 
655  @param[in] parser argument parser (instance of ArgumentParser)
656  @param[in,out] namespace parsed command (an instance of argparse.Namespace);
657  updated values:
658  - namespace.config
659  @param[in] values a list of data config file paths
660  @param[in] option_string option value specified by the user (a str)
661  """
662  if namespace.config is None:
663  return
664  for configfile in values:
665  try:
666  namespace.config.load(configfile)
667  except Exception, e:
668  parser.error("cannot load config file %r: %s" % (configfile, e))
669 
670 
671 class IdValueAction(argparse.Action):
672  """!argparse action callback to process a data ID into a dict
673  """
674  def __call__(self, parser, namespace, values, option_string):
675  """!Parse --id data and append results to namespace.<argument>.idList
676 
677  @param[in] parser argument parser (instance of ArgumentParser)
678  @param[in,out] namespace parsed command (an instance of argparse.Namespace);
679  updated values:
680  - <idName>.idList, where <idName> is the name of the ID argument,
681  for instance "id" for ID argument --id
682  @param[in] values a list of data IDs; see data format below
683  @param[in] option_string option value specified by the user (a str)
684 
685  The data format is:
686  key1=value1_1[^value1_2[^value1_3...] key2=value2_1[^value2_2[^value2_3...]...
687 
688  The values (e.g. value1_1) may either be a string, or of the form "int..int" (e.g. "1..3")
689  which is interpreted as "1^2^3" (inclusive, unlike a python range). So "0^2..4^7..9" is
690  equivalent to "0^2^3^4^7^8^9". You may also specify a stride: "1..5:2" is "1^3^5"
691 
692  The cross product is computed for keys with multiple values. For example:
693  --id visit 1^2 ccd 1,1^2,2
694  results in the following data ID dicts being appended to namespace.<argument>.idList:
695  {"visit":1, "ccd":"1,1"}
696  {"visit":2, "ccd":"1,1"}
697  {"visit":1, "ccd":"2,2"}
698  {"visit":2, "ccd":"2,2"}
699  """
700  if namespace.config is None:
701  return
702  idDict = collections.OrderedDict()
703  for nameValue in values:
704  name, sep, valueStr = nameValue.partition("=")
705  if name in idDict:
706  parser.error("%s appears multiple times in one ID argument: %s" % (name, option_string))
707  idDict[name] = []
708  for v in valueStr.split("^"):
709  mat = re.search(r"^(\d+)\.\.(\d+)(?::(\d+))?$", v)
710  if mat:
711  v1 = int(mat.group(1))
712  v2 = int(mat.group(2))
713  v3 = mat.group(3); v3 = int(v3) if v3 else 1
714  for v in range(v1, v2 + 1, v3):
715  idDict[name].append(str(v))
716  else:
717  idDict[name].append(v)
718 
719  keyList = idDict.keys()
720  iterList = [idDict[key] for key in keyList]
721  idDictList = [collections.OrderedDict(zip(keyList, valList))
722  for valList in itertools.product(*iterList)]
723 
724  argName = option_string.lstrip("-")
725  ident = getattr(namespace, argName)
726  ident.idList += idDictList
727 
728 class TraceLevelAction(argparse.Action):
729  """!argparse action to set trace level
730  """
731  def __call__(self, parser, namespace, values, option_string):
732  """!Set trace level
733 
734  @param[in] parser argument parser (instance of ArgumentParser)
735  @param[in] namespace parsed command (an instance of argparse.Namespace); ignored
736  @param[in] values a list of trace levels;
737  each item must be of the form component_name=level
738  @param[in] option_string option value specified by the user (a str)
739  """
740  for componentLevel in values:
741  component, sep, levelStr = componentLevel.partition("=")
742  if not levelStr:
743  parser.error("%s level %s must be in form component=level" % (option_string, componentLevel))
744  try:
745  level = int(levelStr)
746  except Exception:
747  parser.error("cannot parse %r as an integer level for %s" % (levelStr, component))
748  pexLog.Trace.setVerbosity(component, level)
749 
750 
751 
752 def setDottedAttr(item, name, value):
753  """!Like setattr, but accepts hierarchical names, e.g. foo.bar.baz
754 
755  @param[in,out] item object whose attribute is to be set
756  @param[in] name name of item to set
757  @param[in] value new value for the item
758 
759  For example if name is foo.bar.baz then item.foo.bar.baz is set to the specified value.
760  """
761  subitem = item
762  subnameList = name.split(".")
763  for subname in subnameList[:-1]:
764  subitem = getattr(subitem, subname)
765  setattr(subitem, subnameList[-1], value)
766 
767 def getDottedAttr(item, name):
768  """!Like getattr, but accepts hierarchical names, e.g. foo.bar.baz
769 
770  @param[in] item object whose attribute is to be returned
771  @param[in] name name of item to get
772 
773  For example if name is foo.bar.baz then returns item.foo.bar.baz
774  """
775  subitem = item
776  for subname in name.split("."):
777  subitem = getattr(subitem, subname)
778  return subitem
779 
780 def dataExists(butler, datasetType, dataRef):
781  """!Return True if data exists at the current level or any data exists at a deeper level, False otherwise
782 
783  @param[in] butler data butler (a \ref lsst.daf.persistence.butler.Butler
784  "lsst.daf.persistence.Butler")
785  @param[in] datasetType dataset type (a str)
786  @param[in] dataRef butler data reference (a \ref lsst.daf.persistence.butlerSubset.ButlerDataRef
787  "lsst.daf.persistence.ButlerDataRef")
788  """
789  subDRList = dataRef.subItems()
790  if subDRList:
791  for subDR in subDRList:
792  if dataExists(butler, datasetType, subDR):
793  return True
794  return False
795  else:
796  return butler.datasetExists(datasetType = datasetType, dataId = dataRef.dataId)
def setDatasetType
Set actual dataset type, once it is known.
def castDataIds
Validate data IDs and cast them to the correct type (modify idList in place).
An argument parser for pipeline tasks that is based on argparse.ArgumentParser.
def dataExists
Return True if data exists at the current level or any data exists at a deeper level, False otherwise.
Specify that the dataset type should be a command-line option.
def showTaskHierarchy
Print task hierarchy to stdout.
Glorified struct for data about id arguments, used by ArgumentParser.add_id_argument.
def setDottedAttr
Like setattr, but accepts hierarchical names, e.g.
def __call__
Parse –id data and append results to namespace.
argparse action callback to override config parameters using name=value pairs from the command line ...
def getTaskDict
Get a dictionary of task info for all subtasks in a config.
a place to record messages and descriptions of the state of processing.
Definition: Log.h:154
def _applyInitialOverrides
Apply obs-package-specific and camera-specific config override files, if found.
def makeDataRefList
Compute refList based on idList.
def convert_arg_line_to_args
Allow files of arguments referenced by @&lt;path&gt; to contain multiple values on each line...
def getDottedAttr
Like getattr, but accepts hierarchical names, e.g.
def __call__
Load one or more files of config overrides.
def isDynamicDatasetType
Is the dataset type dynamic (specified on the command line)?
def __call__
Override one or more config name value pairs.
argparse action callback to process a data ID into a dict
def handleCamera
Perform camera-specific operations before parsing the command line.
def parse_args
Parse arguments for a pipeline task.
def _processDataIds
Process the parsed data for each data ID argument.
def __init__
Construct an ArgumentParser.
def obeyShowArgument
Process arguments specified with –show (but ignores &quot;data&quot;)
argparse action to set trace level
def _fixPath
Apply environment variable as default root, if present, and abspath.
argparse action to load config overrides from one or more files
def __init__
Construct a DataIdContainer.
A container for data IDs and associated data references.