LSST Applications g0f08755f38+82efc23009,g12f32b3c4e+e7bdf1200e,g1653933729+a8ce1bb630,g1a0ca8cf93+50eff2b06f,g28da252d5a+52db39f6a5,g2bbee38e9b+37c5a29d61,g2bc492864f+37c5a29d61,g2cdde0e794+c05ff076ad,g3156d2b45e+41e33cbcdc,g347aa1857d+37c5a29d61,g35bb328faa+a8ce1bb630,g3a166c0a6a+37c5a29d61,g3e281a1b8c+fb992f5633,g414038480c+7f03dfc1b0,g41af890bb2+11b950c980,g5fbc88fb19+17cd334064,g6b1c1869cb+12dd639c9a,g781aacb6e4+a8ce1bb630,g80478fca09+72e9651da0,g82479be7b0+04c31367b4,g858d7b2824+82efc23009,g9125e01d80+a8ce1bb630,g9726552aa6+8047e3811d,ga5288a1d22+e532dc0a0b,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gc28159a63d+37c5a29d61,gcf0d15dbbd+2acd6d4d48,gd7358e8bfb+778a810b6e,gda3e153d99+82efc23009,gda6a2b7d83+2acd6d4d48,gdaeeff99f8+1711a396fd,ge2409df99d+6b12de1076,ge79ae78c31+37c5a29d61,gf0baf85859+d0a5978c5a,gf3967379c6+4954f8c433,gfb92a5be7c+82efc23009,gfec2e1e490+2aaed99252,w.2024.46
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``.
60 at : `list` of `~lsst.pex.config.callStack.StackFrame`
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 @overload
151 self,
152 i: slice,
153 x: Iterable[FieldTypeVar],
154 at: Any = None,
155 label: str = "setitem",
156 setHistory: bool = True,
157 ) -> None: ...
158
159 def __setitem__(self, i, x, at=None, label="setitem", setHistory=True):
160 if self._config_config._frozen:
161 raise FieldValidationError(self._field, self._config_config, "Cannot modify a frozen Config")
162 if isinstance(i, slice):
163 k, stop, step = i.indices(len(self))
164 for j, xj in enumerate(x):
165 xj = _autocast(xj, self._field.itemtype)
166 self.validateItem(k, xj)
167 x[j] = xj
168 k += step
169 else:
170 x = _autocast(x, self._field.itemtype)
171 self.validateItem(i, x)
172
173 self._list[i] = x
174 if setHistory:
175 if at is None:
176 at = getCallStack()
177 self.history.append((list(self._list), at, label))
178
179 @overload
180 def __getitem__(self, i: int) -> FieldTypeVar: ...
181
182 @overload
183 def __getitem__(self, i: slice) -> MutableSequence[FieldTypeVar]: ...
184
185 def __getitem__(self, i):
186 return self._list[i]
187
188 def __delitem__(self, i, at=None, label="delitem", setHistory=True):
189 if self._config_config._frozen:
190 raise FieldValidationError(self._field, self._config_config, "Cannot modify a frozen Config")
191 del self._list[i]
192 if setHistory:
193 if at is None:
194 at = getCallStack()
195 self.history.append((list(self._list), at, label))
196
197 def __iter__(self):
198 return iter(self._list)
199
200 def insert(self, i, x, at=None, label="insert", setHistory=True):
201 """Insert an item into the list at the given index.
202
203 Parameters
204 ----------
205 i : `int`
206 Index where the item is inserted.
207 x : object
208 Item that is inserted.
209 at : `list` of `~lsst.pex.config.callStack.StackFrame` or `None`,\
210 optional
211 The call stack (created by
212 `lsst.pex.config.callStack.getCallStack`).
213 label : `str`, optional
214 Event label for the history.
215 setHistory : `bool`, optional
216 Enable setting the field's history, using the value of the ``at``
217 parameter. Default is `True`.
218 """
219 if at is None:
220 at = getCallStack()
221 self.__setitem____setitem____setitem__(slice(i, i), [x], at=at, label=label, setHistory=setHistory)
222
223 def __repr__(self):
224 return repr(self._list)
225
226 def __str__(self):
227 return str(self._list)
228
229 def __eq__(self, other):
230 try:
231 if len(self) != len(other):
232 return False
233
234 for i, j in zip(self, other):
235 if i != j:
236 return False
237 return True
238 except AttributeError:
239 # other is not a sequence type
240 return False
241
242 def __ne__(self, other):
243 return not self.__eq__(other)
244
245 def __setattr__(self, attr, value, at=None, label="assignment"):
246 if hasattr(getattr(self.__class__, attr, None), "__set__"):
247 # This allows properties to work.
248 object.__setattr__(self, attr, value)
249 elif attr in self.__dict__ or attr in ["_field", "_config_", "_history", "_list", "__doc__"]:
250 # This allows specific private attributes to work.
251 object.__setattr__(self, attr, value)
252 else:
253 # We throw everything else.
254 msg = f"{_typeStr(self._field)} has no attribute {attr}"
255 raise FieldValidationError(self._field, self._config_config, msg)
256
257 def __reduce__(self):
259 f"Proxy container for config field {self._field.name} cannot "
260 "be pickled; it should be converted to a built-in container before "
261 "being assigned to other objects or variables."
262 )
263
264
265class ListField(Field[List[FieldTypeVar]], Generic[FieldTypeVar]):
266 """A configuration field (`~lsst.pex.config.Field` subclass) that contains
267 a list of values of a specific type.
268
269 Parameters
270 ----------
271 doc : `str`
272 A description of the field.
273 dtype : class, optional
274 The data type of items in the list. Optional if supplied as typing
275 argument to the class.
276 default : sequence, optional
277 The default items for the field.
278 optional : `bool`, optional
279 Set whether the field is *optional*. When `False`,
280 `lsst.pex.config.Config.validate` will fail if the field's value is
281 `None`.
282 listCheck : callable, optional
283 A callable that validates the list as a whole.
284 itemCheck : callable, optional
285 A callable that validates individual items in the list.
286 length : `int`, optional
287 If set, this field must contain exactly ``length`` number of items.
288 minLength : `int`, optional
289 If set, this field must contain *at least* ``minLength`` number of
290 items.
291 maxLength : `int`, optional
292 If set, this field must contain *no more than* ``maxLength`` number of
293 items.
294 deprecated : None or `str`, optional
295 A description of why this Field is deprecated, including removal date.
296 If not None, the string is appended to the docstring for this Field.
297
298 See Also
299 --------
300 ChoiceField
301 ConfigChoiceField
302 ConfigDictField
303 ConfigField
304 ConfigurableField
305 DictField
306 Field
307 RangeField
308 RegistryField
309 """
310
312 self,
313 doc,
314 dtype=None,
315 default=None,
316 optional=False,
317 listCheck=None,
318 itemCheck=None,
319 length=None,
320 minLength=None,
321 maxLength=None,
322 deprecated=None,
323 ):
324 if dtype is None:
325 raise ValueError(
326 "dtype must either be supplied as an argument or as a type argument to the class"
327 )
328 if dtype not in Field.supportedTypes:
329 raise ValueError(f"Unsupported dtype {_typeStr(dtype)}")
330 if length is not None:
331 if length <= 0:
332 raise ValueError("'length' (%d) must be positive" % length)
333 minLength = None
334 maxLength = None
335 else:
336 if maxLength is not None and maxLength <= 0:
337 raise ValueError("'maxLength' (%d) must be positive" % maxLength)
338 if minLength is not None and maxLength is not None and minLength > maxLength:
339 raise ValueError(
340 "'maxLength' (%d) must be at least as large as 'minLength' (%d)" % (maxLength, minLength)
341 )
342
343 if listCheck is not None and not hasattr(listCheck, "__call__"):
344 raise ValueError("'listCheck' must be callable")
345 if itemCheck is not None and not hasattr(itemCheck, "__call__"):
346 raise ValueError("'itemCheck' must be callable")
347
348 source = getStackFrame()
349 self._setup(
350 doc=doc,
351 dtype=List,
352 default=default,
353 check=None,
354 optional=optional,
355 source=source,
356 deprecated=deprecated,
357 )
358
359 self.listCheck = listCheck
360 """Callable used to check the list as a whole.
361 """
362
363 self.itemCheck = itemCheck
364 """Callable used to validate individual items as they are inserted
365 into the list.
366 """
367
368 self.itemtype = dtype
369 """Data type of list items.
370 """
371
372 self.length = length
373 """Number of items that must be present in the list (or `None` to
374 disable checking the list's length).
375 """
376
377 self.minLength = minLength
378 """Minimum number of items that must be present in the list (or `None`
379 to disable checking the list's minimum length).
380 """
381
382 self.maxLength = maxLength
383 """Maximum number of items that must be present in the list (or `None`
384 to disable checking the list's maximum length).
385 """
386
387 def validate(self, instance):
388 """Validate the field.
389
390 Parameters
391 ----------
392 instance : `lsst.pex.config.Config`
393 The config instance that contains this field.
394
395 Raises
396 ------
397 lsst.pex.config.FieldValidationError
398 Raised if:
399
400 - The field is not optional, but the value is `None`.
401 - The list itself does not meet the requirements of the ``length``,
402 ``minLength``, or ``maxLength`` attributes.
403 - The ``listCheck`` callable returns `False`.
404
405 Notes
406 -----
407 Individual item checks (``itemCheck``) are applied when each item is
408 set and are not re-checked by this method.
409 """
410 Field.validate(self, instance)
411 value = self.__get____get____get__(instance)
412 if value is not None:
413 lenValue = len(value)
414 if self.length is not None and not lenValue == self.length:
415 msg = "Required list length=%d, got length=%d" % (self.length, lenValue)
416 raise FieldValidationError(self, instance, msg)
417 elif self.minLength is not None and lenValue < self.minLength:
418 msg = "Minimum allowed list length=%d, got length=%d" % (self.minLength, lenValue)
419 raise FieldValidationError(self, instance, msg)
420 elif self.maxLength is not None and lenValue > self.maxLength:
421 msg = "Maximum allowed list length=%d, got length=%d" % (self.maxLength, lenValue)
422 raise FieldValidationError(self, instance, msg)
423 elif self.listCheck is not None and not self.listCheck(value):
424 msg = f"{value} is not a valid value"
425 raise FieldValidationError(self, instance, msg)
426
428 self,
429 instance: Config,
430 value: Iterable[FieldTypeVar] | None,
431 at: Any = None,
432 label: str = "assignment",
433 ) -> None:
434 if instance._frozen:
435 raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
436
437 if at is None:
438 at = getCallStack()
439
440 if value is not None:
441 value = List(instance, self, value, at, label)
442 else:
443 history = instance._history.setdefault(self.namenamename, [])
444 history.append((value, at, label))
445
446 instance._storage[self.namenamename] = value
447
448 def toDict(self, instance):
449 """Convert the value of this field to a plain `list`.
450
451 `lsst.pex.config.Config.toDict` is the primary user of this method.
452
453 Parameters
454 ----------
455 instance : `lsst.pex.config.Config`
456 The config instance that contains this field.
457
458 Returns
459 -------
460 `list`
461 Plain `list` of items, or `None` if the field is not set.
462 """
463 value = self.__get____get____get__(instance)
464 return list(value) if value is not None else None
465
466 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
467 """Compare two config instances for equality with respect to this
468 field.
469
470 `lsst.pex.config.config.compare` is the primary user of this method.
471
472 Parameters
473 ----------
474 instance1 : `lsst.pex.config.Config`
475 Left-hand-side `~lsst.pex.config.Config` instance in the
476 comparison.
477 instance2 : `lsst.pex.config.Config`
478 Right-hand-side `~lsst.pex.config.Config` instance in the
479 comparison.
480 shortcut : `bool`
481 If `True`, return as soon as an **inequality** is found.
482 rtol : `float`
483 Relative tolerance for floating point comparisons.
484 atol : `float`
485 Absolute tolerance for floating point comparisons.
486 output : callable
487 If not None, a callable that takes a `str`, used (possibly
488 repeatedly) to report inequalities.
489
490 Returns
491 -------
492 equal : `bool`
493 `True` if the fields are equal; `False` otherwise.
494
495 Notes
496 -----
497 Floating point comparisons are performed by `numpy.allclose`.
498 """
499 l1 = getattr(instance1, self.namenamename)
500 l2 = getattr(instance2, self.namenamename)
501 name = getComparisonName(
502 _joinNamePath(instance1._name, self.namenamename), _joinNamePath(instance2._name, self.namenamename)
503 )
504 if not compareScalars(f"isnone for {name}", l1 is None, l2 is None, output=output):
505 return False
506 if l1 is None and l2 is None:
507 return True
508 if not compareScalars(f"size for {name}", len(l1), len(l2), output=output):
509 return False
510 equal = True
511 for n, v1, v2 in zip(range(len(l1)), l1, l2):
512 result = compareScalars(
513 "%s[%d]" % (name, n), v1, v2, dtype=self.dtype, rtol=rtol, atol=atol, output=output
514 )
515 if not result and shortcut:
516 return False
517 equal = equal and result
518 return equal
__get__(self, instance, owner=None, at=None, label="default")
Definition config.py:707
FieldTypeVar __get__(self, Config instance, Any owner=None, Any at=None, str label="default")
Definition config.py:705
Field[FieldTypeVar] __get__(self, None instance, Any owner=None, Any at=None, str label="default")
Definition config.py:700
_setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition config.py:480
_compare(self, instance1, instance2, shortcut, rtol, atol, output)
Definition listField.py:466
__init__(self, doc, dtype=None, default=None, optional=False, listCheck=None, itemCheck=None, length=None, minLength=None, maxLength=None, deprecated=None)
Definition listField.py:323
None __set__(self, Config instance, Iterable[FieldTypeVar]|None value, Any at=None, str label="assignment")
Definition listField.py:433
None __setitem__(self, slice i, Iterable[FieldTypeVar] x, Any at=None, str label="setitem", bool setHistory=True)
Definition listField.py:157
FieldTypeVar __getitem__(self, int i)
Definition listField.py:180
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:200
__delitem__(self, i, at=None, label="delitem", setHistory=True)
Definition listField.py:188
__setattr__(self, attr, value, at=None, label="assignment")
Definition listField.py:245
__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:159
daf::base::PropertyList * list
Definition fits.cc:932