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