From 99664810fb55952a230c29132b281290e7f21901 Mon Sep 17 00:00:00 2001 From: csimpson Date: Mon, 21 Oct 2019 11:36:11 -1000 Subject: [PATCH] only expose _fields to the user, so deleted Fields remain hidden --- python/lsst/pex/config/config.py | 46 +++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/python/lsst/pex/config/config.py b/python/lsst/pex/config/config.py index 01df864..618ee62 100644 --- a/python/lsst/pex/config/config.py +++ b/python/lsst/pex/config/config.py @@ -812,6 +812,12 @@ def __set__( if instance._frozen: raise FieldValidationError(self, instance, "Cannot modify a frozen Config") + if at is None: + at = getCallStack() + # setDefaults() gets a free pass due to our mashing of inheritance + if self.name not in instance._fields: + raise AttributeError(f"{instance.__class__.__name__} has no attribute {self.name}") + history = instance._history.setdefault(self.name, []) if value is not None: value = _autocast(value, self.dtype) @@ -996,6 +1002,9 @@ class behavior. _history: dict[str, list[Any]] _imports: set[Any] + # Only _fields are exposure. _storage retains items that have been + # deleted. + def __iter__(self): """Iterate over fields.""" return self._fields.__iter__() @@ -1008,7 +1017,7 @@ def keys(self): names : `~collections.abc.KeysView` List of `lsst.pex.config.Field` names. """ - return self._storage.keys() + return list(self._fields) def values(self): """Get field values. @@ -1018,7 +1027,7 @@ def values(self): values : `~collections.abc.ValuesView` Iterator of field values. """ - return self._storage.values() + return self.toDict().values() def items(self): """Get configurations as ``(field name, field value)`` pairs. @@ -1031,7 +1040,11 @@ def items(self): 0. Field name. 1. Field value. """ - return self._storage.items() + return self.toDict().items() + + def doc(self, field): + """Return docstring for field.""" + return self._fields[field].doc def __contains__(self, name): """Return `True` if the specified field exists in this config. @@ -1046,7 +1059,7 @@ def __contains__(self, name): in : `bool` `True` if the specified field exists in the config. """ - return self._storage.__contains__(name) + return self._storage.__contains__(name) and name in self._fields def __new__(cls, *args, **kw): """Allocate a new `lsst.pex.config.Config` object. @@ -1072,9 +1085,7 @@ def __new__(cls, *args, **kw): instance._history = {} instance._imports = set() # load up defaults - for field in instance._fields.values(): - instance._history[field.name] = [] - field.__set__(instance, field.default, at=at + [field.source], label="default") + instance.reset(at=at) # set custom default-overrides instance.setDefaults() # set constructor overrides @@ -1094,6 +1105,14 @@ def __reduce__(self): self.saveToStream(stream) return (unreduceConfig, (self.__class__, stream.getvalue().encode())) + def reset(self, at=None): + """Reset all values to their defaults.""" + if at is None: + at = getCallStack() + for field in self._fields.values(): + self._history[field.name] = [] + field.__set__(self, field.default, at=at + [field.source], label="default") + def setDefaults(self): """Subclass hook for computing defaults. @@ -1165,7 +1184,9 @@ def update(self, **kw): field = self._fields[name] field.__set__(self, value, at=at, label=label) except KeyError: - raise KeyError(f"No field of name {name} exists in config type {_typeStr(self)}") + raise KeyError( + "{} has no field named {}".format(type(self).__name__.replace("Config", ""), name) + ) def load(self, filename, root="config"): """Modify this config in place by executing the Python code in a @@ -1611,11 +1632,12 @@ def __setattr__(self, attr, value, at=None, label="assignment"): raise AttributeError(f"{_typeStr(self)} has no attribute {attr}") def __delattr__(self, attr, at=None, label="deletion"): + # CJS: Hacked to allow setDefaults() to delete non-existent fields + if at is None: + at = getCallStack() if attr in self._fields: - if at is None: - at = getCallStack() - self._fields[attr].__delete__(self, at=at, label=label) - else: + del self._fields[attr] + elif not any(stk.function == "setDefaults" for stk in at): object.__delattr__(self, attr) def __eq__(self, other):