23 from builtins
import str
24 from builtins
import object
25 from past.builtins
import long
35 from .comparison
import getComparisonName, compareScalars, compareConfigs
36 from future.utils
import with_metaclass
38 __all__ = (
"Config",
"Field",
"FieldValidationError")
43 Utility function for generating nested configuration names
45 if not prefix
and not name:
46 raise ValueError(
"Invalid name: cannot be None")
50 name = prefix +
"." + name
53 return "%s[%r]" % (name, index)
60 If appropriate perform type casting of value x to type dtype,
61 otherwise return the original value x
63 if dtype == float
and isinstance(x, int):
65 if dtype == int
and isinstance(x, long):
67 if isinstance(x, str):
74 Utility function to generate a fully qualified type name.
76 This is used primarily in writing config files to be
77 executed later upon 'load'.
79 if hasattr(x,
'__module__')
and hasattr(x,
'__name__'):
83 if (sys.version_info.major <= 2
and xtype.__module__ ==
'__builtin__')
or xtype.__module__ ==
'builtins':
86 return "%s.%s" % (xtype.__module__, xtype.__name__)
90 """A metaclass for Config
92 Adds a dictionary containing all Field class attributes
93 as a class attribute called '_fields', and adds the name of each field as
94 an instance variable of the field itself (so you don't have to pass the
95 name of the field to the field constructor).
98 type.__init__(self, name, bases, dict_)
100 self.
_source = traceback.extract_stack(limit=2)[0]
102 def getFields(classtype):
104 bases = list(classtype.__bases__)
107 fields.update(getFields(b))
109 for k, v
in classtype.__dict__.items():
110 if isinstance(v, Field):
114 fields = getFields(self)
115 for k, v
in fields.items():
116 setattr(self, k, copy.deepcopy(v))
119 if isinstance(value, Field):
122 type.__setattr__(self, name, value)
127 Custom exception class which holds additional information useful to
128 debuggin Config errors:
129 - fieldType: type of the Field which incurred the error
130 - fieldName: name of the Field which incurred the error
131 - fullname: fully qualified name of the Field instance
132 - history: full history of all changes to the Field instance
133 - fieldSource: file and line number of the Field definition
139 self.
history = config.history.setdefault(field.name, [])
142 error =
"%s '%s' failed validation: %s\n"\
143 "For more information read the Field definition at:\n%s"\
144 "And the Config definition at:\n%s" % \
145 (self.fieldType.__name__, self.
fullname, msg,
148 ValueError.__init__(self, error)
152 """A field in a a Config.
154 Instances of Field should be class attributes of Config subclasses:
155 Field only supports basic data types (int, float, complex, bool, str)
157 class Example(Config):
158 myInt = Field(int, "an integer field!", default=0)
162 supportedTypes = set((str, oldStringType, bool, float, int, complex))
164 def __init__(self, doc, dtype, default=None, check=None, optional=False):
165 """Initialize a Field.
167 dtype ------ Data type for the field.
168 doc -------- Documentation for the field.
169 default ---- A default value for the field.
170 check ------ A callable to be called with the field value that returns
171 False if the value is invalid. More complex inter-field
172 validation can be written as part of Config validate()
173 method; this will be ignored if set to None.
174 optional --- When False, Config validate() will fail if value is None
177 raise ValueError(
"Unsupported Field dtype %s" %
_typeStr(dtype))
181 dtype = oldStringType
183 source = traceback.extract_stack(limit=2)[0]
184 self.
_setup(doc=doc, dtype=dtype, default=default, check=check, optional=optional, source=source)
186 def _setup(self, doc, dtype, default, check, optional, source):
188 Convenience function provided to simplify initialization of derived
201 Rename an instance of this field, not the field itself.
202 This is invoked by the owning config object and should not be called
205 Only useful for fields which hold sub-configs.
206 Fields which hold subconfigs should rename each sub-config with
207 the full field name as generated by _joinNamePath
213 Base validation for any field.
214 Ensures that non-optional fields are not None.
215 Ensures type correctness
216 Ensures that user-provided check function is valid
217 Most derived Field types should call Field.validate if they choose
218 to re-implement validate
220 value = self.__get__(instance)
221 if not self.optional
and value
is None:
222 raise FieldValidationError(self, instance,
"Required value cannot be None")
226 Make this field read-only.
227 Only important for fields which hold sub-configs.
228 Fields which hold subconfigs should freeze each sub-config.
234 Validate a value that is not None
236 This is called from __set__
237 This is not part of the Field API. However, simple derived field types
238 may benifit from implementing _validateValue
243 if not isinstance(value, self.dtype):
244 msg =
"Value %s is of incorrect type %s. Expected type %s" % \
247 if self.check
is not None and not self.check(value):
248 msg =
"Value %s is not a valid value" % str(value)
249 raise ValueError(msg)
251 def save(self, outfile, instance):
253 Saves an instance of this field to file.
254 This is invoked by the owning config object, and should not be called
257 outfile ---- an open output stream.
263 doc =
"# " + str(self.
doc).replace(
"\n",
"\n# ")
264 if isinstance(value, float)
and (math.isinf(value)
or math.isnan(value)):
266 outfile.write(
u"{}\n{}=float('{!r}')\n\n".
format(doc, fullname, value))
268 outfile.write(
u"{}\n{}={!r}\n\n".
format(doc, fullname, value))
272 Convert the field value so that it can be set as the value of an item
274 This is invoked by the owning config object and should not be called
277 Simple values are passed through. Complex data structures must be
278 manipulated. For example, a field holding a sub-config should, instead
279 of the subconfig object, return a dict where the keys are the field
280 names in the subconfig, and the values are the field values in the
285 def __get__(self, instance, owner=None, at=None, label="default"):
287 Define how attribute access should occur on the Config instance
288 This is invoked by the owning config object and should not be called
291 When the field attribute is accessed on a Config class object, it
292 returns the field object itself in order to allow inspection of
295 When the field attribute is access on a config instance, the actual
296 value described by the field (and held by the Config instance) is
299 if instance
is None or not isinstance(instance, Config):
302 return instance._storage[self.name]
304 def __set__(self, instance, value, at=None, label='assignment'):
306 Describe how attribute setting should occur on the config instance.
307 This is invoked by the owning config object and should not be called
310 Derived Field classes may need to override the behavior. When overriding
311 __set__, Field authors should follow the following rules:
312 * Do not allow modification of frozen configs
313 * Validate the new value *BEFORE* modifying the field. Except if the
314 new value is None. None is special and no attempt should be made to
315 validate it until Config.validate is called.
316 * Do not modify the Config instance to contain invalid values.
317 * If the field is modified, update the history of the field to reflect the
320 In order to decrease the need to implement this method in derived Field
321 types, value validation is performed in the method _validateValue. If
322 only the validation step differs in the derived Field, it is simpler to
323 implement _validateValue than to re-implement __set__. More complicated
324 behavior, however, may require a reimplementation.
329 history = instance._history.setdefault(self.name, [])
330 if value
is not None:
334 except BaseException
as e:
337 instance._storage[self.name] = value
339 at = traceback.extract_stack()[:-1]
340 history.append((value, at, label))
344 Describe how attribute deletion should occur on the Config instance.
345 This is invoked by the owning config object and should not be called
349 at = traceback.extract_stack()[:-1]
350 self.
__set__(instance,
None, at=at, label=label)
352 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
353 """Helper function for Config.compare; used to compare two fields for equality.
355 Must be overridden by more complex field types.
357 @param[in] instance1 LHS Config instance to compare.
358 @param[in] instance2 RHS Config instance to compare.
359 @param[in] shortcut If True, return as soon as an inequality is found.
360 @param[in] rtol Relative tolerance for floating point comparisons.
361 @param[in] atol Absolute tolerance for floating point comparisons.
362 @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
363 to report inequalities.
365 Floating point comparisons are performed by numpy.allclose; refer to that for details.
367 v1 = getattr(instance1, self.name)
368 v2 = getattr(instance2, self.name)
377 """An Importer (for sys.meta_path) that records which modules are being imported.
379 Objects also act as Context Managers, so you can:
380 with RecordingImporter() as importer:
382 print("Imported: " + importer.getModules())
383 This ensures it is properly uninstalled when done.
385 This class makes no effort to do any importing itself.
388 """Create and install the Importer"""
394 sys.meta_path = [self] + sys.meta_path
402 """Uninstall the Importer"""
406 """Called as part of the 'import' chain of events.
408 We return None because we don't do any importing.
410 self._modules.add(fullname)
414 """Return the set of modules that were imported."""
419 """Base class for control objects.
421 A Config object will usually have several Field instances as class
422 attributes; these are used to define most of the base class behavior.
423 Simple derived class should be able to be defined simply by setting those
426 Config also emulates a dict of field name: field value
430 """!Iterate over fields
432 return self._fields.__iter__()
435 """!Return the list of field names
437 return list(self._storage.keys())
440 """!Return the list of field values
442 return list(self._storage.values())
445 """!Return the list of (field name, field value) pairs
447 return list(self._storage.items())
450 """!Iterate over (field name, field value) pairs
452 return iter(self._storage.items())
455 """!Iterate over field values
457 return iter(self.storage.values())
460 """!Iterate over field names
462 return iter(self.storage.keys())
465 """!Return True if the specified field exists in this config
467 @param[in] name field name to test for
469 return self._storage.__contains__(name)
472 """!Allocate a new Config object.
474 In order to ensure that all Config object are always in a proper
475 state when handed to users or to derived Config classes, some
476 attributes are handled at allocation time rather than at initialization
478 This ensures that even if a derived Config class implements __init__,
479 the author does not need to be concerned about when or even if he
480 should call the base Config.__init__
482 name = kw.pop(
"__name",
None)
483 at = kw.pop(
"__at", traceback.extract_stack()[:-1])
485 kw.pop(
"__label",
"default")
487 instance = object.__new__(cls)
488 instance._frozen =
False
489 instance._name = name
490 instance._storage = {}
491 instance._history = {}
492 instance._imports = set()
494 for field
in instance._fields.values():
495 instance._history[field.name] = []
496 field.__set__(instance, field.default, at=at+[field.source], label=
"default")
498 instance.setDefaults()
500 instance.update(__at=at, **kw)
504 """Reduction for pickling (function with arguments to reproduce).
506 We need to condense and reconstitute the Config, since it may contain lambdas
507 (as the 'check' elements) that cannot be pickled.
510 stream = io.StringIO()
512 return (unreduceConfig, (self.__class__, stream.getvalue().encode()))
516 Derived config classes that must compute defaults rather than using the
517 Field defaults should do so here.
518 To correctly use inherited defaults, implementations of setDefaults()
519 must call their base class' setDefaults()
524 """!Update values specified by the keyword arguments
526 @warning The '__at' and '__label' keyword arguments are special internal
527 keywords. They are used to strip out any internal steps from the
528 history tracebacks of the config. Modifying these keywords allows users
529 to lie about a Config's history. Please do not do so!
531 at = kw.pop(
"__at", traceback.extract_stack()[:-1])
532 label = kw.pop(
"__label",
"update")
534 for name, value
in kw.items():
536 field = self._fields[name]
537 field.__set__(self, value, at=at, label=label)
539 raise KeyError(
"No field of name %s exists in config type %s" % (name,
_typeStr(self)))
541 def load(self, filename, root="config"):
542 """!Modify this config in place by executing the Python code in the named file.
544 @param[in] filename name of file containing config override code
545 @param[in] root name of variable in file that refers to the config being overridden
547 For example: if the value of root is "config" and the file contains this text:
548 "config.myField = 5" then this config's field "myField" is set to 5.
550 @deprecated For purposes of backwards compatibility, older config files that use
551 root="root" instead of root="config" will be loaded with a warning printed to sys.stderr.
552 This feature will be removed at some point.
554 with open(filename,
"r") as f:
555 code = compile(f.read(), filename=filename, mode="exec")
559 """!Modify this config in place by executing the python code in the provided stream.
561 @param[in] stream open file object, string or compiled string containing config override code
562 @param[in] root name of variable in stream that refers to the config being overridden
563 @param[in] filename name of config override file, or None if unknown or contained
564 in the stream; used for error reporting
566 For example: if the value of root is "config" and the stream contains this text:
567 "config.myField = 5" then this config's field "myField" is set to 5.
569 @deprecated For purposes of backwards compatibility, older config files that use
570 root="root" instead of root="config" will be loaded with a warning printed to sys.stderr.
571 This feature will be removed at some point.
576 exec(stream, {}, local)
577 except NameError
as e:
578 if root ==
"config" and "root" in e.args[0]:
582 filename = getattr(stream,
"co_filename",
None)
584 filename = getattr(stream,
"name",
"?")
585 sys.stderr.write(
u"Config override file %r" % (filename,) +
586 u" appears to use 'root' instead of 'config'; trying with 'root'")
587 local = {
"root": self}
588 exec(stream, {}, local)
592 self._imports.update(importer.getModules())
594 def save(self, filename, root="config"):
595 """!Save a python script to the named file, which, when loaded, reproduces this Config
597 @param[in] filename name of file to which to write the config
598 @param[in] root name to use for the root config variable; the same value must be used when loading
600 d = os.path.dirname(filename)
601 with tempfile.NamedTemporaryFile(mode=
"w", delete=
False, dir=d)
as outfile:
603 os.rename(outfile.name, filename)
606 """!Save a python script to a stream, which, when loaded, reproduces this Config
608 @param outfile [inout] open file object to which to write the config. Accepts strings not bytes.
609 @param root [in] name to use for the root config variable; the same value must be used when loading
614 configType = type(self)
616 outfile.write(
u"import {}\n".
format(configType.__module__))
617 outfile.write(
u"assert type({})=={}, 'config is of type %s.%s ".
format(root, typeString))
618 outfile.write(
u"instead of {}' % (type({}).__module__, type({}).__name__)\n".
format(typeString,
626 """!Make this Config and all sub-configs read-only
629 for field
in self._fields.values():
633 """!Save this Config to an open stream object
635 for imp
in self._imports:
636 if imp
in sys.modules
and sys.modules[imp]
is not None:
637 outfile.write(
u"import {}\n".
format(imp))
638 for field
in self._fields.values():
639 field.save(outfile, self)
642 """!Return a dict of field name: value
644 Correct behavior is dependent on proper implementation of Field.toDict. If implementing a new
645 Field type, you may need to implement your own toDict method.
648 for name, field
in self._fields.items():
649 dict_[name] = field.toDict(self)
653 """!Rename this Config object in its parent config
655 @param[in] name new name for this config in its parent config
657 Correct behavior is dependent on proper implementation of Field.rename. If implementing a new
658 Field type, you may need to implement your own rename method.
661 for field
in self._fields.values():
665 """!Validate the Config; raise an exception if invalid
667 The base class implementation performs type checks on all fields by
668 calling Field.validate().
670 Complex single-field validation can be defined by deriving new Field
671 types. As syntactic sugar, some derived Field types are defined in
672 this module which handle recursing into sub-configs
673 (ConfigField, ConfigChoiceField)
675 Inter-field relationships should only be checked in derived Config
676 classes after calling this method, and base validation is complete
678 for field
in self._fields.values():
682 """!Format the specified config field's history to a more human-readable format
684 @param[in] name name of field whose history is wanted
685 @param[in] kwargs keyword arguments for lsst.pex.config.history.format
686 @return a string containing the formatted history
689 return pexHist.format(self, name, **kwargs)
692 Read-only history property
694 history = property(
lambda x: x._history)
697 """!Regulate which attributes can be set
699 Unlike normal python objects, Config objects are locked such
700 that no additional attributes nor properties may be added to them
703 Although this is not the standard Python behavior, it helps to
704 protect users from accidentally mispelling a field name, or
705 trying to set a non-existent field.
709 at = traceback.extract_stack()[:-1]
711 self.
_fields[attr].__set__(self, value, at=at, label=label)
712 elif hasattr(getattr(self.__class__, attr,
None),
'__set__'):
714 return object.__setattr__(self, attr, value)
715 elif attr
in self.__dict__
or attr
in (
"_name",
"_history",
"_storage",
"_frozen",
"_imports"):
717 self.__dict__[attr] = value
720 raise AttributeError(
"%s has no attribute %s" % (
_typeStr(self), attr))
725 at = traceback.extract_stack()[:-1]
726 self.
_fields[attr].__delete__(self, at=at, label=label)
728 object.__delattr__(self, attr)
731 if type(other) == type(self):
733 thisValue = getattr(self, name)
734 otherValue = getattr(other, name)
735 if isinstance(thisValue, float)
and math.isnan(thisValue):
736 if not math.isnan(otherValue):
738 elif thisValue != otherValue:
744 return not self.
__eq__(other)
752 ", ".join(
"%s=%r" % (k, v)
for k, v
in self.
toDict().
items()
if v
is not None)
755 def compare(self, other, shortcut=True, rtol=1E-8, atol=1E-8, output=None):
756 """!Compare two Configs for equality; return True if equal
758 If the Configs contain RegistryFields or ConfigChoiceFields, unselected Configs
759 will not be compared.
761 @param[in] other Config object to compare with self.
762 @param[in] shortcut If True, return as soon as an inequality is found.
763 @param[in] rtol Relative tolerance for floating point comparisons.
764 @param[in] atol Absolute tolerance for floating point comparisons.
765 @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
766 to report inequalities.
768 Floating point comparisons are performed by numpy.allclose; refer to that for details.
770 name1 = self.
_name if self.
_name is not None else "config"
771 name2 = other._name
if other._name
is not None else "config"
774 rtol=rtol, atol=atol, output=output)
779 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.