Coverage for python/lsst/pex/config/configField.py: 26%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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/>.
28__all__ = ["ConfigField"]
30from .callStack import getCallStack, getStackFrame
31from .comparison import compareConfigs, getComparisonName
32from .config import Config, Field, FieldValidationError, _joinNamePath, _typeStr
35class ConfigField(Field):
36 """A configuration field (`~lsst.pex.config.Field` subclass) that takes a
37 `~lsst.pex.config.Config`-type as a value.
39 Parameters
40 ----------
41 doc : `str`
42 A description of the configuration field.
43 dtype : `lsst.pex.config.Config`-type
44 The type of the field, which must be a subclass of
45 `lsst.pex.config.Config`.
46 default : `lsst.pex.config.Config`, optional
47 If default is `None`, the field will default to a default-constructed
48 instance of ``dtype``. Additionally, to allow for fewer deep-copies,
49 assigning an instance of ``ConfigField`` to ``dtype`` itself, is
50 considered equivalent to assigning a default-constructed sub-config.
51 This means that the argument default can be ``dtype``, as well as an
52 instance of ``dtype``.
53 check : callable, optional
54 A callback function that validates the field's value, returning `True`
55 if the value is valid, and `False` otherwise.
56 deprecated : None or `str`, optional
57 A description of why this Field is deprecated, including removal date.
58 If not None, the string is appended to the docstring for this Field.
60 See also
61 --------
62 ChoiceField
63 ConfigChoiceField
64 ConfigDictField
65 ConfigurableField
66 DictField
67 Field
68 ListField
69 RangeField
70 RegistryField
72 Notes
73 -----
74 The behavior of this type of field is much like that of the base `Field`
75 type.
77 Assigning to ``ConfigField`` will update all of the fields in the
78 configuration.
79 """
81 def __init__(self, doc, dtype, default=None, check=None, deprecated=None):
82 if not issubclass(dtype, Config): 82 ↛ 83line 82 didn't jump to line 83, because the condition on line 82 was never true
83 raise ValueError("dtype=%s is not a subclass of Config" % _typeStr(dtype))
84 if default is None: 84 ↛ 86line 84 didn't jump to line 86, because the condition on line 84 was never false
85 default = dtype
86 source = getStackFrame()
87 self._setup(
88 doc=doc,
89 dtype=dtype,
90 default=default,
91 check=check,
92 optional=False,
93 source=source,
94 deprecated=deprecated,
95 )
97 def __get__(self, instance, owner=None):
98 if instance is None or not isinstance(instance, Config):
99 return self
100 else:
101 value = instance._storage.get(self.name, None)
102 if value is None:
103 at = getCallStack()
104 at.insert(0, self.source)
105 self.__set__(instance, self.default, at=at, label="default")
106 return value
108 def __set__(self, instance, value, at=None, label="assignment"):
109 if instance._frozen:
110 raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
111 name = _joinNamePath(prefix=instance._name, name=self.name)
113 if value != self.dtype and type(value) != self.dtype:
114 msg = "Value %s is of incorrect type %s. Expected %s" % (
115 value,
116 _typeStr(value),
117 _typeStr(self.dtype),
118 )
119 raise FieldValidationError(self, instance, msg)
121 if at is None:
122 at = getCallStack()
124 oldValue = instance._storage.get(self.name, None)
125 if oldValue is None:
126 if value == self.dtype:
127 instance._storage[self.name] = self.dtype(__name=name, __at=at, __label=label)
128 else:
129 instance._storage[self.name] = self.dtype(
130 __name=name, __at=at, __label=label, **value._storage
131 )
132 else:
133 if value == self.dtype:
134 value = value()
135 oldValue.update(__at=at, __label=label, **value._storage)
136 history = instance._history.setdefault(self.name, [])
137 history.append(("config value set", at, label))
139 def rename(self, instance):
140 """Rename the field in a `~lsst.pex.config.Config` (for internal use
141 only).
143 Parameters
144 ----------
145 instance : `lsst.pex.config.Config`
146 The config instance that contains this field.
148 Notes
149 -----
150 This method is invoked by the `lsst.pex.config.Config` object that
151 contains this field and should not be called directly.
153 Renaming is only relevant for `~lsst.pex.config.Field` instances that
154 hold subconfigs. `~lsst.pex.config.Fields` that hold subconfigs should
155 rename each subconfig with the full field name as generated by
156 `lsst.pex.config.config._joinNamePath`.
157 """
158 value = self.__get__(instance)
159 value._rename(_joinNamePath(instance._name, self.name))
161 def _collectImports(self, instance, imports):
162 value = self.__get__(instance)
163 value._collectImports()
164 imports |= value._imports
166 def save(self, outfile, instance):
167 """Save this field to a file (for internal use only).
169 Parameters
170 ----------
171 outfile : file-like object
172 A writeable field handle.
173 instance : `Config`
174 The `Config` instance that contains this field.
176 Notes
177 -----
178 This method is invoked by the `~lsst.pex.config.Config` object that
179 contains this field and should not be called directly.
181 The output consists of the documentation string
182 (`lsst.pex.config.Field.doc`) formatted as a Python comment. The second
183 line is formatted as an assignment: ``{fullname}={value}``.
185 This output can be executed with Python.
186 """
187 value = self.__get__(instance)
188 value._save(outfile)
190 def freeze(self, instance):
191 """Make this field read-only.
193 Parameters
194 ----------
195 instance : `lsst.pex.config.Config`
196 The config instance that contains this field.
198 Notes
199 -----
200 Freezing is only relevant for fields that hold subconfigs. Fields which
201 hold subconfigs should freeze each subconfig.
203 **Subclasses should implement this method.**
204 """
205 value = self.__get__(instance)
206 value.freeze()
208 def toDict(self, instance):
209 """Convert the field value so that it can be set as the value of an
210 item in a `dict` (for internal use only).
212 Parameters
213 ----------
214 instance : `Config`
215 The `Config` that contains this field.
217 Returns
218 -------
219 value : object
220 The field's value. See *Notes*.
222 Notes
223 -----
224 This method invoked by the owning `~lsst.pex.config.Config` object and
225 should not be called directly.
227 Simple values are passed through. Complex data structures must be
228 manipulated. For example, a `~lsst.pex.config.Field` holding a
229 subconfig should, instead of the subconfig object, return a `dict`
230 where the keys are the field names in the subconfig, and the values are
231 the field values in the subconfig.
232 """
233 value = self.__get__(instance)
234 return value.toDict()
236 def validate(self, instance):
237 """Validate the field (for internal use only).
239 Parameters
240 ----------
241 instance : `lsst.pex.config.Config`
242 The config instance that contains this field.
244 Raises
245 ------
246 lsst.pex.config.FieldValidationError
247 Raised if verification fails.
249 Notes
250 -----
251 This method provides basic validation:
253 - Ensures that the value is not `None` if the field is not optional.
254 - Ensures type correctness.
255 - Ensures that the user-provided ``check`` function is valid.
257 Most `~lsst.pex.config.Field` subclasses should call
258 `lsst.pex.config.field.Field.validate` if they re-implement
259 `~lsst.pex.config.field.Field.validate`.
260 """
261 value = self.__get__(instance)
262 value.validate()
264 if self.check is not None and not self.check(value):
265 msg = "%s is not a valid value" % str(value)
266 raise FieldValidationError(self, instance, msg)
268 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
269 """Compare two fields for equality.
271 Used by `ConfigField.compare`.
273 Parameters
274 ----------
275 instance1 : `lsst.pex.config.Config`
276 Left-hand side config instance to compare.
277 instance2 : `lsst.pex.config.Config`
278 Right-hand side config instance to compare.
279 shortcut : `bool`
280 If `True`, this function returns as soon as an inequality if found.
281 rtol : `float`
282 Relative tolerance for floating point comparisons.
283 atol : `float`
284 Absolute tolerance for floating point comparisons.
285 output : callable
286 A callable that takes a string, used (possibly repeatedly) to
287 report inequalities.
289 Returns
290 -------
291 isEqual : bool
292 `True` if the fields are equal, `False` otherwise.
294 Notes
295 -----
296 Floating point comparisons are performed by `numpy.allclose`.
297 """
298 c1 = getattr(instance1, self.name)
299 c2 = getattr(instance2, self.name)
300 name = getComparisonName(
301 _joinNamePath(instance1._name, self.name), _joinNamePath(instance2._name, self.name)
302 )
303 return compareConfigs(name, c1, c2, shortcut=shortcut, rtol=rtol, atol=atol, output=output)