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