@@ -668,6 +668,12 @@ def __set__(self, instance, value, at=None, label="assignment"):
668668 if instance ._frozen :
669669 raise FieldValidationError (self , instance , "Cannot modify a frozen Config" )
670670
671+ if at is None :
672+ at = getCallStack ()
673+ # setDefaults() gets a free pass due to our mashing of inheritance
674+ if self .name not in instance ._fields :
675+ raise AttributeError ("{} has no attribute {}" .format (instance .__class__ .__name__ , self .name ))
676+
671677 history = instance ._history .setdefault (self .name , [])
672678 if value is not None :
673679 value = _autocast (value , self .dtype )
@@ -835,6 +841,9 @@ class behavior.
835841 ['coffee', 'green tea', 'water', 'earl grey tea']
836842 """
837843
844+ # Only _fields are exposure. _storage retains items that have been
845+ # deleted.
846+
838847 def __iter__ (self ):
839848 """Iterate over fields."""
840849 return self ._fields .__iter__ ()
@@ -851,7 +860,7 @@ def keys(self):
851860 --------
852861 lsst.pex.config.Config.iterkeys
853862 """
854- return list (self ._storage . keys () )
863+ return list (self ._fields )
855864
856865 def values (self ):
857866 """Get field values.
@@ -865,7 +874,7 @@ def values(self):
865874 --------
866875 lsst.pex.config.Config.itervalues
867876 """
868- return list ( self ._storage .values () )
877+ return self .toDict () .values ()
869878
870879 def items (self ):
871880 """Get configurations as ``(field name, field value)`` pairs.
@@ -882,7 +891,7 @@ def items(self):
882891 --------
883892 lsst.pex.config.Config.iteritems
884893 """
885- return list ( self ._storage .items () )
894+ return self .toDict () .items ()
886895
887896 def iteritems (self ):
888897 """Iterate over (field name, field value) pairs.
@@ -899,7 +908,7 @@ def iteritems(self):
899908 --------
900909 lsst.pex.config.Config.items
901910 """
902- return iter ( self ._storage . items () )
911+ return self .toDict (). iteritems ( )
903912
904913 def itervalues (self ):
905914 """Iterate over field values.
@@ -913,7 +922,7 @@ def itervalues(self):
913922 --------
914923 lsst.pex.config.Config.values
915924 """
916- return iter ( self .storage . values () )
925+ return self .toDict (). itervalues ( )
917926
918927 def iterkeys (self ):
919928 """Iterate over field names
@@ -927,14 +936,22 @@ def iterkeys(self):
927936 --------
928937 lsst.pex.config.Config.values
929938 """
930- return iter (self .storage .keys ())
939+ return self .toDict ().iterkeys ()
940+
941+ def iterfields (self ):
942+ """Iterate over field objects"""
943+ return iter (self ._fields .values ())
944+
945+ def doc (self , field ):
946+ """Return docstring for field"""
947+ return self ._fields [field ].doc
931948
932949 def __contains__ (self , name ):
933950 """!Return True if the specified field exists in this config
934951
935952 @param[in] name field name to test for
936953 """
937- return self ._storage .__contains__ (name )
954+ return self ._storage .__contains__ (name ) and name in self . _fields
938955
939956 def __new__ (cls , * args , ** kw ):
940957 """Allocate a new `lsst.pex.config.Config` object.
@@ -960,9 +977,7 @@ def __new__(cls, *args, **kw):
960977 instance ._history = {}
961978 instance ._imports = set ()
962979 # load up defaults
963- for field in instance ._fields .values ():
964- instance ._history [field .name ] = []
965- field .__set__ (instance , field .default , at = at + [field .source ], label = "default" )
980+ instance .reset (at = at )
966981 # set custom default-overides
967982 instance .setDefaults ()
968983 # set constructor overides
@@ -982,6 +997,14 @@ def __reduce__(self):
982997 self .saveToStream (stream )
983998 return (unreduceConfig , (self .__class__ , stream .getvalue ().encode ()))
984999
1000+ def reset (self , at = None ):
1001+ """Reset all values to their defaults."""
1002+ if at is None :
1003+ at = getCallStack ()
1004+ for field in self ._fields .values ():
1005+ self ._history [field .name ] = []
1006+ field .__set__ (self , field .default , at = at + [field .source ], label = "default" )
1007+
9851008 def setDefaults (self ):
9861009 """Subclass hook for computing defaults.
9871010
@@ -1053,7 +1076,9 @@ def update(self, **kw):
10531076 field = self ._fields [name ]
10541077 field .__set__ (self , value , at = at , label = label )
10551078 except KeyError :
1056- raise KeyError ("No field of name %s exists in config type %s" % (name , _typeStr (self )))
1079+ raise KeyError (
1080+ "{} has no field named {}" .format (type (self ).__name__ .replace ("Config" , "" ), name )
1081+ )
10571082
10581083 def load (self , filename , root = "config" ):
10591084 """Modify this config in place by executing the Python code in a
@@ -1504,11 +1529,12 @@ def __setattr__(self, attr, value, at=None, label="assignment"):
15041529 raise AttributeError ("%s has no attribute %s" % (_typeStr (self ), attr ))
15051530
15061531 def __delattr__ (self , attr , at = None , label = "deletion" ):
1532+ # CJS: Hacked to allow setDefaults() to delete non-existent fields
1533+ if at is None :
1534+ at = getCallStack ()
15071535 if attr in self ._fields :
1508- if at is None :
1509- at = getCallStack ()
1510- self ._fields [attr ].__delete__ (self , at = at , label = label )
1511- else :
1536+ del self ._fields [attr ]
1537+ elif not any (stk .function == "setDefaults" for stk in at ):
15121538 object .__delattr__ (self , attr )
15131539
15141540 def __eq__ (self , other ):
0 commit comments