@@ -818,6 +818,12 @@ def __set__(
818818 if instance ._frozen :
819819 raise FieldValidationError (self , instance , "Cannot modify a frozen Config" )
820820
821+ if at is None :
822+ at = getCallStack ()
823+ # setDefaults() gets a free pass due to our mashing of inheritance
824+ if self .name not in instance ._fields :
825+ raise AttributeError (f"{ instance .__class__ .__name__ } has no attribute { self .name } " )
826+
821827 history = instance ._history .setdefault (self .name , [])
822828 if value is not None :
823829 value = _autocast (value , self .dtype )
@@ -1004,6 +1010,9 @@ class behavior.
10041010 _history : dict [str , list [Any ]]
10051011 _imports : set [Any ]
10061012
1013+ # Only _fields are exposure. _storage retains items that have been
1014+ # deleted.
1015+
10071016 def __iter__ (self ):
10081017 """Iterate over fields."""
10091018 return self ._fields .__iter__ ()
@@ -1016,7 +1025,7 @@ def keys(self):
10161025 names : `~collections.abc.KeysView`
10171026 List of `lsst.pex.config.Field` names.
10181027 """
1019- return self ._storage . keys ( )
1028+ return list ( self ._fields )
10201029
10211030 def values (self ):
10221031 """Get field values.
@@ -1026,7 +1035,7 @@ def values(self):
10261035 values : `~collections.abc.ValuesView`
10271036 Iterator of field values.
10281037 """
1029- return self ._storage .values ()
1038+ return self .toDict () .values ()
10301039
10311040 def items (self ):
10321041 """Get configurations as ``(field name, field value)`` pairs.
@@ -1039,7 +1048,22 @@ def items(self):
10391048 0. Field name.
10401049 1. Field value.
10411050 """
1042- return self ._storage .items ()
1051+ return self .toDict ().items ()
1052+
1053+ def doc (self , field ):
1054+ """Return docstring for field.
1055+
1056+ Parameters
1057+ ----------
1058+ field : `str`
1059+ Field to select.
1060+
1061+ Returns
1062+ -------
1063+ doc : `str`
1064+ Associated docstring.
1065+ """
1066+ return self ._fields [field ].doc
10431067
10441068 def __contains__ (self , name ):
10451069 """Return `True` if the specified field exists in this config.
@@ -1054,7 +1078,7 @@ def __contains__(self, name):
10541078 in : `bool`
10551079 `True` if the specified field exists in the config.
10561080 """
1057- return self ._storage .__contains__ (name )
1081+ return self ._storage .__contains__ (name ) and name in self . _fields
10581082
10591083 def __new__ (cls , * args , ** kw ):
10601084 """Allocate a new `lsst.pex.config.Config` object.
@@ -1080,9 +1104,7 @@ def __new__(cls, *args, **kw):
10801104 instance ._history = {}
10811105 instance ._imports = set ()
10821106 # load up defaults
1083- for field in instance ._fields .values ():
1084- instance ._history [field .name ] = []
1085- field .__set__ (instance , field .default , at = at + [field .source ], label = "default" )
1107+ instance .reset (at = at )
10861108 # set custom default-overrides
10871109 instance .setDefaults ()
10881110 # set constructor overrides
@@ -1102,6 +1124,20 @@ def __reduce__(self):
11021124 self .saveToStream (stream )
11031125 return (unreduceConfig , (self .__class__ , stream .getvalue ().encode ()))
11041126
1127+ def reset (self , at = None ):
1128+ """Reset all values to their defaults.
1129+
1130+ Parameters
1131+ ----------
1132+ at : `lists` [ `StackFrame` ] or `None`, optional
1133+ Location in stack.
1134+ """
1135+ if at is None :
1136+ at = getCallStack ()
1137+ for field in self ._fields .values ():
1138+ self ._history [field .name ] = []
1139+ field .__set__ (self , field .default , at = at + [field .source ], label = "default" )
1140+
11051141 def setDefaults (self ):
11061142 """Subclass hook for computing defaults.
11071143
@@ -1170,7 +1206,7 @@ def update(self, **kw):
11701206 field = self ._fields [name ]
11711207 field .__set__ (self , value , at = at , label = label )
11721208 except KeyError as e :
1173- e .add_note (f"No field of name { name } exists in config type { _typeStr ( self ) } " )
1209+ e .add_note (f"{ type ( self ). __name__ . replace ( 'Config' , '' ) } has no field named { name } " )
11741210 raise
11751211
11761212 def load (self , filename , root = "config" ):
@@ -1617,11 +1653,12 @@ def __setattr__(self, attr, value, at=None, label="assignment"):
16171653 raise AttributeError (f"{ _typeStr (self )} has no attribute { attr } " )
16181654
16191655 def __delattr__ (self , attr , at = None , label = "deletion" ):
1656+ # CJS: Hacked to allow setDefaults() to delete non-existent fields
1657+ if at is None :
1658+ at = getCallStack ()
16201659 if attr in self ._fields :
1621- if at is None :
1622- at = getCallStack ()
1623- self ._fields [attr ].__delete__ (self , at = at , label = label )
1624- else :
1660+ del self ._fields [attr ]
1661+ elif not any (stk .function == "setDefaults" for stk in at ):
16251662 object .__delattr__ (self , attr )
16261663
16271664 def __eq__ (self , other ):
0 commit comments