LSST Applications 26.0.0,g0265f82a02+6660c170cc,g07994bdeae+30b05a742e,g0a0026dc87+17526d298f,g0a60f58ba1+17526d298f,g0e4bf8285c+96dd2c2ea9,g0ecae5effc+c266a536c8,g1e7d6db67d+6f7cb1f4bb,g26482f50c6+6346c0633c,g2bbee38e9b+6660c170cc,g2cc88a2952+0a4e78cd49,g3273194fdb+f6908454ef,g337abbeb29+6660c170cc,g337c41fc51+9a8f8f0815,g37c6e7c3d5+7bbafe9d37,g44018dc512+6660c170cc,g4a941329ef+4f7594a38e,g4c90b7bd52+5145c320d2,g58be5f913a+bea990ba40,g635b316a6c+8d6b3a3e56,g67924a670a+bfead8c487,g6ae5381d9b+81bc2a20b4,g93c4d6e787+26b17396bd,g98cecbdb62+ed2cb6d659,g98ffbb4407+81bc2a20b4,g9ddcbc5298+7f7571301f,ga1e77700b3+99e9273977,gae46bcf261+6660c170cc,gb2715bf1a1+17526d298f,gc86a011abf+17526d298f,gcf0d15dbbd+96dd2c2ea9,gdaeeff99f8+0d8dbea60f,gdb4ec4c597+6660c170cc,ge23793e450+96dd2c2ea9,gf041782ebf+171108ac67
LSST Data Management Base Package
Loading...
Searching...
No Matches
listField.py
Go to the documentation of this file.
1# This file is part of pex_config.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
27
28__all__ = ["ListField"]
29
30import collections.abc
31import weakref
32from collections.abc import Iterable, MutableSequence
33from typing import Any, Generic, overload
34
35from .callStack import getCallStack, getStackFrame
36from .comparison import compareScalars, getComparisonName
37from .config import (
38 Config,
39 Field,
40 FieldTypeVar,
41 FieldValidationError,
42 UnexpectedProxyUsageError,
43 _autocast,
44 _joinNamePath,
45 _typeStr,
46)
47
48
49class List(collections.abc.MutableSequence[FieldTypeVar]):
50 """List collection used internally by `ListField`.
51
52 Parameters
53 ----------
54 config : `lsst.pex.config.Config`
55 Config instance that contains the ``field``.
56 field : `ListField`
57 Instance of the `ListField` using this ``List``.
58 value : sequence
59 Sequence of values that are inserted into this ``List``.
61 The call stack (created by `lsst.pex.config.callStack.getCallStack`).
62 label : `str`
63 Event label for the history.
64 setHistory : `bool`, optional
65 Enable setting the field's history, using the value of the ``at``
66 parameter. Default is `True`.
67
68 Raises
69 ------
70 FieldValidationError
71 Raised if an item in the ``value`` parameter does not have the
72 appropriate type for this field or does not pass the
73 `ListField.itemCheck` method of the ``field`` parameter.
74 """
75
76 def __init__(self, config, field, value, at, label, setHistory=True):
77 self._field = field
78 self._config_ = weakref.ref(config)
79 self._history = self._config_config._history.setdefault(self._field.name, [])
80 self._list = []
81 self.__doc__ = field.doc
82 if value is not None:
83 try:
84 for i, x in enumerate(value):
85 self.insert(i, x, setHistory=False)
86 except TypeError:
87 msg = f"Value {value} is of incorrect type {_typeStr(value)}. Sequence type expected"
88 raise FieldValidationError(self._field, config, msg)
89 if setHistory:
90 self.history.append((list(self._list), at, label))
91
92 @property
93 def _config(self) -> Config:
94 # Config Fields should never outlive their config class instance
95 # assert that as such here
96 value = self._config_()
97 assert value is not None
98 return value
99
100 def validateItem(self, i, x):
101 """Validate an item to determine if it can be included in the list.
102
103 Parameters
104 ----------
105 i : `int`
106 Index of the item in the `list`.
107 x : object
108 Item in the `list`.
109
110 Raises
111 ------
112 FieldValidationError
113 Raised if an item in the ``value`` parameter does not have the
114 appropriate type for this field or does not pass the field's
115 `ListField.itemCheck` method.
116 """
117 if not isinstance(x, self._field.itemtype) and x is not None:
118 msg = "Item at position %d with value %s is of incorrect type %s. Expected %s" % (
119 i,
120 x,
121 _typeStr(x),
122 _typeStr(self._field.itemtype),
123 )
125
126 if self._field.itemCheck is not None and not self._field.itemCheck(x):
127 msg = "Item at position %d is not a valid value: %s" % (i, x)
128 raise FieldValidationError(self._field, self._config_config, msg)
129
130 def list(self):
131 """Sequence of items contained by the `List` (`list`)."""
132 return self._list
133
134 history = property(lambda x: x._history)
135 """Read-only history.
136 """
137
138 def __contains__(self, x):
139 return x in self._list
140
141 def __len__(self):
142 return len(self._list)
143
144 @overload
146 self, i: int, x: FieldTypeVar, at: Any = None, label: str = "setitem", setHistory: bool = True
147 ) -> None:
148 ...
149
150 @overload
152 self,
153 i: slice,
154 x: Iterable[FieldTypeVar],
155 at: Any = None,
156 label: str = "setitem",
157 setHistory: bool = True,
158 ) -> None:
159 ...
160
161 def __setitem__(self, i, x, at=None, label="setitem", setHistory=True):
162 if self._config_config._frozen:
163 raise FieldValidationError(self._field, self._config_config, "Cannot modify a frozen Config")
164 if isinstance(i, slice):
165 k, stop, step = i.indices(len(self))
166 for j, xj in enumerate(x):
167 xj = _autocast(xj, self._field.itemtype)
168 self.validateItem(k, xj)
169 x[j] = xj
170 k += step
171 else:
172 x = _autocast(x, self._field.itemtype)
173 self.validateItem(i, x)
174
175 self._list[i] = x
176 if setHistory:
177 if at is None:
178 at = getCallStack()
179 self.history.append((list(self._list), at, label))
180
181 @overload
182 def __getitem__(self, i: int) -> FieldTypeVar:
183 ...
184
185 @overload
186 def __getitem__(self, i: slice) -> MutableSequence[FieldTypeVar]:
187 ...
188
189 def __getitem__(self, i):
190 return self._list[i]
191
192 def __delitem__(self, i, at=None, label="delitem", setHistory=True):
193 if self._config_config._frozen:
194 raise FieldValidationError(self._field, self._config_config, "Cannot modify a frozen Config")
195 del self._list[i]
196 if setHistory:
197 if at is None:
198 at = getCallStack()
199 self.history.append((list(self._list), at, label))
200
201 def __iter__(self):
202 return iter(self._list)
203
204 def insert(self, i, x, at=None, label="insert", setHistory=True):
205 """Insert an item into the list at the given index.
206
207 Parameters
208 ----------
209 i : `int`
210 Index where the item is inserted.
211 x : object
212 Item that is inserted.
213 at : `list` of `lsst.pex.config.callStack.StackFrame`, optional
214 The call stack (created by
215 `lsst.pex.config.callStack.getCallStack`).
216 label : `str`, optional
217 Event label for the history.
218 setHistory : `bool`, optional
219 Enable setting the field's history, using the value of the ``at``
220 parameter. Default is `True`.
221 """
222 if at is None:
223 at = getCallStack()
224 self.__setitem____setitem____setitem__(slice(i, i), [x], at=at, label=label, setHistory=setHistory)
225
226 def __repr__(self):
227 return repr(self._list)
228
229 def __str__(self):
230 return str(self._list)
231
232 def __eq__(self, other):
233 try:
234 if len(self) != len(other):
235 return False
236
237 for i, j in zip(self, other):
238 if i != j:
239 return False
240 return True
241 except AttributeError:
242 # other is not a sequence type
243 return False
244
245 def __ne__(self, other):
246 return not self.__eq__(other)
247
248 def __setattr__(self, attr, value, at=None, label="assignment"):
249 if hasattr(getattr(self.__class__, attr, None), "__set__"):
250 # This allows properties to work.
251 object.__setattr__(self, attr, value)
252 elif attr in self.__dict__ or attr in ["_field", "_config_", "_history", "_list", "__doc__"]:
253 # This allows specific private attributes to work.
254 object.__setattr__(self, attr, value)
255 else:
256 # We throw everything else.
257 msg = f"{_typeStr(self._field)} has no attribute {attr}"
258 raise FieldValidationError(self._field, self._config_config, msg)
259
260 def __reduce__(self):
262 f"Proxy container for config field {self._field.name} cannot "
263 "be pickled; it should be converted to a built-in container before "
264 "being assigned to other objects or variables."
265 )
266
267
268class ListField(Field[List[FieldTypeVar]], Generic[FieldTypeVar]):
269 """A configuration field (`~lsst.pex.config.Field` subclass) that contains
270 a list of values of a specific type.
271
272 Parameters
273 ----------
274 doc : `str`
275 A description of the field.
276 dtype : class, optional
277 The data type of items in the list. Optional if supplied as typing
278 argument to the class.
279 default : sequence, optional
280 The default items for the field.
281 optional : `bool`, optional
282 Set whether the field is *optional*. When `False`,
283 `lsst.pex.config.Config.validate` will fail if the field's value is
284 `None`.
285 listCheck : callable, optional
286 A callable that validates the list as a whole.
287 itemCheck : callable, optional
288 A callable that validates individual items in the list.
289 length : `int`, optional
290 If set, this field must contain exactly ``length`` number of items.
291 minLength : `int`, optional
292 If set, this field must contain *at least* ``minLength`` number of
293 items.
294 maxLength : `int`, optional
295 If set, this field must contain *no more than* ``maxLength`` number of
296 items.
297 deprecated : None or `str`, optional
298 A description of why this Field is deprecated, including removal date.
299 If not None, the string is appended to the docstring for this Field.
300
301 See Also
302 --------
303 ChoiceField
304 ConfigChoiceField
305 ConfigDictField
306 ConfigField
307 ConfigurableField
308 DictField
309 Field
310 RangeField
311 RegistryField
312 """
313
315 self,
316 doc,
317 dtype=None,
318 default=None,
319 optional=False,
320 listCheck=None,
321 itemCheck=None,
322 length=None,
323 minLength=None,
324 maxLength=None,
325 deprecated=None,
326 ):
327 if dtype is None:
328 raise ValueError(
329 "dtype must either be supplied as an argument or as a type argument to the class"
330 )
331 if dtype not in Field.supportedTypes:
332 raise ValueError("Unsupported dtype %s" % _typeStr(dtype))
333 if length is not None:
334 if length <= 0:
335 raise ValueError("'length' (%d) must be positive" % length)
336 minLength = None
337 maxLength = None
338 else:
339 if maxLength is not None and maxLength <= 0:
340 raise ValueError("'maxLength' (%d) must be positive" % maxLength)
341 if minLength is not None and maxLength is not None and minLength > maxLength:
342 raise ValueError(
343 "'maxLength' (%d) must be at least as large as 'minLength' (%d)" % (maxLength, minLength)
344 )
345
346 if listCheck is not None and not hasattr(listCheck, "__call__"):
347 raise ValueError("'listCheck' must be callable")
348 if itemCheck is not None and not hasattr(itemCheck, "__call__"):
349 raise ValueError("'itemCheck' must be callable")
350
351 source = getStackFrame()
352 self._setup(
353 doc=doc,
354 dtype=List,
355 default=default,
356 check=None,
357 optional=optional,
358 source=source,
359 deprecated=deprecated,
360 )
361
362 self.listCheck = listCheck
363 """Callable used to check the list as a whole.
364 """
365
366 self.itemCheck = itemCheck
367 """Callable used to validate individual items as they are inserted
368 into the list.
369 """
370
371 self.itemtype = dtype
372 """Data type of list items.
373 """
374
375 self.length = length
376 """Number of items that must be present in the list (or `None` to
377 disable checking the list's length).
378 """
379
380 self.minLength = minLength
381 """Minimum number of items that must be present in the list (or `None`
382 to disable checking the list's minimum length).
383 """
384
385 self.maxLength = maxLength
386 """Maximum number of items that must be present in the list (or `None`
387 to disable checking the list's maximum length).
388 """
389
390 def validate(self, instance):
391 """Validate the field.
392
393 Parameters
394 ----------
395 instance : `lsst.pex.config.Config`
396 The config instance that contains this field.
397
398 Raises
399 ------
401 Raised if:
402
403 - The field is not optional, but the value is `None`.
404 - The list itself does not meet the requirements of the ``length``,
405 ``minLength``, or ``maxLength`` attributes.
406 - The ``listCheck`` callable returns `False`.
407
408 Notes
409 -----
410 Individual item checks (``itemCheck``) are applied when each item is
411 set and are not re-checked by this method.
412 """
413 Field.validate(self, instance)
414 value = self.__get____get____get__(instance)
415 if value is not None:
416 lenValue = len(value)
417 if self.length is not None and not lenValue == self.length:
418 msg = "Required list length=%d, got length=%d" % (self.length, lenValue)
419 raise FieldValidationError(self, instance, msg)
420 elif self.minLength is not None and lenValue < self.minLength:
421 msg = "Minimum allowed list length=%d, got length=%d" % (self.minLength, lenValue)
422 raise FieldValidationError(self, instance, msg)
423 elif self.maxLength is not None and lenValue > self.maxLength:
424 msg = "Maximum allowed list length=%d, got length=%d" % (self.maxLength, lenValue)
425 raise FieldValidationError(self, instance, msg)
426 elif self.listCheck is not None and not self.listCheck(value):
427 msg = "%s is not a valid value" % str(value)
428 raise FieldValidationError(self, instance, msg)
429
431 self,
432 instance: Config,
433 value: Iterable[FieldTypeVar] | None,
434 at: Any = None,
435 label: str = "assignment",
436 ) -> None:
437 if instance._frozen:
438 raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
439
440 if at is None:
441 at = getCallStack()
442
443 if value is not None:
444 value = List(instance, self, value, at, label)
445 else:
446 history = instance._history.setdefault(self.namenamename, [])
447 history.append((value, at, label))
448
449 instance._storage[self.namenamename] = value
450
451 def toDict(self, instance):
452 """Convert the value of this field to a plain `list`.
453
454 `lsst.pex.config.Config.toDict` is the primary user of this method.
455
456 Parameters
457 ----------
458 instance : `lsst.pex.config.Config`
459 The config instance that contains this field.
460
461 Returns
462 -------
463 `list`
464 Plain `list` of items, or `None` if the field is not set.
465 """
466 value = self.__get____get____get__(instance)
467 return list(value) if value is not None else None
468
469 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
470 """Compare two config instances for equality with respect to this
471 field.
472
473 `lsst.pex.config.config.compare` is the primary user of this method.
474
475 Parameters
476 ----------
477 instance1 : `lsst.pex.config.Config`
478 Left-hand-side `~lsst.pex.config.Config` instance in the
479 comparison.
480 instance2 : `lsst.pex.config.Config`
481 Right-hand-side `~lsst.pex.config.Config` instance in the
482 comparison.
483 shortcut : `bool`
484 If `True`, return as soon as an **inequality** is found.
485 rtol : `float`
486 Relative tolerance for floating point comparisons.
487 atol : `float`
488 Absolute tolerance for floating point comparisons.
489 output : callable
490 If not None, a callable that takes a `str`, used (possibly
491 repeatedly) to report inequalities.
492
493 Returns
494 -------
495 equal : `bool`
496 `True` if the fields are equal; `False` otherwise.
497
498 Notes
499 -----
500 Floating point comparisons are performed by `numpy.allclose`.
501 """
502 l1 = getattr(instance1, self.namenamename)
503 l2 = getattr(instance2, self.namenamename)
504 name = getComparisonName(
505 _joinNamePath(instance1._name, self.namenamename), _joinNamePath(instance2._name, self.namenamename)
506 )
507 if not compareScalars("isnone for %s" % name, l1 is None, l2 is None, output=output):
508 return False
509 if l1 is None and l2 is None:
510 return True
511 if not compareScalars("size for %s" % name, len(l1), len(l2), output=output):
512 return False
513 equal = True
514 for n, v1, v2 in zip(range(len(l1)), l1, l2):
515 result = compareScalars(
516 "%s[%d]" % (name, n), v1, v2, dtype=self.dtype, rtol=rtol, atol=atol, output=output
517 )
518 if not result and shortcut:
519 return False
520 equal = equal and result
521 return equal
__get__(self, instance, owner=None, at=None, label="default")
Definition config.py:720
FieldTypeVar __get__(self, Config instance, Any owner=None, Any at=None, str label="default")
Definition config.py:717
Field[FieldTypeVar] __get__(self, None instance, Any owner=None, Any at=None, str label="default")
Definition config.py:711
_setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition config.py:489
_compare(self, instance1, instance2, shortcut, rtol, atol, output)
Definition listField.py:469
__init__(self, doc, dtype=None, default=None, optional=False, listCheck=None, itemCheck=None, length=None, minLength=None, maxLength=None, deprecated=None)
Definition listField.py:326
None __set__(self, Config instance, Iterable[FieldTypeVar]|None value, Any at=None, str label="assignment")
Definition listField.py:436
None __setitem__(self, slice i, Iterable[FieldTypeVar] x, Any at=None, str label="setitem", bool setHistory=True)
Definition listField.py:158
FieldTypeVar __getitem__(self, int i)
Definition listField.py:182
None __setitem__(self, int i, FieldTypeVar x, Any at=None, str label="setitem", bool setHistory=True)
Definition listField.py:147
insert(self, i, x, at=None, label="insert", setHistory=True)
Definition listField.py:204
__delitem__(self, i, at=None, label="delitem", setHistory=True)
Definition listField.py:192
__setattr__(self, attr, value, at=None, label="assignment")
Definition listField.py:248
__init__(self, config, field, value, at, label, setHistory=True)
Definition listField.py:76
__setitem__(self, i, x, at=None, label="setitem", setHistory=True)
Definition listField.py:161
daf::base::PropertyList * list
Definition fits.cc:928