LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
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 sys
32import weakref
33from typing import Any, Generic, Iterable, MutableSequence, Union, 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
48if int(sys.version_info.minor) < 9:
49 _bases = (collections.abc.MutableSequence, Generic[FieldTypeVar])
50else:
51 _bases = (collections.abc.MutableSequence[FieldTypeVar],)
52
53
54class List(*_bases):
55 """List collection used internally by `ListField`.
56
57 Parameters
58 ----------
59 config : `lsst.pex.config.Config`
60 Config instance that contains the ``field``.
61 field : `ListField`
62 Instance of the `ListField` using this ``List``.
63 value : sequence
64 Sequence of values that are inserted into this ``List``.
66 The call stack (created by `lsst.pex.config.callStack.getCallStack`).
67 label : `str`
68 Event label for the history.
69 setHistory : `bool`, optional
70 Enable setting the field's history, using the value of the ``at``
71 parameter. Default is `True`.
72
73 Raises
74 ------
75 FieldValidationError
76 Raised if an item in the ``value`` parameter does not have the
77 appropriate type for this field or does not pass the
78 `ListField.itemCheck` method of the ``field`` parameter.
79 """
80
81 def __init__(self, config, field, value, at, label, setHistory=True):
82 self._field_field = field
83 self._config__config_ = weakref.ref(config)
84 self._history_history = self._config_config._history.setdefault(self._field_field.name, [])
85 self._list_list = []
86 self.__doc____doc__ = field.doc
87 if value is not None:
88 try:
89 for i, x in enumerate(value):
90 self.insertinsert(i, x, setHistory=False)
91 except TypeError:
92 msg = "Value %s is of incorrect type %s. Sequence type expected" % (value, _typeStr(value))
93 raise FieldValidationError(self._field_field, config, msg)
94 if setHistory:
95 self.historyhistory.append((list(self._list_list), at, label))
96
97 @property
98 def _config(self) -> Config:
99 # Config Fields should never outlive their config class instance
100 # assert that as such here
101 value = self._config__config_()
102 assert value is not None
103 return value
104
105 def validateItem(self, i, x):
106 """Validate an item to determine if it can be included in the list.
107
108 Parameters
109 ----------
110 i : `int`
111 Index of the item in the `list`.
112 x : object
113 Item in the `list`.
114
115 Raises
116 ------
117 FieldValidationError
118 Raised if an item in the ``value`` parameter does not have the
119 appropriate type for this field or does not pass the field's
120 `ListField.itemCheck` method.
121 """
122
123 if not isinstance(x, self._field_field.itemtype) and x is not None:
124 msg = "Item at position %d with value %s is of incorrect type %s. Expected %s" % (
125 i,
126 x,
127 _typeStr(x),
128 _typeStr(self._field_field.itemtype),
129 )
130 raise FieldValidationError(self._field_field, self._config_config, msg)
131
132 if self._field_field.itemCheck is not None and not self._field_field.itemCheck(x):
133 msg = "Item at position %d is not a valid value: %s" % (i, x)
134 raise FieldValidationError(self._field_field, self._config_config, msg)
135
136 def list(self):
137 """Sequence of items contained by the `List` (`list`)."""
138 return self._list_list
139
140 history = property(lambda x: x._history)
141 """Read-only history.
142 """
143
144 def __contains__(self, x):
145 return x in self._list_list
146
147 def __len__(self):
148 return len(self._list_list)
149
150 @overload
152 self, i: int, x: FieldTypeVar, at: Any = None, label: str = "setitem", setHistory: bool = True
153 ) -> None:
154 ...
155
156 @overload
158 self,
159 i: slice,
160 x: Iterable[FieldTypeVar],
161 at: Any = None,
162 label: str = "setitem",
163 setHistory: bool = True,
164 ) -> None:
165 ...
166
167 def __setitem__(self, i, x, at=None, label="setitem", setHistory=True):
168 if self._config_config._frozen:
169 raise FieldValidationError(self._field_field, self._config_config, "Cannot modify a frozen Config")
170 if isinstance(i, slice):
171 k, stop, step = i.indices(len(self))
172 for j, xj in enumerate(x):
173 xj = _autocast(xj, self._field_field.itemtype)
174 self.validateItemvalidateItem(k, xj)
175 x[j] = xj
176 k += step
177 else:
178 x = _autocast(x, self._field_field.itemtype)
179 self.validateItemvalidateItem(i, x)
180
181 self._list_list[i] = x
182 if setHistory:
183 if at is None:
184 at = getCallStack()
185 self.historyhistory.append((list(self._list_list), at, label))
186
187 @overload
188 def __getitem__(self, i: int) -> FieldTypeVar:
189 ...
190
191 @overload
192 def __getitem__(self, i: slice) -> MutableSequence[FieldTypeVar]:
193 ...
194
195 def __getitem__(self, i):
196 return self._list_list[i]
197
198 def __delitem__(self, i, at=None, label="delitem", setHistory=True):
199 if self._config_config._frozen:
200 raise FieldValidationError(self._field_field, self._config_config, "Cannot modify a frozen Config")
201 del self._list_list[i]
202 if setHistory:
203 if at is None:
204 at = getCallStack()
205 self.historyhistory.append((list(self._list_list), at, label))
206
207 def __iter__(self):
208 return iter(self._list_list)
209
210 def insert(self, i, x, at=None, label="insert", setHistory=True):
211 """Insert an item into the list at the given index.
212
213 Parameters
214 ----------
215 i : `int`
216 Index where the item is inserted.
217 x : object
218 Item that is inserted.
219 at : `list` of `lsst.pex.config.callStack.StackFrame`, optional
220 The call stack (created by
221 `lsst.pex.config.callStack.getCallStack`).
222 label : `str`, optional
223 Event label for the history.
224 setHistory : `bool`, optional
225 Enable setting the field's history, using the value of the ``at``
226 parameter. Default is `True`.
227 """
228 if at is None:
229 at = getCallStack()
230 self.__setitem____setitem____setitem____setitem__(slice(i, i), [x], at=at, label=label, setHistory=setHistory)
231
232 def __repr__(self):
233 return repr(self._list_list)
234
235 def __str__(self):
236 return str(self._list_list)
237
238 def __eq__(self, other):
239 try:
240 if len(self) != len(other):
241 return False
242
243 for i, j in zip(self, other):
244 if i != j:
245 return False
246 return True
247 except AttributeError:
248 # other is not a sequence type
249 return False
250
251 def __ne__(self, other):
252 return not self.__eq____eq__(other)
253
254 def __setattr__(self, attr, value, at=None, label="assignment"):
255 if hasattr(getattr(self.__class__, attr, None), "__set__"):
256 # This allows properties to work.
257 object.__setattr__(self, attr, value)
258 elif attr in self.__dict__ or attr in ["_field", "_config_", "_history", "_list", "__doc__"]:
259 # This allows specific private attributes to work.
260 object.__setattr__(self, attr, value)
261 else:
262 # We throw everything else.
263 msg = "%s has no attribute %s" % (_typeStr(self._field_field), attr)
264 raise FieldValidationError(self._field_field, self._config_config, msg)
265
266 def __reduce__(self):
268 f"Proxy container for config field {self._field.name} cannot "
269 "be pickled; it should be converted to a built-in container before "
270 "being assigned to other objects or variables."
271 )
272
273
274class ListField(Field[List[FieldTypeVar]], Generic[FieldTypeVar]):
275 """A configuration field (`~lsst.pex.config.Field` subclass) that contains
276 a list of values of a specific type.
277
278 Parameters
279 ----------
280 doc : `str`
281 A description of the field.
282 dtype : class, optional
283 The data type of items in the list. Optional if supplied as typing
284 argument to the class.
285 default : sequence, optional
286 The default items for the field.
287 optional : `bool`, optional
288 Set whether the field is *optional*. When `False`,
289 `lsst.pex.config.Config.validate` will fail if the field's value is
290 `None`.
291 listCheck : callable, optional
292 A callable that validates the list as a whole.
293 itemCheck : callable, optional
294 A callable that validates individual items in the list.
295 length : `int`, optional
296 If set, this field must contain exactly ``length`` number of items.
297 minLength : `int`, optional
298 If set, this field must contain *at least* ``minLength`` number of
299 items.
300 maxLength : `int`, optional
301 If set, this field must contain *no more than* ``maxLength`` number of
302 items.
303 deprecated : None or `str`, optional
304 A description of why this Field is deprecated, including removal date.
305 If not None, the string is appended to the docstring for this Field.
306
307 See also
308 --------
309 ChoiceField
310 ConfigChoiceField
311 ConfigDictField
312 ConfigField
313 ConfigurableField
314 DictField
315 Field
316 RangeField
317 RegistryField
318 """
319
321 self,
322 doc,
323 dtype=None,
324 default=None,
325 optional=False,
326 listCheck=None,
327 itemCheck=None,
328 length=None,
329 minLength=None,
330 maxLength=None,
331 deprecated=None,
332 ):
333 if dtype is None:
334 raise ValueError(
335 "dtype must either be supplied as an argument or as a type argument to the class"
336 )
337 if dtype not in Field.supportedTypes:
338 raise ValueError("Unsupported dtype %s" % _typeStr(dtype))
339 if length is not None:
340 if length <= 0:
341 raise ValueError("'length' (%d) must be positive" % length)
342 minLength = None
343 maxLength = None
344 else:
345 if maxLength is not None and maxLength <= 0:
346 raise ValueError("'maxLength' (%d) must be positive" % maxLength)
347 if minLength is not None and maxLength is not None and minLength > maxLength:
348 raise ValueError(
349 "'maxLength' (%d) must be at least"
350 " as large as 'minLength' (%d)" % (maxLength, minLength)
351 )
352
353 if listCheck is not None and not hasattr(listCheck, "__call__"):
354 raise ValueError("'listCheck' must be callable")
355 if itemCheck is not None and not hasattr(itemCheck, "__call__"):
356 raise ValueError("'itemCheck' must be callable")
357
358 source = getStackFrame()
359 self._setup_setup(
360 doc=doc,
361 dtype=List,
362 default=default,
363 check=None,
364 optional=optional,
365 source=source,
366 deprecated=deprecated,
367 )
368
369 self.listChecklistCheck = listCheck
370 """Callable used to check the list as a whole.
371 """
372
373 self.itemCheckitemCheck = itemCheck
374 """Callable used to validate individual items as they are inserted
375 into the list.
376 """
377
378 self.itemtypeitemtype = dtype
379 """Data type of list items.
380 """
381
382 self.lengthlength = length
383 """Number of items that must be present in the list (or `None` to
384 disable checking the list's length).
385 """
386
387 self.minLengthminLength = minLength
388 """Minimum number of items that must be present in the list (or `None`
389 to disable checking the list's minimum length).
390 """
391
392 self.maxLengthmaxLength = maxLength
393 """Maximum number of items that must be present in the list (or `None`
394 to disable checking the list's maximum length).
395 """
396
397 def validate(self, instance):
398 """Validate the field.
399
400 Parameters
401 ----------
402 instance : `lsst.pex.config.Config`
403 The config instance that contains this field.
404
405 Raises
406 ------
408 Raised if:
409
410 - The field is not optional, but the value is `None`.
411 - The list itself does not meet the requirements of the `length`,
412 `minLength`, or `maxLength` attributes.
413 - The `listCheck` callable returns `False`.
414
415 Notes
416 -----
417 Individual item checks (`itemCheck`) are applied when each item is
418 set and are not re-checked by this method.
419 """
420 Field.validate(self, instance)
421 value = self.__get____get____get____get__(instance)
422 if value is not None:
423 lenValue = len(value)
424 if self.lengthlength is not None and not lenValue == self.lengthlength:
425 msg = "Required list length=%d, got length=%d" % (self.lengthlength, lenValue)
426 raise FieldValidationError(self, instance, msg)
427 elif self.minLengthminLength is not None and lenValue < self.minLengthminLength:
428 msg = "Minimum allowed list length=%d, got length=%d" % (self.minLengthminLength, lenValue)
429 raise FieldValidationError(self, instance, msg)
430 elif self.maxLengthmaxLength is not None and lenValue > self.maxLengthmaxLength:
431 msg = "Maximum allowed list length=%d, got length=%d" % (self.maxLengthmaxLength, lenValue)
432 raise FieldValidationError(self, instance, msg)
433 elif self.listChecklistCheck is not None and not self.listChecklistCheck(value):
434 msg = "%s is not a valid value" % str(value)
435 raise FieldValidationError(self, instance, msg)
436
438 self,
439 instance: Config,
440 value: Union[Iterable[FieldTypeVar], None],
441 at: Any = None,
442 label: str = "assignment",
443 ) -> None:
444 if instance._frozen:
445 raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
446
447 if at is None:
448 at = getCallStack()
449
450 if value is not None:
451 value = List(instance, self, value, at, label)
452 else:
453 history = instance._history.setdefault(self.name, [])
454 history.append((value, at, label))
455
456 instance._storage[self.name] = value
457
458 def toDict(self, instance):
459 """Convert the value of this field to a plain `list`.
460
461 `lsst.pex.config.Config.toDict` is the primary user of this method.
462
463 Parameters
464 ----------
465 instance : `lsst.pex.config.Config`
466 The config instance that contains this field.
467
468 Returns
469 -------
470 `list`
471 Plain `list` of items, or `None` if the field is not set.
472 """
473 value = self.__get____get____get____get__(instance)
474 return list(value) if value is not None else None
475
476 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
477 """Compare two config instances for equality with respect to this
478 field.
479
480 `lsst.pex.config.config.compare` is the primary user of this method.
481
482 Parameters
483 ----------
484 instance1 : `lsst.pex.config.Config`
485 Left-hand-side `~lsst.pex.config.Config` instance in the
486 comparison.
487 instance2 : `lsst.pex.config.Config`
488 Right-hand-side `~lsst.pex.config.Config` instance in the
489 comparison.
490 shortcut : `bool`
491 If `True`, return as soon as an **inequality** is found.
492 rtol : `float`
493 Relative tolerance for floating point comparisons.
494 atol : `float`
495 Absolute tolerance for floating point comparisons.
496 output : callable
497 If not None, a callable that takes a `str`, used (possibly
498 repeatedly) to report inequalities.
499
500 Returns
501 -------
502 equal : `bool`
503 `True` if the fields are equal; `False` otherwise.
504
505 Notes
506 -----
507 Floating point comparisons are performed by `numpy.allclose`.
508 """
509 l1 = getattr(instance1, self.name)
510 l2 = getattr(instance2, self.name)
511 name = getComparisonName(
512 _joinNamePath(instance1._name, self.name), _joinNamePath(instance2._name, self.name)
513 )
514 if not compareScalars("isnone for %s" % name, l1 is None, l2 is None, output=output):
515 return False
516 if l1 is None and l2 is None:
517 return True
518 if not compareScalars("size for %s" % name, len(l1), len(l2), output=output):
519 return False
520 equal = True
521 for n, v1, v2 in zip(range(len(l1)), l1, l2):
522 result = compareScalars(
523 "%s[%d]" % (name, n), v1, v2, dtype=self.dtypedtype, rtol=rtol, atol=atol, output=output
524 )
525 if not result and shortcut:
526 return False
527 equal = equal and result
528 return equal
"Field[FieldTypeVar]" __get__(self, None instance, Any owner=None, Any at=None, str label="default")
Definition: config.py:713
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:722
FieldTypeVar __get__(self, "Config" instance, Any owner=None, Any at=None, str label="default")
Definition: config.py:719
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition: config.py:494
def __init__(self, doc, dtype=None, default=None, optional=False, listCheck=None, itemCheck=None, length=None, minLength=None, maxLength=None, deprecated=None)
Definition: listField.py:332
None __set__(self, Config instance, Union[Iterable[FieldTypeVar], None] value, Any at=None, str label="assignment")
Definition: listField.py:443
def validate(self, instance)
Definition: listField.py:397
def __delitem__(self, i, at=None, label="delitem", setHistory=True)
Definition: listField.py:198
None __setitem__(self, slice i, Iterable[FieldTypeVar] x, Any at=None, str label="setitem", bool setHistory=True)
Definition: listField.py:164
FieldTypeVar __getitem__(self, int i)
Definition: listField.py:188
None __setitem__(self, int i, FieldTypeVar x, Any at=None, str label="setitem", bool setHistory=True)
Definition: listField.py:153
def __setattr__(self, attr, value, at=None, label="assignment")
Definition: listField.py:254
def __init__(self, config, field, value, at, label, setHistory=True)
Definition: listField.py:81
def insert(self, i, x, at=None, label="insert", setHistory=True)
Definition: listField.py:210
def __setitem__(self, i, x, at=None, label="setitem", setHistory=True)
Definition: listField.py:167
def validateItem(self, i, x)
Definition: listField.py:105
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
def getCallStack(skip=0)
Definition: callStack.py:174
def getStackFrame(relative=0)
Definition: callStack.py:58
def compareScalars(name, v1, v2, output, rtol=1e-8, atol=1e-8, dtype=None)
Definition: comparison.py:62
def getComparisonName(name1, name2)
Definition: comparison.py:40