23 from builtins
import str
24 from builtins
import object
25 from past.builtins
import long
36 from .comparison
import getComparisonName, compareScalars, compareConfigs
37 from future.utils
import with_metaclass
39 __all__ = (
"Config",
"Field",
"FieldValidationError")
44 Utility function for generating nested configuration names
46 if not prefix
and not name:
47 raise ValueError(
"Invalid name: cannot be None")
51 name = prefix +
"." + name
54 return "%s[%r]" % (name, index)
61 If appropriate perform type casting of value x to type dtype,
62 otherwise return the original value x
64 if dtype == float
and isinstance(x, int):
66 if dtype == int
and isinstance(x, long):
68 if isinstance(x, str):
75 Utility function to generate a fully qualified type name.
77 This is used primarily in writing config files to be
78 executed later upon 'load'.
80 if hasattr(x,
'__module__')
and hasattr(x,
'__name__'):
84 if (sys.version_info.major <= 2
and xtype.__module__ ==
'__builtin__')
or xtype.__module__ ==
'builtins':
87 return "%s.%s" % (xtype.__module__, xtype.__name__)
91 """A metaclass for Config
93 Adds a dictionary containing all Field class attributes
94 as a class attribute called '_fields', and adds the name of each field as
95 an instance variable of the field itself (so you don't have to pass the
96 name of the field to the field constructor).
99 type.__init__(self, name, bases, dict_)
101 self.
_source = traceback.extract_stack(limit=2)[0]
103 def getFields(classtype):
105 bases = list(classtype.__bases__)
108 fields.update(getFields(b))
110 for k, v
in classtype.__dict__.items():
111 if isinstance(v, Field):
115 fields = getFields(self)
116 for k, v
in fields.items():
117 setattr(self, k, copy.deepcopy(v))
120 if isinstance(value, Field):
123 type.__setattr__(self, name, value)
128 Custom exception class which holds additional information useful to
129 debuggin Config errors:
130 - fieldType: type of the Field which incurred the error
131 - fieldName: name of the Field which incurred the error
132 - fullname: fully qualified name of the Field instance
133 - history: full history of all changes to the Field instance
134 - fieldSource: file and line number of the Field definition
140 self.
history = config.history.setdefault(field.name, [])
143 error =
"%s '%s' failed validation: %s\n"\
144 "For more information read the Field definition at:\n%s"\
145 "And the Config definition at:\n%s" % \
146 (self.fieldType.__name__, self.
fullname, msg,
149 ValueError.__init__(self, error)
153 """A field in a a Config.
155 Instances of Field should be class attributes of Config subclasses:
156 Field only supports basic data types (int, float, complex, bool, str)
158 class Example(Config):
159 myInt = Field(int, "an integer field!", default=0)
163 supportedTypes = set((str, oldStringType, bool, float, int, complex))
165 def __init__(self, doc, dtype, default=None, check=None, optional=False):
166 """Initialize a Field.
168 dtype ------ Data type for the field.
169 doc -------- Documentation for the field.
170 default ---- A default value for the field.
171 check ------ A callable to be called with the field value that returns
172 False if the value is invalid. More complex inter-field
173 validation can be written as part of Config validate()
174 method; this will be ignored if set to None.
175 optional --- When False, Config validate() will fail if value is None
178 raise ValueError(
"Unsupported Field dtype %s" %
_typeStr(dtype))
182 dtype = oldStringType
184 source = traceback.extract_stack(limit=2)[0]
185 self.
_setup(doc=doc, dtype=dtype, default=default, check=check, optional=optional, source=source)
187 def _setup(self, doc, dtype, default, check, optional, source):
189 Convenience function provided to simplify initialization of derived
202 Rename an instance of this field, not the field itself.
203 This is invoked by the owning config object and should not be called
206 Only useful for fields which hold sub-configs.
207 Fields which hold subconfigs should rename each sub-config with
208 the full field name as generated by _joinNamePath
214 Base validation for any field.
215 Ensures that non-optional fields are not None.
216 Ensures type correctness
217 Ensures that user-provided check function is valid
218 Most derived Field types should call Field.validate if they choose
219 to re-implement validate
221 value = self.__get__(instance)
222 if not self.optional
and value
is None:
223 raise FieldValidationError(self, instance,
"Required value cannot be None")
227 Make this field read-only.
228 Only important for fields which hold sub-configs.
229 Fields which hold subconfigs should freeze each sub-config.
235 Validate a value that is not None
237 This is called from __set__
238 This is not part of the Field API. However, simple derived field types
239 may benifit from implementing _validateValue
244 if not isinstance(value, self.dtype):
245 msg =
"Value %s is of incorrect type %s. Expected type %s" % \
248 if self.check
is not None and not self.check(value):
249 msg =
"Value %s is not a valid value" % str(value)
250 raise ValueError(msg)
252 def save(self, outfile, instance):
254 Saves an instance of this field to file.
255 This is invoked by the owning config object, and should not be called
258 outfile ---- an open output stream.
264 doc =
"# " + str(self.
doc).replace(
"\n",
"\n# ")
265 if isinstance(value, float)
and (math.isinf(value)
or math.isnan(value)):
267 outfile.write(
u"{}\n{}=float('{!r}')\n\n".
format(doc, fullname, value))
269 outfile.write(
u"{}\n{}={!r}\n\n".
format(doc, fullname, value))
273 Convert the field value so that it can be set as the value of an item
275 This is invoked by the owning config object and should not be called
278 Simple values are passed through. Complex data structures must be
279 manipulated. For example, a field holding a sub-config should, instead
280 of the subconfig object, return a dict where the keys are the field
281 names in the subconfig, and the values are the field values in the
286 def __get__(self, instance, owner=None, at=None, label="default"):
288 Define how attribute access should occur on the Config instance
289 This is invoked by the owning config object and should not be called
292 When the field attribute is accessed on a Config class object, it
293 returns the field object itself in order to allow inspection of
296 When the field attribute is access on a config instance, the actual
297 value described by the field (and held by the Config instance) is
300 if instance
is None or not isinstance(instance, Config):
303 return instance._storage[self.name]
305 def __set__(self, instance, value, at=None, label='assignment'):
307 Describe how attribute setting should occur on the config instance.
308 This is invoked by the owning config object and should not be called
311 Derived Field classes may need to override the behavior. When overriding
312 __set__, Field authors should follow the following rules:
313 * Do not allow modification of frozen configs
314 * Validate the new value *BEFORE* modifying the field. Except if the
315 new value is None. None is special and no attempt should be made to
316 validate it until Config.validate is called.
317 * Do not modify the Config instance to contain invalid values.
318 * If the field is modified, update the history of the field to reflect the
321 In order to decrease the need to implement this method in derived Field
322 types, value validation is performed in the method _validateValue. If
323 only the validation step differs in the derived Field, it is simpler to
324 implement _validateValue than to re-implement __set__. More complicated
325 behavior, however, may require a reimplementation.
330 history = instance._history.setdefault(self.name, [])
331 if value
is not None:
335 except BaseException
as e:
338 instance._storage[self.name] = value
340 at = traceback.extract_stack()[:-1]
341 history.append((value, at, label))
345 Describe how attribute deletion should occur on the Config instance.
346 This is invoked by the owning config object and should not be called
350 at = traceback.extract_stack()[:-1]
351 self.
__set__(instance,
None, at=at, label=label)
353 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
354 """Helper function for Config.compare; used to compare two fields for equality.
356 Must be overridden by more complex field types.
358 @param[in] instance1 LHS Config instance to compare.
359 @param[in] instance2 RHS Config instance to compare.
360 @param[in] shortcut If True, return as soon as an inequality is found.
361 @param[in] rtol Relative tolerance for floating point comparisons.
362 @param[in] atol Absolute tolerance for floating point comparisons.
363 @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
364 to report inequalities.
366 Floating point comparisons are performed by numpy.allclose; refer to that for details.
368 v1 = getattr(instance1, self.name)
369 v2 = getattr(instance2, self.name)
378 """An Importer (for sys.meta_path) that records which modules are being imported.
380 Objects also act as Context Managers, so you can:
381 with RecordingImporter() as importer:
383 print("Imported: " + importer.getModules())
384 This ensures it is properly uninstalled when done.
386 This class makes no effort to do any importing itself.
389 """Create and install the Importer"""
395 sys.meta_path = [self] + sys.meta_path
403 """Uninstall the Importer"""
407 """Called as part of the 'import' chain of events.
409 We return None because we don't do any importing.
411 self._modules.add(fullname)
415 """Return the set of modules that were imported."""
420 """Base class for control objects.
422 A Config object will usually have several Field instances as class
423 attributes; these are used to define most of the base class behavior.
424 Simple derived class should be able to be defined simply by setting those
427 Config also emulates a dict of field name: field value
431 """!Iterate over fields
433 return self._fields.__iter__()
436 """!Return the list of field names
438 return list(self._storage.keys())
441 """!Return the list of field values
443 return list(self._storage.values())
446 """!Return the list of (field name, field value) pairs
448 return list(self._storage.items())
451 """!Iterate over (field name, field value) pairs
453 return iter(self._storage.items())
456 """!Iterate over field values
458 return iter(self.storage.values())
461 """!Iterate over field names
463 return iter(self.storage.keys())
466 """!Return True if the specified field exists in this config
468 @param[in] name field name to test for
470 return self._storage.__contains__(name)
473 """!Allocate a new Config object.
475 In order to ensure that all Config object are always in a proper
476 state when handed to users or to derived Config classes, some
477 attributes are handled at allocation time rather than at initialization
479 This ensures that even if a derived Config class implements __init__,
480 the author does not need to be concerned about when or even if he
481 should call the base Config.__init__
483 name = kw.pop(
"__name",
None)
484 at = kw.pop(
"__at", traceback.extract_stack()[:-1])
486 kw.pop(
"__label",
"default")
488 instance = object.__new__(cls)
489 instance._frozen =
False
490 instance._name = name
491 instance._storage = {}
492 instance._history = {}
493 instance._imports = set()
495 for field
in instance._fields.values():
496 instance._history[field.name] = []
497 field.__set__(instance, field.default, at=at+[field.source], label=
"default")
499 instance.setDefaults()
501 instance.update(__at=at, **kw)
505 """Reduction for pickling (function with arguments to reproduce).
507 We need to condense and reconstitute the Config, since it may contain lambdas
508 (as the 'check' elements) that cannot be pickled.
511 stream = io.StringIO()
513 return (unreduceConfig, (self.__class__, stream.getvalue().encode()))
517 Derived config classes that must compute defaults rather than using the
518 Field defaults should do so here.
519 To correctly use inherited defaults, implementations of setDefaults()
520 must call their base class' setDefaults()
525 """!Update values specified by the keyword arguments
527 @warning The '__at' and '__label' keyword arguments are special internal
528 keywords. They are used to strip out any internal steps from the
529 history tracebacks of the config. Modifying these keywords allows users
530 to lie about a Config's history. Please do not do so!
532 at = kw.pop(
"__at", traceback.extract_stack()[:-1])
533 label = kw.pop(
"__label",
"update")
535 for name, value
in kw.items():
537 field = self._fields[name]
538 field.__set__(self, value, at=at, label=label)
540 raise KeyError(
"No field of name %s exists in config type %s" % (name,
_typeStr(self)))
542 def load(self, filename, root="config"):
543 """!Modify this config in place by executing the Python code in the named file.
545 @param[in] filename name of file containing config override code
546 @param[in] root name of variable in file that refers to the config being overridden
548 For example: if the value of root is "config" and the file contains this text:
549 "config.myField = 5" then this config's field "myField" is set to 5.
551 @deprecated For purposes of backwards compatibility, older config files that use
552 root="root" instead of root="config" will be loaded with a warning printed to sys.stderr.
553 This feature will be removed at some point.
555 with open(filename,
"r") as f:
556 code = compile(f.read(), filename=filename, mode="exec")
560 """!Modify this config in place by executing the python code in the provided stream.
562 @param[in] stream open file object, string or compiled string containing config override code
563 @param[in] root name of variable in stream that refers to the config being overridden
564 @param[in] filename name of config override file, or None if unknown or contained
565 in the stream; used for error reporting
567 For example: if the value of root is "config" and the stream contains this text:
568 "config.myField = 5" then this config's field "myField" is set to 5.
570 @deprecated For purposes of backwards compatibility, older config files that use
571 root="root" instead of root="config" will be loaded with a warning printed to sys.stderr.
572 This feature will be removed at some point.
577 exec(stream, {}, local)
578 except NameError
as e:
579 if root ==
"config" and "root" in e.args[0]:
583 filename = getattr(stream,
"co_filename",
None)
585 filename = getattr(stream,
"name",
"?")
586 sys.stderr.write(
u"Config override file %r" % (filename,) +
587 u" appears to use 'root' instead of 'config'; trying with 'root'")
588 local = {
"root": self}
589 exec(stream, {}, local)
593 self._imports.update(importer.getModules())
595 def save(self, filename, root="config"):
596 """!Save a python script to the named file, which, when loaded, reproduces this Config
598 @param[in] filename name of file to which to write the config
599 @param[in] root name to use for the root config variable; the same value must be used when loading
601 d = os.path.dirname(filename)
602 with tempfile.NamedTemporaryFile(mode=
"w", delete=
False, dir=d)
as outfile:
607 umask = os.umask(0o077)
609 os.chmod(outfile.name, (~umask & 0o666))
613 shutil.move(outfile.name, filename)
616 """!Save a python script to a stream, which, when loaded, reproduces this Config
618 @param outfile [inout] open file object to which to write the config. Accepts strings not bytes.
619 @param root [in] name to use for the root config variable; the same value must be used when loading
624 configType = type(self)
626 outfile.write(
u"import {}\n".
format(configType.__module__))
627 outfile.write(
u"assert type({})=={}, 'config is of type %s.%s ".
format(root, typeString))
628 outfile.write(
u"instead of {}' % (type({}).__module__, type({}).__name__)\n".
format(typeString,
636 """!Make this Config and all sub-configs read-only
639 for field
in self._fields.values():
643 """!Save this Config to an open stream object
645 for imp
in self._imports:
646 if imp
in sys.modules
and sys.modules[imp]
is not None:
647 outfile.write(
u"import {}\n".
format(imp))
648 for field
in self._fields.values():
649 field.save(outfile, self)
652 """!Return a dict of field name: value
654 Correct behavior is dependent on proper implementation of Field.toDict. If implementing a new
655 Field type, you may need to implement your own toDict method.
658 for name, field
in self._fields.items():
659 dict_[name] = field.toDict(self)
663 """!Rename this Config object in its parent config
665 @param[in] name new name for this config in its parent config
667 Correct behavior is dependent on proper implementation of Field.rename. If implementing a new
668 Field type, you may need to implement your own rename method.
671 for field
in self._fields.values():
675 """!Validate the Config; raise an exception if invalid
677 The base class implementation performs type checks on all fields by
678 calling Field.validate().
680 Complex single-field validation can be defined by deriving new Field
681 types. As syntactic sugar, some derived Field types are defined in
682 this module which handle recursing into sub-configs
683 (ConfigField, ConfigChoiceField)
685 Inter-field relationships should only be checked in derived Config
686 classes after calling this method, and base validation is complete
688 for field
in self._fields.values():
692 """!Format the specified config field's history to a more human-readable format
694 @param[in] name name of field whose history is wanted
695 @param[in] kwargs keyword arguments for lsst.pex.config.history.format
696 @return a string containing the formatted history
699 return pexHist.format(self, name, **kwargs)
702 Read-only history property
704 history = property(
lambda x: x._history)
707 """!Regulate which attributes can be set
709 Unlike normal python objects, Config objects are locked such
710 that no additional attributes nor properties may be added to them
713 Although this is not the standard Python behavior, it helps to
714 protect users from accidentally mispelling a field name, or
715 trying to set a non-existent field.
719 at = traceback.extract_stack()[:-1]
721 self.
_fields[attr].__set__(self, value, at=at, label=label)
722 elif hasattr(getattr(self.__class__, attr,
None),
'__set__'):
724 return object.__setattr__(self, attr, value)
725 elif attr
in self.__dict__
or attr
in (
"_name",
"_history",
"_storage",
"_frozen",
"_imports"):
727 self.__dict__[attr] = value
730 raise AttributeError(
"%s has no attribute %s" % (
_typeStr(self), attr))
735 at = traceback.extract_stack()[:-1]
736 self.
_fields[attr].__delete__(self, at=at, label=label)
738 object.__delattr__(self, attr)
741 if type(other) == type(self):
743 thisValue = getattr(self, name)
744 otherValue = getattr(other, name)
745 if isinstance(thisValue, float)
and math.isnan(thisValue):
746 if not math.isnan(otherValue):
748 elif thisValue != otherValue:
754 return not self.
__eq__(other)
762 ", ".join(
"%s=%r" % (k, v)
for k, v
in self.
toDict().
items()
if v
is not None)
765 def compare(self, other, shortcut=True, rtol=1E-8, atol=1E-8, output=None):
766 """!Compare two Configs for equality; return True if equal
768 If the Configs contain RegistryFields or ConfigChoiceFields, unselected Configs
769 will not be compared.
771 @param[in] other Config object to compare with self.
772 @param[in] shortcut If True, return as soon as an inequality is found.
773 @param[in] rtol Relative tolerance for floating point comparisons.
774 @param[in] atol Absolute tolerance for floating point comparisons.
775 @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
776 to report inequalities.
778 Floating point comparisons are performed by numpy.allclose; refer to that for details.
780 name1 = self.
_name if self.
_name is not None else "config"
781 name2 = other._name
if other._name
is not None else "config"
784 rtol=rtol, atol=atol, output=output)
789 config.loadFromStream(stream)
def keys
Return the list of field names.
def __setattr__
Regulate which attributes can be set.
def freeze
Make this Config and all sub-configs read-only.
def saveToStream
Save a python script to a stream, which, when loaded, reproduces this Config.
def loadFromStream
Modify this config in place by executing the python code in the provided stream.
def compare
Compare two Configs for equality; return True if equal.
def iteritems
Iterate over (field name, field value) pairs.
def __contains__
Return True if the specified field exists in this config.
def save
Save a python script to the named file, which, when loaded, reproduces this Config.
def _rename
Rename this Config object in its parent config.
def _save
Save this Config to an open stream object.
def load
Modify this config in place by executing the Python code in the named file.
def itervalues
Iterate over field values.
def iterkeys
Iterate over field names.
def toDict
Return a dict of field name: value.
def __iter__
Iterate over fields.
def formatHistory
Format the specified config field's history to a more human-readable format.
def items
Return the list of (field name, field value) pairs.
def update
Update values specified by the keyword arguments.
def validate
Validate the Config; raise an exception if invalid.
def __new__
Allocate a new Config object.
def values
Return the list of field values.