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