28from __future__
import annotations
30__all__ = [
"DictField"]
35from typing
import Any, ForwardRef, Generic, Iterator, Mapping, Type, TypeVar, Union, cast
37from .callStack
import getCallStack, getStackFrame
38from .comparison
import compareScalars, getComparisonName
43 UnexpectedProxyUsageError,
49KeyTypeVar = TypeVar(
"KeyTypeVar")
50ItemTypeVar = TypeVar(
"ItemTypeVar")
53if int(sys.version_info.minor) < 9:
54 _bases = (collections.abc.MutableMapping, Generic[KeyTypeVar, ItemTypeVar])
56 _bases = (collections.abc.MutableMapping[KeyTypeVar, ItemTypeVar],)
60 """An internal mapping container.
62 This class emulates
a `dict`, but adds validation
and provenance.
65 def __init__(self, config, field, value, at, label, setHistory=True):
75 self.
__setitem__(k, value[k], at=at, label=label, setHistory=
False)
77 msg =
"Value %s is of incorrect type %s. Mapping type expected." % (value, _typeStr(value))
83 def _config(self) -> Config:
87 assert value
is not None
90 history = property(
lambda x: x._history)
91 """History (read-only).
98 return len(self.
_dict)
101 return iter(self.
_dict)
104 return k
in self.
_dict
107 self, k: KeyTypeVar, x: ItemTypeVar, at: Any =
None, label: str =
"setitem", setHistory: bool =
True
110 msg =
"Cannot modify a frozen Config. Attempting to set item at key %r to value %s" % (k, x)
114 k = _autocast(k, self.
_field.keytype)
116 msg =
"Key %r is of type %s, expected type %s" % (k, _typeStr(k), _typeStr(self.
_field.keytype))
120 x = _autocast(x, self.
_field.itemtype)
121 if self.
_field.itemtype
is None:
122 if type(x)
not in self.
_field.supportedTypes
and x
is not None:
123 msg =
"Value %s at key %r is of invalid type %s" % (x, k, _typeStr(x))
126 if type(x) != self.
_field.itemtype
and x
is not None:
127 msg =
"Value %s at key %r is of incorrect type %s. Expected type %s" % (
131 _typeStr(self.
_field.itemtype),
136 if self.
_field.itemCheck
is not None and not self.
_field.itemCheck(x):
137 msg =
"Item at key %r is not a valid value: %s" % (k, x)
148 self, k: KeyTypeVar, at: Any =
None, label: str =
"delitem", setHistory: bool =
True
160 return repr(self.
_dict)
166 if hasattr(getattr(self.__class__, attr,
None),
"__set__"):
168 object.__setattr__(self, attr, value)
169 elif attr
in self.__dict__
or attr
in [
"_field",
"_config_",
"_history",
"_dict",
"__doc__"]:
171 object.__setattr__(self, attr, value)
174 msg =
"%s has no attribute %s" % (_typeStr(self.
_field), attr)
179 f
"Proxy container for config field {self._field.name} cannot "
180 "be pickled; it should be converted to a built-in container before "
181 "being assigned to other objects or variables."
185class DictField(
Field[Dict[KeyTypeVar, ItemTypeVar]], Generic[KeyTypeVar, ItemTypeVar]):
186 """A configuration field (`~lsst.pex.config.Field` subclass) that maps keys
189 The types of both items
and keys are restricted to these builtin types:
190 `int`, `float`, `complex`, `bool`,
and `str`). All keys share the same type
191 and all values share the same type. Keys can have a different type
from
197 A documentation string that describes the configuration field.
198 keytype : {`int`, `float`, `complex`, `bool`, `str`}, optional
199 The type of the mapping keys. All keys must have this type. Optional
200 if keytype
and itemtype are supplied
as typing arguments to the
class.
201 itemtype : {`int`, `float`, `complex`, `bool`, `str`}, optional
202 Type of the mapping values. Optional
if keytype
and itemtype are
203 supplied
as typing arguments to the
class.
204 default : `dict`, optional
206 optional : `bool`, optional
207 If `
True`, the field doesn
't need to have a set value.
209 A function that validates the dictionary as a whole.
211 A function that validates individual mapping values.
212 deprecated :
None or `str`, optional
213 A description of why this Field
is deprecated, including removal date.
214 If
not None, the string
is appended to the docstring
for this Field.
230 This field maps has `str` keys
and `int` values:
233 >>>
class MyConfig(
Config):
235 ... doc=
"Example string-to-int mapping field.",
236 ... keytype=str, itemtype=int,
239 >>> config = MyConfig()
240 >>> config.field[
'myKey'] = 42
241 >>> print(config.field)
245 DictClass: Type[Dict] = Dict
248 def _parseTypingArgs(
249 params: Union[tuple[type, ...], tuple[str, ...]], kwds: Mapping[str, Any]
250 ) -> Mapping[str, Any]:
252 raise ValueError(
"Only tuples of types that are length 2 are supported")
255 if isinstance(typ, str):
256 _typ = ForwardRef(typ)
262 result = _typ._evaluate(globals(), locals(),
set())
265 result = _typ._evaluate(globals(), locals())
267 raise ValueError(
"Could not deduce type from input")
268 typ = cast(type, result)
269 resultParams.append(typ)
270 keyType, itemType = resultParams
272 if (supplied := kwds.get(
"keytype"))
and supplied != keyType:
273 raise ValueError(
"Conflicting definition for keytype")
275 results[
"keytype"] = keyType
276 if (supplied := kwds.get(
"itemtype"))
and supplied != itemType:
277 raise ValueError(
"Conflicting definition for itemtype")
279 results[
"itemtype"] = itemType
293 source = getStackFrame()
301 deprecated=deprecated,
305 "keytype must either be supplied as an argument or as a type argument to the class"
308 raise ValueError(
"'keytype' %s is not a supported type" % _typeStr(keytype))
309 elif itemtype
is not None and itemtype
not in self.
supportedTypes:
310 raise ValueError(
"'itemtype' %s is not a supported type" % _typeStr(itemtype))
311 if dictCheck
is not None and not hasattr(dictCheck,
"__call__"):
312 raise ValueError(
"'dictCheck' must be callable")
313 if itemCheck
is not None and not hasattr(itemCheck,
"__call__"):
314 raise ValueError(
"'itemCheck' must be callable")
322 """Validate the field's value (for internal use only).
327 The configuration that contains this field.
332 `True`
is returned
if the field passes validation criteria (see
333 *Notes*). Otherwise `
False`.
337 This method validates values according to the following criteria:
339 - A non-optional field
is not `
None`.
340 - If a value
is not `
None`,
is must
pass the `ConfigField.dictCheck`
341 user callback functon.
343 Individual item checks by the `ConfigField.itemCheck` user callback
344 function are done immediately when the value
is set on a key. Those
345 checks are
not repeated by this method.
347 Field.validate(self, instance)
350 msg =
"%s is not a valid value" %
str(value)
356 value: Union[Mapping[KeyTypeVar, ItemTypeVar],
None],
358 label: str =
"assignment",
361 msg =
"Cannot modify a frozen Config. Attempting to set field to value %s" % value
366 if value
is not None:
367 value = self.DictClass(instance, self, value, at=at, label=label)
369 history = instance._history.setdefault(self.name, [])
370 history.append((value, at, label))
372 instance._storage[self.name] = value
375 """Convert this field's key-value pairs into a regular `dict`.
380 The configuration that contains this field.
384 result : `dict` or `
None`
385 If this field has a value of `
None`, then this method returns
386 `
None`. Otherwise, this method returns the field
's value as a
387 regular Python `dict`.
390 return dict(value)
if value
is not None else None
392 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
393 """Compare two fields for equality.
395 Used by `lsst.pex.ConfigDictField.compare`.
400 Left-hand side config instance to compare.
402 Right-hand side config instance to compare.
404 If `True`, this function returns
as soon
as an inequality
if found.
406 Relative tolerance
for floating point comparisons.
408 Absolute tolerance
for floating point comparisons.
410 A callable that takes a string, used (possibly repeatedly) to
416 `
True`
if the fields are equal, `
False` otherwise.
420 Floating point comparisons are performed by `numpy.allclose`.
422 d1 = getattr(instance1, self.name)
423 d2 = getattr(instance2, self.name)
424 name = getComparisonName(
425 _joinNamePath(instance1._name, self.name), _joinNamePath(instance2._name, self.name)
427 if not compareScalars(
"isnone for %s" % name, d1
is None, d2
is None, output=output):
429 if d1
is None and d2
is None:
431 if not compareScalars(
"keys for %s" % name,
set(d1.keys()),
set(d2.keys()), output=output):
434 for k, v1
in d1.items():
436 result = compareScalars(
437 "%s[%r]" % (name, k), v1, v2, dtype=self.
itemtype, rtol=rtol, atol=atol, output=output
439 if not result
and shortcut:
441 equal = equal
and result
"Field[FieldTypeVar]" __get__(self, None instance, Any owner=None, Any at=None, str label="default")
def __get__(self, instance, owner=None, at=None, label="default")
FieldTypeVar __get__(self, "Config" instance, Any owner=None, Any at=None, str label="default")
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
def __init__(self, doc, keytype=None, itemtype=None, default=None, optional=False, dictCheck=None, itemCheck=None, deprecated=None)
None __set__(self, Config instance, Union[Mapping[KeyTypeVar, ItemTypeVar], None] value, Any at=None, str label="assignment")
def toDict(self, instance)
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)
def __setattr__(self, attr, value, at=None, label="assignment")
None __delitem__(self, KeyTypeVar k, Any at=None, str label="delitem", bool setHistory=True)
def __init__(self, config, field, value, at, label, setHistory=True)
Iterator[KeyTypeVar] __iter__(self)
daf::base::PropertySet * set