LSSTApplications  8.0.0.0+107,8.0.0.1+13,9.1+18,9.2,master-g084aeec0a4,master-g0aced2eed8+6,master-g15627eb03c,master-g28afc54ef9,master-g3391ba5ea0,master-g3d0fb8ae5f,master-g4432ae2e89+36,master-g5c3c32f3ec+17,master-g60f1e072bb+1,master-g6a3ac32d1b,master-g76a88a4307+1,master-g7bce1f4e06+57,master-g8ff4092549+31,master-g98e65bf68e,master-ga6b77976b1+53,master-gae20e2b580+3,master-gb584cd3397+53,master-gc5448b162b+1,master-gc54cf9771d,master-gc69578ece6+1,master-gcbf758c456+22,master-gcec1da163f+63,master-gcf15f11bcc,master-gd167108223,master-gf44c96c709
LSSTDataManagementBasePackage
config.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 
23 import io
24 import traceback
25 import sys
26 import math
27 import collections
28 import copy
29 
30 from .comparison import *
31 
32 __all__ = ("Config", "Field", "FieldValidationError")
33 
34 def _joinNamePath(prefix=None, name=None, index=None):
35  """
36  Utility function for generating nested configuration names
37  """
38  if not prefix and not name:
39  raise ValueError("Invalid name: cannot be None")
40  elif not name:
41  name = prefix
42  elif prefix and name:
43  name = prefix + "." + name
44 
45  if index is not None:
46  return "%s[%r]"%(name, index)
47  else:
48  return name
49 
50 def _autocast(x, dtype):
51  """
52  If appropriate perform type casting of value x to type dtype,
53  otherwise return the original value x
54  """
55  if dtype==float and type(x)==int:
56  return float(x)
57  if dtype==int and type(x)==long:
58  return int(x)
59  return x
60 
61 def _typeStr(x):
62  """
63  Utility function to generate a fully qualified type name.
64 
65  This is used primarily in writing config files to be
66  executed later upon 'load'.
67  """
68  if hasattr(x, '__module__') and hasattr(x, '__name__'):
69  xtype = x
70  else:
71  xtype = type(x)
72  if xtype.__module__ == '__builtin__':
73  return xtype.__name__
74  else:
75  return "%s.%s"%(xtype.__module__, xtype.__name__)
76 
77 class ConfigMeta(type):
78  """A metaclass for Config
79 
80  Adds a dictionary containing all Field class attributes
81  as a class attribute called '_fields', and adds the name of each field as
82  an instance variable of the field itself (so you don't have to pass the
83  name of the field to the field constructor).
84  """
85  def __init__(self, name, bases, dict_):
86  type.__init__(self, name, bases, dict_)
87  self._fields = {}
88  self._source = traceback.extract_stack(limit=2)[0]
89  def getFields(classtype):
90  fields = {}
91  bases=list(classtype.__bases__)
92  bases.reverse()
93  for b in bases:
94  fields.update(getFields(b))
95 
96  for k, v in classtype.__dict__.iteritems():
97  if isinstance(v, Field):
98  fields[k] = v
99  return fields
100 
101  fields = getFields(self)
102  for k, v in fields.iteritems():
103  setattr(self, k, copy.deepcopy(v))
104 
105  def __setattr__(self, name, value):
106  if isinstance(value, Field):
107  value.name = name
108  self._fields[name] = value
109  type.__setattr__(self, name, value)
110 
111 class FieldValidationError(ValueError):
112  """
113  Custom exception class which holds additional information useful to
114  debuggin Config errors:
115  - fieldType: type of the Field which incurred the error
116  - fieldName: name of the Field which incurred the error
117  - fullname: fully qualified name of the Field instance
118  - history: full history of all changes to the Field instance
119  - fieldSource: file and line number of the Field definition
120  """
121  def __init__(self, field, config, msg):
122  self.fieldType = type(field)
123  self.fieldName = field.name
124  self.fullname = _joinNamePath(config._name, field.name)
125  self.history = config.history.setdefault(field.name, [])
126  self.fieldSource = field.source
127  self.configSource = config._source
128  error="%s '%s' failed validation: %s\n"\
129  "For more information read the Field definition at:\n%s"\
130  "And the Config definition at:\n%s"%\
131  (self.fieldType.__name__, self.fullname, msg,
132  traceback.format_list([self.fieldSource])[0],
133  traceback.format_list([self.configSource])[0])
134  ValueError.__init__(self, error)
135 
136 
137 class Field(object):
138  """A field in a a Config.
139 
140  Instances of Field should be class attributes of Config subclasses:
141  Field only supports basic data types (int, float, complex, bool, str)
142 
143  class Example(Config):
144  myInt = Field(int, "an integer field!", default=0)
145  """
146  supportedTypes=(str, bool, float, int, complex)
147 
148  def __init__(self, doc, dtype, default=None, check=None, optional=False):
149  """Initialize a Field.
150 
151  dtype ------ Data type for the field.
152  doc -------- Documentation for the field.
153  default ---- A default value for the field.
154  check ------ A callable to be called with the field value that returns
155  False if the value is invalid. More complex inter-field
156  validation can be written as part of Config validate()
157  method; this will be ignored if set to None.
158  optional --- When False, Config validate() will fail if value is None
159  """
160  if dtype not in self.supportedTypes:
161  raise ValueError("Unsupported Field dtype %s"%_typeStr(dtype))
162  source = traceback.extract_stack(limit=2)[0]
163  self._setup(doc=doc, dtype=dtype, default=default, check=check, optional=optional, source=source)
164 
165 
166  def _setup(self, doc, dtype, default, check, optional, source):
167  """
168  Convenience function provided to simplify initialization of derived
169  Field types
170  """
171  self.dtype = dtype
172  self.doc = doc
173  self.__doc__ = doc
174  self.default = default
175  self.check = check
176  self.optional = optional
177  self.source = source
178 
179  def rename(self, instance):
180  """
181  Rename an instance of this field, not the field itself.
182  This is invoked by the owning config object and should not be called
183  directly
184 
185  Only useful for fields which hold sub-configs.
186  Fields which hold subconfigs should rename each sub-config with
187  the full field name as generated by _joinNamePath
188  """
189  pass
190 
191  def validate(self, instance):
192  """
193  Base validation for any field.
194  Ensures that non-optional fields are not None.
195  Ensures type correctness
196  Ensures that user-provided check function is valid
197  Most derived Field types should call Field.validate if they choose
198  to re-implement validate
199  """
200  value = self.__get__(instance)
201  if not self.optional and value is None:
202  raise FieldValidationError(self, instance, "Required value cannot be None")
203 
204  def freeze(self, instance):
205  """
206  Make this field read-only.
207  Only important for fields which hold sub-configs.
208  Fields which hold subconfigs should freeze each sub-config.
209  """
210  pass
211 
212  def _validateValue(self, value):
213  """
214  Validate a value that is not None
215 
216  This is called from __set__
217  This is not part of the Field API. However, simple derived field types
218  may benifit from implementing _validateValue
219  """
220  if value is None:
221  return
222 
223  if not isinstance(value, self.dtype):
224  msg = "Value %s is of incorrect type %s. Expected type %s"%\
225  (value, _typeStr(value), _typeStr(self.dtype))
226  raise TypeError(msg)
227  if self.check is not None and not self.check(value):
228  msg = "Value %s is not a valid value"%str(value)
229  raise ValueError(msg)
230 
231  def save(self, outfile, instance):
232  """
233  Saves an instance of this field to file.
234  This is invoked by the owning config object, and should not be called
235  directly
236 
237  outfile ---- an open output stream.
238  """
239  value = self.__get__(instance)
240  fullname = _joinNamePath(instance._name, self.name)
241  if isinstance(value, float) and (math.isinf(value) or math.isnan(value)):
242  # non-finite numbers need special care
243  print >> outfile, "%s=float('%r')"%(fullname, value)
244  else:
245  print >> outfile, "%s=%r"%(fullname, value)
246 
247  def toDict(self, instance):
248  """
249  Convert the field value so that it can be set as the value of an item
250  in a dict.
251  This is invoked by the owning config object and should not be called
252  directly
253 
254  Simple values are passed through. Complex data structures must be
255  manipulated. For example, a field holding a sub-config should, instead
256  of the subconfig object, return a dict where the keys are the field
257  names in the subconfig, and the values are the field values in the
258  subconfig.
259  """
260  return self.__get__(instance)
261 
262  def __get__(self, instance, owner=None, at=None, label="default"):
263  """
264  Define how attribute access should occur on the Config instance
265  This is invoked by the owning config object and should not be called
266  directly
267 
268  When the field attribute is accessed on a Config class object, it
269  returns the field object itself in order to allow inspection of
270  Config classes.
271 
272  When the field attribute is access on a config instance, the actual
273  value described by the field (and held by the Config instance) is
274  returned.
275  """
276  if instance is None or not isinstance(instance, Config):
277  return self
278  else:
279  return instance._storage[self.name]
280 
281  def __set__(self, instance, value, at=None, label='assignment'):
282  """
283  Describe how attribute setting should occur on the config instance.
284  This is invoked by the owning config object and should not be called
285  directly
286 
287  Derived Field classes may need to override the behavior. When overriding
288  __set__, Field authors should follow the following rules:
289  * Do not allow modification of frozen configs
290  * Validate the new value *BEFORE* modifying the field. Except if the
291  new value is None. None is special and no attempt should be made to
292  validate it until Config.validate is called.
293  * Do not modify the Config instance to contain invalid values.
294  * If the field is modified, update the history of the field to reflect the
295  changes
296 
297  In order to decrease the need to implement this method in derived Field
298  types, value validation is performed in the method _validateValue. If
299  only the validation step differs in the derived Field, it is simpler to
300  implement _validateValue than to re-implement __set__. More complicated
301  behavior, however, may require a reimplementation.
302  """
303  if instance._frozen:
304  raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
305 
306  history = instance._history.setdefault(self.name, [])
307  if value is not None:
308  value = _autocast(value, self.dtype)
309  try:
310  self._validateValue(value)
311  except BaseException, e:
312  raise FieldValidationError(self, instance, e.message)
313 
314  instance._storage[self.name] = value
315  if at is None:
316  at = traceback.extract_stack()[:-1]
317  history.append((value, at, label))
318 
319  def __delete__(self, instance, at=None, label='deletion'):
320  """
321  Describe how attribute deletion should occur on the Config instance.
322  This is invoked by the owning config object and should not be called
323  directly
324  """
325  if at is None:
326  at = traceback.extract_stack()[:-1]
327  self.__set__(instance, None, at=at, label=label)
328 
329  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
330  """Helper function for Config.compare; used to compare two fields for equality.
331 
332  Must be overridden by more complex field types.
333 
334  @param[in] instance1 LHS Config instance to compare.
335  @param[in] instance2 RHS Config instance to compare.
336  @param[in] shortcut If True, return as soon as an inequality is found.
337  @param[in] rtol Relative tolerance for floating point comparisons.
338  @param[in] atol Absolute tolerance for floating point comparisons.
339  @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
340  to report inequalities.
341 
342  Floating point comparisons are performed by numpy.allclose; refer to that for details.
343  """
344  v1 = getattr(instance1, self.name)
345  v2 = getattr(instance2, self.name)
346  name = getComparisonName(
347  _joinNamePath(instance1._name, self.name),
348  _joinNamePath(instance2._name, self.name)
349  )
350  return compareScalars(name, v1, v2, dtype=self.dtype, rtol=rtol, atol=atol, output=output)
351 
352 class RecordingImporter(object):
353  """An Importer (for sys.meta_path) that records which modules are being imported.
354 
355  Objects also act as Context Managers, so you can:
356  with RecordingImporter() as importer:
357  import stuff
358  print "Imported: " + importer.getModules()
359  This ensures it is properly uninstalled when done.
360 
361  This class makes no effort to do any importing itself.
362  """
363  def __init__(self):
364  """Create and install the Importer"""
365  self._modules = set()
366 
367  def __enter__(self):
368 
369  self.origMetaPath = sys.meta_path
370  sys.meta_path = [self] + sys.meta_path
371  return self
372 
373  def __exit__(self, *args):
374  self.uninstall()
375  return False # Don't suppress exceptions
376 
377  def uninstall(self):
378  """Uninstall the Importer"""
379  sys.meta_path = self.origMetaPath
380 
381  def find_module(self, fullname, path=None):
382  """Called as part of the 'import' chain of events.
383 
384  We return None because we don't do any importing.
385  """
386  self._modules.add(fullname)
387  return None
388 
389  def getModules(self):
390  """Return the set of modules that were imported."""
391  return self._modules
392 
393 class Config(object):
394  """Base class for control objects.
395 
396  A Config object will usually have several Field instances as class
397  attributes; these are used to define most of the base class behavior.
398  Simple derived class should be able to be defined simply by setting those
399  attributes.
400  """
401 
402  __metaclass__ = ConfigMeta
403 
404  def __iter__(self):
405  """
406  Enable iterating over a config allows inspection of a Config's fields,
407  so that a Config behaves like a dict mapping field names to field
408  descriptors
409  """
410  return self._fields.__iter__()
411 
412  def keys(self):
413  """
414  Return the list of field names
415  """
416  return self._storage.keys()
417  def values(self):
418  """
419  Return the list of field values
420  """
421  return self._storage.values()
422  def items(self):
423  """
424  Return the list of (field name, field value) pairs
425  """
426  return self._storage.items()
427 
428  def iteritems(self):
429  """
430  Enable iterate over (field name, field value) pairs
431  """
432  return self._storage.iteritems()
433  def itervalues(self):
434  """
435  Enable iteration over field values
436  """
437  return self.storage.itervalues()
438  def iterkeys(self):
439  """
440  Enable iteration over field names
441  """
442  return self.storage.iterkeys()
443 
444  def __contains__(self, name):
445  """
446  Determines whether the field 'name' exists in this config
447  """
448  return self._storage.__contains__(name)
449 
450  def __new__(cls, *args, **kw):
451  """
452  Allocate a new Config object.
453 
454  In order to ensure that all Config object are always in a proper
455  state when handed to users or to derived Config classes, some
456  attributes are handled at allocation time rather than at initialization
457 
458  This ensures that even if a derived Config class implements __init__,
459  the author does not need to be concerned about when or even if he
460  should call the base Config.__init__
461  """
462  name=kw.pop("__name", None)
463  at=kw.pop("__at", traceback.extract_stack()[:-1])
464  label=kw.pop("__label", "default")
465 
466  instance = object.__new__(cls)
467  instance._frozen=False
468  instance._name=name
469  instance._storage = {}
470  instance._history = {}
471  instance._imports = set()
472  # load up defaults
473  for field in instance._fields.itervalues():
474  instance._history[field.name]=[]
475  field.__set__(instance, field.default, at=at+[field.source], label="default")
476  # set custom default-overides
477  instance.setDefaults()
478  # set constructor overides
479  instance.update(__at=at, **kw)
480  return instance
481 
482  def __reduce__(self):
483  """Reduction for pickling (function with arguments to reproduce).
484 
485  We need to condense and reconstitute the Config, since it may contain lambdas
486  (as the 'check' elements) that cannot be pickled.
487  """
488  stream = io.BytesIO()
489  self.saveToStream(stream)
490  return (unreduceConfig, (self.__class__, stream.getvalue()))
491 
492  def setDefaults(self):
493  """
494  Derived config classes that must compute defaults rather than using the
495  Field defaults should do so here.
496  To correctly use inherited defaults, implementations of setDefaults()
497  must call their base class' setDefaults()
498  """
499  pass
500 
501  def update(self, **kw):
502  """
503  Treat the Config as a dict, updating values as provided by the keyword
504  arguments.
505 
506  The '__at' and '__label' keyword arguments are special internal
507  keywords. They are used to strip out any internal steps from the
508  history tracebacks of the config. Modifying these keywords allows users
509  to lie about a Config's history. Please do not do so!
510  """
511  at=kw.pop("__at", traceback.extract_stack()[:-1])
512  label = kw.pop("__label", "update")
513 
514  for name, value in kw.iteritems():
515  try:
516  field = self._fields[name]
517  field.__set__(self, value, at=at, label=label)
518  except KeyError, e:
519  raise KeyError("No field of name %s exists in config type %s"%(name, _typeStr(self)))
520 
521  def load(self, filename, root="root"):
522  """
523  Load override from files, modify this config in place by executing the
524  Python code in the given file.
525 
526  The file should modify a Config named root
527 
528  For example:
529  root.myField = 5
530  """
531  with RecordingImporter() as importer:
532  local = {root: self}
533  execfile(filename, {}, local)
534  self._imports.update(importer.getModules())
535 
536  def loadFromStream(self, stream, root="root"):
537  """
538  Modify this config in place by executign the python code in the
539  provided stream.
540 
541  The stream should modify a Config named 'root', e.g.: root.myField = 5
542  """
543  with RecordingImporter() as importer:
544  local = {root: self}
545  exec stream in {}, local
546  self._imports.update(importer.getModules())
547 
548  def save(self, filename, root="root"):
549  """
550  Generates a python script at the given filename, which, when loaded,
551  reproduces this Config.
552 
553  @param filename [in] name of file to write to
554  @param root [in] name to use for the root config variable
555  If not "root", must match what is used in load())
556  """
557  with open(filename, 'w') as outfile:
558  self.saveToStream(outfile, root)
559 
560  def saveToStream(self, outfile, root="root"):
561  """
562  Generates a python script to the given open file object, which, when
563  loaded, reproduces this Config.
564 
565  @param outfile [inout] open file object to write to
566  @param root [in] name to use for the root config variable
567  If not "root", must match what is used in load())
568  """
569  tmp = self._name
570  self._rename(root)
571  try:
572  configType = type(self)
573  typeString = _typeStr(configType)
574  print >> outfile, "import %s" % (configType.__module__)
575  print >> outfile, "assert type(%s)==%s, 'config is of type %%s.%%s" % (root, typeString), \
576  "instead of %s' %% (type(root).__module__, type(root).__name__)" % (typeString,)
577  self._save(outfile)
578  finally:
579  self._rename(tmp)
580 
581  def freeze(self):
582  """
583  Make this Config, and recursively all sub-configs read-only
584  """
585  self._frozen=True
586  for field in self._fields.itervalues():
587  field.freeze(self)
588 
589  def _save(self, outfile):
590  """
591  Internal use only. Save this Config to an open stream object
592  """
593  for imp in self._imports:
594  if imp in sys.modules and sys.modules[imp] is not None:
595  print >> outfile, "import %s" % imp
596  for field in self._fields.itervalues():
597  field.save(outfile, self)
598 
599  def toDict(self):
600  """
601  Convert this Config into a dict whose keys are field names,
602  and whose values are field values.
603 
604  Correct behavior is dependent on proper implementation of
605  Field.toDict. If implementing a new Field type, you may need to
606  implement your own toDict method.
607  """
608  dict_ = {}
609  for name, field in self._fields.iteritems():
610  dict_[name] = field.toDict(self)
611  return dict_
612 
613  def _rename(self, name):
614  """
615  Internal use only.
616  Rename this Config object to reflect its position in a Config hierarchy
617 
618 
619  Correct behavior is dependent on proper implementation of
620  Field.rename. If implementing a new Field type, you may need to
621  implement your own rename method.
622  """
623  self._name = name
624  for field in self._fields.itervalues():
625  field.rename(self)
626 
627  def validate(self):
628  """
629  Validate the Config.
630 
631  The base class implementation performs type checks on all fields by
632  calling Field.validate().
633 
634  Complex single-field validation can be defined by deriving new Field
635  types. As syntactic sugar, some derived Field types are defined in
636  this module which handle recursing into sub-configs
637  (ConfigField, ConfigChoiceField)
638 
639  Inter-field relationships should only be checked in derived Config
640  classes after calling this method, and base validation is complete
641  """
642  for field in self._fields.itervalues():
643  field.validate(self)
644 
645  def formatHistory(self, name, **kwargs):
646  """
647  Format the config's history to a more human-readable format
648  """
649  import lsst.pex.config.history as pexHist
650  return pexHist.format(self, name, **kwargs)
651 
652  """
653  Read-only history property
654  """
655  history = property(lambda x: x._history)
656 
657  def __setattr__(self, attr, value, at=None, label="assignment"):
658  """
659  Regulate which attributes can be set.
660 
661  Unlike normal python objects, Config objects are locked such
662  that no additional attributes nor properties may be added to them
663  dynamically.
664 
665  Although this is not the standard Python behavior, it helps to
666  protect users from accidentally mispelling a field name, or
667  trying to set a non-existent field.
668  """
669  if attr in self._fields:
670  if at is None:
671  at=traceback.extract_stack()[:-1]
672  # This allows Field descriptors to work.
673  self._fields[attr].__set__(self, value, at=at, label=label)
674  elif hasattr(getattr(self.__class__, attr, None), '__set__'):
675  # This allows properties and other non-Field descriptors to work.
676  return object.__setattr__(self, attr, value)
677  elif attr in self.__dict__ or attr in ("_name", "_history", "_storage", "_frozen", "_imports"):
678  # This allows specific private attributes to work.
679  self.__dict__[attr] = value
680  else:
681  # We throw everything else.
682  raise AttributeError("%s has no attribute %s"%(_typeStr(self), attr))
683 
684  def __delattr__(self, attr, at=None, label="deletion"):
685  if attr in self._fields:
686  if at is None:
687  at=traceback.extract_stack()[:-1]
688  self._fields[attr].__delete__(self, at=at, label=label)
689  else:
690  object.__delattr__(self, attr)
691 
692  def __eq__(self, other):
693  if type(other) == type(self):
694  for name in self._fields:
695  thisValue = getattr(self, name)
696  otherValue = getattr(other, name)
697  if isinstance(thisValue, float) and math.isnan(thisValue):
698  if not math.isnan(otherValue):
699  return False
700  elif thisValue != otherValue:
701  return False
702  return True
703  return False
704 
705  def __ne__(self, other):
706  return not self.__eq__(other)
707 
708  def __str__(self):
709  return str(self.toDict())
710 
711  def __repr__(self):
712  return "%s(%s)" % (
713  _typeStr(self),
714  ", ".join("%s=%r" % (k, v) for k, v in self.toDict().iteritems() if v is not None)
715  )
716 
717  def compare(self, other, shortcut=True, rtol=1E-8, atol=1E-8, output=None):
718  """Compare two Configs for equality.
719 
720  If the Configs contain RegistryFields or ConfigChoiceFields, unselected Configs
721  will not be compared.
722 
723  @param[in] other Config object to compare with self.
724  @param[in] shortcut If True, return as soon as an inequality is found.
725  @param[in] rtol Relative tolerance for floating point comparisons.
726  @param[in] atol Absolute tolerance for floating point comparisons.
727  @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
728  to report inequalities.
729 
730  Floating point comparisons are performed by numpy.allclose; refer to that for details.
731  """
732  name1 = self._name if self._name is not None else "root"
733  name2 = other._name if other._name is not None else "root"
734  name = getComparisonName(name1, name2)
735  return compareConfigs(name, self, other, shortcut=shortcut,
736  rtol=rtol, atol=atol, output=output)
737 
738 def unreduceConfig(cls, stream):
739  config = cls()
740  config.loadFromStream(stream)
741  return config