28from __future__
import annotations
30__all__ = [
"DictField"]
35from typing
import Any, ForwardRef, Generic, TypeVar, cast
37from .callStack
import getCallStack, getStackFrame
38from .comparison
import compareScalars, getComparisonName
43 UnexpectedProxyUsageError,
49KeyTypeVar = TypeVar(
"KeyTypeVar")
50ItemTypeVar = TypeVar(
"ItemTypeVar")
53class Dict(collections.abc.MutableMapping[KeyTypeVar, ItemTypeVar]):
54 """An internal mapping container.
56 This class emulates
a `dict`, but adds validation
and provenance.
59 def __init__(self, config, field, value, at, label, setHistory=True):
69 self.
__setitem__(k, value[k], at=at, label=label, setHistory=
False)
71 msg = f
"Value {value} is of incorrect type {_typeStr(value)}. Mapping type expected."
81 assert value
is not None
84 history = property(
lambda x: x._history)
85 """History (read-only).
92 return len(self.
_dict)
95 return iter(self.
_dict)
98 return k
in self.
_dict
101 self, k: KeyTypeVar, x: ItemTypeVar, at: Any =
None, label: str =
"setitem", setHistory: bool =
True
104 msg = f
"Cannot modify a frozen Config. Attempting to set item at key {k!r} to value {x}"
108 k = _autocast(k, self.
_field.keytype)
110 msg = f
"Key {k!r} is of type {_typeStr(k)}, expected type {_typeStr(self._field.keytype)}"
114 x = _autocast(x, self.
_field.itemtype)
115 if self.
_field.itemtype
is None:
116 if type(x)
not in self.
_field.supportedTypes
and x
is not None:
117 msg = f
"Value {x} at key {k!r} is of invalid type {_typeStr(x)}"
120 if type(x) != self.
_field.itemtype
and x
is not None:
121 msg =
"Value {} at key {!r} is of incorrect type {}. Expected type {}".format(
125 _typeStr(self.
_field.itemtype),
130 if self.
_field.itemCheck
is not None and not self.
_field.itemCheck(x):
131 msg = f
"Item at key {k!r} is not a valid value: {x}"
142 self, k: KeyTypeVar, at: Any =
None, label: str =
"delitem", setHistory: bool =
True
154 return repr(self.
_dict)
157 return str(self.
_dict)
160 if hasattr(getattr(self.
__class__, attr,
None),
"__set__"):
162 object.__setattr__(self, attr, value)
163 elif attr
in self.__dict__
or attr
in [
"_field",
"_config_",
"_history",
"_dict",
"__doc__"]:
165 object.__setattr__(self, attr, value)
168 msg = f
"{_typeStr(self._field)} has no attribute {attr}"
173 f
"Proxy container for config field {self._field.name} cannot "
174 "be pickled; it should be converted to a built-in container before "
175 "being assigned to other objects or variables."
179class DictField(
Field[Dict[KeyTypeVar, ItemTypeVar]], Generic[KeyTypeVar, ItemTypeVar]):
180 """A configuration field (`~lsst.pex.config.Field` subclass) that maps keys
183 The types of both items
and keys are restricted to these builtin types:
184 `int`, `float`, `complex`, `bool`,
and `str`). All keys share the same type
185 and all values share the same type. Keys can have a different type
from
191 A documentation string that describes the configuration field.
192 keytype : {`int`, `float`, `complex`, `bool`, `str`}, optional
193 The type of the mapping keys. All keys must have this type. Optional
194 if keytype
and itemtype are supplied
as typing arguments to the
class.
195 itemtype : {`int`, `float`, `complex`, `bool`, `str`}, optional
196 Type of the mapping values. Optional
if keytype
and itemtype are
197 supplied
as typing arguments to the
class.
198 default : `dict`, optional
200 optional : `bool`, optional
201 If `
True`, the field doesn
't need to have a set value.
203 A function that validates the dictionary as a whole.
205 A function that validates individual mapping values.
206 deprecated :
None or `str`, optional
207 A description of why this Field
is deprecated, including removal date.
208 If
not None, the string
is appended to the docstring
for this Field.
224 This field maps has `str` keys
and `int` values:
227 >>>
class MyConfig(
Config):
229 ... doc=
"Example string-to-int mapping field.",
230 ... keytype=str, itemtype=int,
233 >>> config = MyConfig()
234 >>> config.field[
'myKey'] = 42
235 >>> print(config.field)
239 DictClass: type[Dict] = Dict
243 params: tuple[type, ...] | tuple[str, ...], kwds: Mapping[str, Any]
244 ) -> Mapping[str, Any]:
246 raise ValueError(
"Only tuples of types that are length 2 are supported")
249 if isinstance(typ, str):
250 _typ = ForwardRef(typ)
256 result = _typ._evaluate(globals(), locals(),
set())
259 result = _typ._evaluate(globals(), locals())
261 raise ValueError(
"Could not deduce type from input")
262 typ = cast(type, result)
263 resultParams.append(typ)
264 keyType, itemType = resultParams
266 if (supplied := kwds.get(
"keytype"))
and supplied != keyType:
267 raise ValueError(
"Conflicting definition for keytype")
269 results[
"keytype"] = keyType
270 if (supplied := kwds.get(
"itemtype"))
and supplied != itemType:
271 raise ValueError(
"Conflicting definition for itemtype")
273 results[
"itemtype"] = itemType
287 source = getStackFrame()
295 deprecated=deprecated,
299 "keytype must either be supplied as an argument or as a type argument to the class"
302 raise ValueError(
"'keytype' %s is not a supported type" % _typeStr(keytype))
303 elif itemtype
is not None and itemtype
not in self.
supportedTypes:
304 raise ValueError(
"'itemtype' %s is not a supported type" % _typeStr(itemtype))
305 if dictCheck
is not None and not hasattr(dictCheck,
"__call__"):
306 raise ValueError(
"'dictCheck' must be callable")
307 if itemCheck
is not None and not hasattr(itemCheck,
"__call__"):
308 raise ValueError(
"'itemCheck' must be callable")
316 """Validate the field's value (for internal use only).
321 The configuration that contains this field.
326 `True`
is returned
if the field passes validation criteria (see
327 *Notes*). Otherwise `
False`.
331 This method validates values according to the following criteria:
333 - A non-optional field
is not `
None`.
334 - If a value
is not `
None`,
is must
pass the `ConfigField.dictCheck`
335 user callback functon.
337 Individual item checks by the `ConfigField.itemCheck` user callback
338 function are done immediately when the value
is set on a key. Those
339 checks are
not repeated by this method.
341 Field.validate(self, instance)
344 msg =
"%s is not a valid value" % str(value)
350 value: Mapping[KeyTypeVar, ItemTypeVar] |
None,
352 label: str =
"assignment",
355 msg =
"Cannot modify a frozen Config. Attempting to set field to value %s" % value
360 if value
is not None:
361 value = self.
DictClass(instance, self, value, at=at, label=label)
363 history = instance._history.setdefault(self.
namenamename, [])
364 history.append((value, at, label))
369 """Convert this field's key-value pairs into a regular `dict`.
374 The configuration that contains this field.
378 result : `dict` or `
None`
379 If this field has a value of `
None`, then this method returns
380 `
None`. Otherwise, this method returns the field
's value as a
381 regular Python `dict`.
384 return dict(value)
if value
is not None else None
386 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
387 """Compare two fields for equality.
389 Used by `lsst.pex.ConfigDictField.compare`.
394 Left-hand side config instance to compare.
396 Right-hand side config instance to compare.
398 If `True`, this function returns
as soon
as an inequality
if found.
400 Relative tolerance
for floating point comparisons.
402 Absolute tolerance
for floating point comparisons.
404 A callable that takes a string, used (possibly repeatedly) to
410 `
True`
if the fields are equal, `
False` otherwise.
414 Floating point comparisons are performed by `numpy.allclose`.
418 name = getComparisonName(
421 if not compareScalars(
"isnone for %s" % name, d1
is None, d2
is None, output=output):
423 if d1
is None and d2
is None:
425 if not compareScalars(
"keys for %s" % name,
set(d1.keys()),
set(d2.keys()), output=output):
428 for k, v1
in d1.items():
430 result = compareScalars(
431 f
"{name}[{k!r}]", v1, v2, dtype=self.
itemtype, rtol=rtol, atol=atol, output=output
433 if not result
and shortcut:
435 equal = equal
and result
__get__(self, instance, owner=None, at=None, label="default")
FieldTypeVar __get__(self, Config instance, Any owner=None, Any at=None, str label="default")
Field[FieldTypeVar] __get__(self, None instance, Any owner=None, Any at=None, str label="default")
_setup(self, doc, dtype, default, check, optional, source, deprecated)
__init__(self, doc, keytype=None, itemtype=None, default=None, optional=False, dictCheck=None, itemCheck=None, deprecated=None)
Mapping[str, Any] _parseTypingArgs(tuple[type,...]|tuple[str,...] params, Mapping[str, Any] kwds)
None __set__(self, Config instance, Mapping[KeyTypeVar, ItemTypeVar]|None value, Any at=None, str label="assignment")
_compare(self, instance1, instance2, shortcut, rtol, atol, output)
bool __contains__(self, Any k)
None __setitem__(self, KeyTypeVar k, ItemTypeVar x, Any at=None, str label="setitem", bool setHistory=True)
ItemTypeVar __getitem__(self, KeyTypeVar k)
None __delitem__(self, KeyTypeVar k, Any at=None, str label="delitem", bool setHistory=True)
Iterator[KeyTypeVar] __iter__(self)
__init__(self, config, field, value, at, label, setHistory=True)
__setattr__(self, attr, value, at=None, label="assignment")
daf::base::PropertySet * set