LSST Applications g180d380827+6621f76652,g2079a07aa2+86d27d4dc4,g2305ad1205+f5a9e323a1,g2bbee38e9b+c6a8a0fb72,g337abbeb29+c6a8a0fb72,g33d1c0ed96+c6a8a0fb72,g3a166c0a6a+c6a8a0fb72,g3ddfee87b4+9a10e1fe7b,g48712c4677+c9a099281a,g487adcacf7+f2e03ea30b,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+aead732c78,g64a986408d+eddffb812c,g858d7b2824+eddffb812c,g864b0138d7+aa38e45daa,g974c55ee3d+f37bf00e57,g99cad8db69+119519a52d,g9c22b2923f+e2510deafe,g9ddcbc5298+9a081db1e4,ga1e77700b3+03d07e1c1f,gb0e22166c9+60f28cb32d,gb23b769143+eddffb812c,gba4ed39666+c2a2e4ac27,gbb8dafda3b+27317ec8e9,gbd998247f1+585e252eca,gc120e1dc64+5817c176a8,gc28159a63d+c6a8a0fb72,gc3e9b769f7+6707aea8b4,gcf0d15dbbd+9a10e1fe7b,gdaeeff99f8+f9a426f77a,ge6526c86ff+6a2e01d432,ge79ae78c31+c6a8a0fb72,gee10cc3b42+585e252eca,gff1a9f87cc+eddffb812c,v27.0.0.rc1
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
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` or `None`,\
214 optional
215 The call stack (created by
216 `lsst.pex.config.callStack.getCallStack`).
217 label : `str`, optional
218 Event label for the history.
219 setHistory : `bool`, optional
220 Enable setting the field's history, using the value of the ``at``
221 parameter. Default is `True`.
222 """
223 if at is None:
224 at = getCallStack()
225 self.__setitem____setitem____setitem__(slice(i, i), [x], at=at, label=label, setHistory=setHistory)
226
227 def __repr__(self):
228 return repr(self._list)
229
230 def __str__(self):
231 return str(self._list)
232
233 def __eq__(self, other):
234 try:
235 if len(self) != len(other):
236 return False
237
238 for i, j in zip(self, other):
239 if i != j:
240 return False
241 return True
242 except AttributeError:
243 # other is not a sequence type
244 return False
245
246 def __ne__(self, other):
247 return not self.__eq__(other)
248
249 def __setattr__(self, attr, value, at=None, label="assignment"):
250 if hasattr(getattr(self.__class__, attr, None), "__set__"):
251 # This allows properties to work.
252 object.__setattr__(self, attr, value)
253 elif attr in self.__dict__ or attr in ["_field", "_config_", "_history", "_list", "__doc__"]:
254 # This allows specific private attributes to work.
255 object.__setattr__(self, attr, value)
256 else:
257 # We throw everything else.
258 msg = f"{_typeStr(self._field)} has no attribute {attr}"
259 raise FieldValidationError(self._field, self._config_config, msg)
260
261 def __reduce__(self):
263 f"Proxy container for config field {self._field.name} cannot "
264 "be pickled; it should be converted to a built-in container before "
265 "being assigned to other objects or variables."
266 )
267
268
269class ListField(Field[List[FieldTypeVar]], Generic[FieldTypeVar]):
270 """A configuration field (`~lsst.pex.config.Field` subclass) that contains
271 a list of values of a specific type.
272
273 Parameters
274 ----------
275 doc : `str`
276 A description of the field.
277 dtype : class, optional
278 The data type of items in the list. Optional if supplied as typing
279 argument to the class.
280 default : sequence, optional
281 The default items for the field.
282 optional : `bool`, optional
283 Set whether the field is *optional*. When `False`,
284 `lsst.pex.config.Config.validate` will fail if the field's value is
285 `None`.
286 listCheck : callable, optional
287 A callable that validates the list as a whole.
288 itemCheck : callable, optional
289 A callable that validates individual items in the list.
290 length : `int`, optional
291 If set, this field must contain exactly ``length`` number of items.
292 minLength : `int`, optional
293 If set, this field must contain *at least* ``minLength`` number of
294 items.
295 maxLength : `int`, optional
296 If set, this field must contain *no more than* ``maxLength`` number of
297 items.
298 deprecated : None or `str`, optional
299 A description of why this Field is deprecated, including removal date.
300 If not None, the string is appended to the docstring for this Field.
301
302 See Also
303 --------
304 ChoiceField
305 ConfigChoiceField
306 ConfigDictField
307 ConfigField
308 ConfigurableField
309 DictField
310 Field
311 RangeField
312 RegistryField
313 """
314
316 self,
317 doc,
318 dtype=None,
319 default=None,
320 optional=False,
321 listCheck=None,
322 itemCheck=None,
323 length=None,
324 minLength=None,
325 maxLength=None,
326 deprecated=None,
327 ):
328 if dtype is None:
329 raise ValueError(
330 "dtype must either be supplied as an argument or as a type argument to the class"
331 )
332 if dtype not in Field.supportedTypes:
333 raise ValueError("Unsupported dtype %s" % _typeStr(dtype))
334 if length is not None:
335 if length <= 0:
336 raise ValueError("'length' (%d) must be positive" % length)
337 minLength = None
338 maxLength = None
339 else:
340 if maxLength is not None and maxLength <= 0:
341 raise ValueError("'maxLength' (%d) must be positive" % maxLength)
342 if minLength is not None and maxLength is not None and minLength > maxLength:
343 raise ValueError(
344 "'maxLength' (%d) must be at least as large as 'minLength' (%d)" % (maxLength, minLength)
345 )
346
347 if listCheck is not None and not hasattr(listCheck, "__call__"):
348 raise ValueError("'listCheck' must be callable")
349 if itemCheck is not None and not hasattr(itemCheck, "__call__"):
350 raise ValueError("'itemCheck' must be callable")
351
352 source = getStackFrame()
353 self._setup(
354 doc=doc,
355 dtype=List,
356 default=default,
357 check=None,
358 optional=optional,
359 source=source,
360 deprecated=deprecated,
361 )
362
363 self.listCheck = listCheck
364 """Callable used to check the list as a whole.
365 """
366
367 self.itemCheck = itemCheck
368 """Callable used to validate individual items as they are inserted
369 into the list.
370 """
371
372 self.itemtype = dtype
373 """Data type of list items.
374 """
375
376 self.length = length
377 """Number of items that must be present in the list (or `None` to
378 disable checking the list's length).
379 """
380
381 self.minLength = minLength
382 """Minimum number of items that must be present in the list (or `None`
383 to disable checking the list's minimum length).
384 """
385
386 self.maxLength = maxLength
387 """Maximum number of items that must be present in the list (or `None`
388 to disable checking the list's maximum length).
389 """
390
391 def validate(self, instance):
392 """Validate the field.
393
394 Parameters
395 ----------
396 instance : `lsst.pex.config.Config`
397 The config instance that contains this field.
398
399 Raises
400 ------
401 lsst.pex.config.FieldValidationError
402 Raised if:
403
404 - The field is not optional, but the value is `None`.
405 - The list itself does not meet the requirements of the ``length``,
406 ``minLength``, or ``maxLength`` attributes.
407 - The ``listCheck`` callable returns `False`.
408
409 Notes
410 -----
411 Individual item checks (``itemCheck``) are applied when each item is
412 set and are not re-checked by this method.
413 """
414 Field.validate(self, instance)
415 value = self.__get____get____get__(instance)
416 if value is not None:
417 lenValue = len(value)
418 if self.length is not None and not lenValue == self.length:
419 msg = "Required list length=%d, got length=%d" % (self.length, lenValue)
420 raise FieldValidationError(self, instance, msg)
421 elif self.minLength is not None and lenValue < self.minLength:
422 msg = "Minimum allowed list length=%d, got length=%d" % (self.minLength, lenValue)
423 raise FieldValidationError(self, instance, msg)
424 elif self.maxLength is not None and lenValue > self.maxLength:
425 msg = "Maximum allowed list length=%d, got length=%d" % (self.maxLength, lenValue)
426 raise FieldValidationError(self, instance, msg)
427 elif self.listCheck is not None and not self.listCheck(value):
428 msg = "%s is not a valid value" % str(value)
429 raise FieldValidationError(self, instance, msg)
430
432 self,
433 instance: Config,
434 value: Iterable[FieldTypeVar] | None,
435 at: Any = None,
436 label: str = "assignment",
437 ) -> None:
438 if instance._frozen:
439 raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
440
441 if at is None:
442 at = getCallStack()
443
444 if value is not None:
445 value = List(instance, self, value, at, label)
446 else:
447 history = instance._history.setdefault(self.namenamename, [])
448 history.append((value, at, label))
449
450 instance._storage[self.namenamename] = value
451
452 def toDict(self, instance):
453 """Convert the value of this field to a plain `list`.
454
455 `lsst.pex.config.Config.toDict` is the primary user of this method.
456
457 Parameters
458 ----------
459 instance : `lsst.pex.config.Config`
460 The config instance that contains this field.
461
462 Returns
463 -------
464 `list`
465 Plain `list` of items, or `None` if the field is not set.
466 """
467 value = self.__get____get____get__(instance)
468 return list(value) if value is not None else None
469
470 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
471 """Compare two config instances for equality with respect to this
472 field.
473
474 `lsst.pex.config.config.compare` is the primary user of this method.
475
476 Parameters
477 ----------
478 instance1 : `lsst.pex.config.Config`
479 Left-hand-side `~lsst.pex.config.Config` instance in the
480 comparison.
481 instance2 : `lsst.pex.config.Config`
482 Right-hand-side `~lsst.pex.config.Config` instance in the
483 comparison.
484 shortcut : `bool`
485 If `True`, return as soon as an **inequality** is found.
486 rtol : `float`
487 Relative tolerance for floating point comparisons.
488 atol : `float`
489 Absolute tolerance for floating point comparisons.
490 output : callable
491 If not None, a callable that takes a `str`, used (possibly
492 repeatedly) to report inequalities.
493
494 Returns
495 -------
496 equal : `bool`
497 `True` if the fields are equal; `False` otherwise.
498
499 Notes
500 -----
501 Floating point comparisons are performed by `numpy.allclose`.
502 """
503 l1 = getattr(instance1, self.namenamename)
504 l2 = getattr(instance2, self.namenamename)
505 name = getComparisonName(
506 _joinNamePath(instance1._name, self.namenamename), _joinNamePath(instance2._name, self.namenamename)
507 )
508 if not compareScalars("isnone for %s" % name, l1 is None, l2 is None, output=output):
509 return False
510 if l1 is None and l2 is None:
511 return True
512 if not compareScalars("size for %s" % name, len(l1), len(l2), output=output):
513 return False
514 equal = True
515 for n, v1, v2 in zip(range(len(l1)), l1, l2):
516 result = compareScalars(
517 "%s[%d]" % (name, n), v1, v2, dtype=self.dtype, rtol=rtol, atol=atol, output=output
518 )
519 if not result and shortcut:
520 return False
521 equal = equal and result
522 return equal
__get__(self, instance, owner=None, at=None, label="default")
Definition config.py:728
FieldTypeVar __get__(self, Config instance, Any owner=None, Any at=None, str label="default")
Definition config.py:725
Field[FieldTypeVar] __get__(self, None instance, Any owner=None, Any at=None, str label="default")
Definition config.py:719
_setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition config.py:497
_compare(self, instance1, instance2, shortcut, rtol, atol, output)
Definition listField.py:470
__init__(self, doc, dtype=None, default=None, optional=False, listCheck=None, itemCheck=None, length=None, minLength=None, maxLength=None, deprecated=None)
Definition listField.py:327
None __set__(self, Config instance, Iterable[FieldTypeVar]|None value, Any at=None, str label="assignment")
Definition listField.py:437
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:249
__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:932