Skip to content

Commit b047819

Browse files
chris-simpsontimj
authored andcommitted
only expose _fields to the user, so deleted Fields remain hidden
1 parent f44bf22 commit b047819

File tree

1 file changed

+41
-15
lines changed

1 file changed

+41
-15
lines changed

python/lsst/pex/config/config.py

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)