28 __all__ = [
"DictField"]
32 from .config
import Field, FieldValidationError, _typeStr, _autocast, _joinNamePath, Config
33 from .comparison
import getComparisonName, compareScalars
34 from .callStack
import getCallStack, getStackFrame
39 class Dict(collections.abc.MutableMapping):
40 """An internal mapping container.
42 This class emulates a `dict`, but adds validation and provenance.
45 def __init__(self, config, field, value, at, label, setHistory=True):
47 self.
_config__config_ = weakref.ref(config)
55 self.
__setitem____setitem__(k, value[k], at=at, label=label, setHistory=
False)
57 msg =
"Value %s is of incorrect type %s. Mapping type expected." % \
58 (value, _typeStr(value))
64 def _config(self) -> Config:
67 assert(self.
_config__config_()
is not None)
70 history = property(
lambda x: x._history)
71 """History (read-only).
75 return self.
_dict_dict[k]
78 return len(self.
_dict_dict)
84 return k
in self.
_dict_dict
86 def __setitem__(self, k, x, at=None, label="setitem", setHistory=True):
88 msg =
"Cannot modify a frozen Config. "\
89 "Attempting to set item at key %r to value %s" % (k, x)
93 k = _autocast(k, self.
_field_field.keytype)
95 msg =
"Key %r is of type %s, expected type %s" % \
96 (k, _typeStr(k), _typeStr(self.
_field_field.keytype))
100 x = _autocast(x, self.
_field_field.itemtype)
101 if self.
_field_field.itemtype
is None:
102 if type(x)
not in self.
_field_field.supportedTypes
and x
is not None:
103 msg =
"Value %s at key %r is of invalid type %s" % (x, k, _typeStr(x))
106 if type(x) != self.
_field_field.itemtype
and x
is not None:
107 msg =
"Value %s at key %r is of incorrect type %s. Expected type %s" % \
108 (x, k, _typeStr(x), _typeStr(self.
_field_field.itemtype))
112 if self.
_field_field.itemCheck
is not None and not self.
_field_field.itemCheck(x):
113 msg =
"Item at key %r is not a valid value: %s" % (k, x)
119 self.
_dict_dict[k] = x
123 def __delitem__(self, k, at=None, label="delitem", setHistory=True):
124 if self.
_config_config._frozen:
126 "Cannot modify a frozen Config")
128 del self.
_dict_dict[k]
135 return repr(self.
_dict_dict)
138 return str(self.
_dict_dict)
141 if hasattr(getattr(self.__class__, attr,
None),
'__set__'):
143 object.__setattr__(self, attr, value)
144 elif attr
in self.__dict__
or attr
in [
"_field",
"_config_",
"_history",
"_dict",
"__doc__"]:
146 object.__setattr__(self, attr, value)
149 msg =
"%s has no attribute %s" % (_typeStr(self.
_field_field), attr)
154 """A configuration field (`~lsst.pex.config.Field` subclass) that maps keys
157 The types of both items and keys are restricted to these builtin types:
158 `int`, `float`, `complex`, `bool`, and `str`). All keys share the same type
159 and all values share the same type. Keys can have a different type from
165 A documentation string that describes the configuration field.
166 keytype : {`int`, `float`, `complex`, `bool`, `str`}
167 The type of the mapping keys. All keys must have this type.
168 itemtype : {`int`, `float`, `complex`, `bool`, `str`}
169 Type of the mapping values.
170 default : `dict`, optional
172 optional : `bool`, optional
173 If `True`, the field doesn't need to have a set value.
175 A function that validates the dictionary as a whole.
177 A function that validates individual mapping values.
178 deprecated : None or `str`, optional
179 A description of why this Field is deprecated, including removal date.
180 If not None, the string is appended to the docstring for this Field.
196 This field maps has `str` keys and `int` values:
198 >>> from lsst.pex.config import Config, DictField
199 >>> class MyConfig(Config):
200 ... field = DictField(
201 ... doc="Example string-to-int mapping field.",
202 ... keytype=str, itemtype=int,
205 >>> config = MyConfig()
206 >>> config.field['myKey'] = 42
207 >>> print(config.field)
213 def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None,
216 self.
_setup_setup(doc=doc, dtype=Dict, default=default, check=
None,
217 optional=optional, source=source, deprecated=deprecated)
219 raise ValueError(
"'keytype' %s is not a supported type" %
221 elif itemtype
is not None and itemtype
not in self.
supportedTypessupportedTypes:
222 raise ValueError(
"'itemtype' %s is not a supported type" %
224 if dictCheck
is not None and not hasattr(dictCheck,
"__call__"):
225 raise ValueError(
"'dictCheck' must be callable")
226 if itemCheck
is not None and not hasattr(itemCheck,
"__call__"):
227 raise ValueError(
"'itemCheck' must be callable")
235 """Validate the field's value (for internal use only).
239 instance : `lsst.pex.config.Config`
240 The configuration that contains this field.
245 `True` is returned if the field passes validation criteria (see
246 *Notes*). Otherwise `False`.
250 This method validates values according to the following criteria:
252 - A non-optional field is not `None`.
253 - If a value is not `None`, is must pass the `ConfigField.dictCheck`
254 user callback functon.
256 Individual item checks by the `ConfigField.itemCheck` user callback
257 function are done immediately when the value is set on a key. Those
258 checks are not repeated by this method.
260 Field.validate(self, instance)
261 value = self.
__get____get__(instance)
262 if value
is not None and self.
dictCheckdictCheck
is not None \
264 msg =
"%s is not a valid value" % str(value)
267 def __set__(self, instance, value, at=None, label="assignment"):
269 msg =
"Cannot modify a frozen Config. "\
270 "Attempting to set field to value %s" % value
275 if value
is not None:
276 value = self.
DictClassDictClass(instance, self, value, at=at, label=label)
278 history = instance._history.setdefault(self.name, [])
279 history.append((value, at, label))
281 instance._storage[self.name] = value
284 """Convert this field's key-value pairs into a regular `dict`.
288 instance : `lsst.pex.config.Config`
289 The configuration that contains this field.
293 result : `dict` or `None`
294 If this field has a value of `None`, then this method returns
295 `None`. Otherwise, this method returns the field's value as a
296 regular Python `dict`.
298 value = self.
__get____get__(instance)
299 return dict(value)
if value
is not None else None
301 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
302 """Compare two fields for equality.
304 Used by `lsst.pex.ConfigDictField.compare`.
308 instance1 : `lsst.pex.config.Config`
309 Left-hand side config instance to compare.
310 instance2 : `lsst.pex.config.Config`
311 Right-hand side config instance to compare.
313 If `True`, this function returns as soon as an inequality if found.
315 Relative tolerance for floating point comparisons.
317 Absolute tolerance for floating point comparisons.
319 A callable that takes a string, used (possibly repeatedly) to
325 `True` if the fields are equal, `False` otherwise.
329 Floating point comparisons are performed by `numpy.allclose`.
331 d1 = getattr(instance1, self.name)
332 d2 = getattr(instance2, self.name)
334 _joinNamePath(instance1._name, self.name),
335 _joinNamePath(instance2._name, self.name)
337 if not compareScalars(
"isnone for %s" % name, d1
is None, d2
is None, output=output):
339 if d1
is None and d2
is None:
344 for k, v1
in d1.items():
347 rtol=rtol, atol=atol, output=output)
348 if not result
and shortcut:
350 equal = equal
and result
def __get__(self, instance, owner=None, at=None, label="default")
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
def __set__(self, instance, value, at=None, label="assignment")
def validate(self, instance)
def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None, deprecated=None)
def toDict(self, instance)
def __delitem__(self, k, at=None, label="delitem", setHistory=True)
def __setattr__(self, attr, value, at=None, label="assignment")
def __contains__(self, k)
def __init__(self, config, field, value, at, label, setHistory=True)
def __setitem__(self, k, x, at=None, label="setitem", setHistory=True)
daf::base::PropertySet * set
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
def getStackFrame(relative=0)
def compareScalars(name, v1, v2, output, rtol=1E-8, atol=1E-8, dtype=None)
def getComparisonName(name1, name2)