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 .config import Config, Field, FieldValidationError, _joinNamePath, _typeStr
31from .comparison import compareConfigs, getComparisonName
32from .callStack import getCallStack, getStackFrame
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" %
84 _typeStr(dtype))
85 if default is None: 85 ↛ 87line 85 didn't jump to line 87, because the condition on line 85 was never false
86 default = dtype
87 source = getStackFrame()
88 self._setup(doc=doc, dtype=dtype, default=default, check=check,
89 optional=False, source=source, deprecated=deprecated)
91 def __get__(self, instance, owner=None):
92 if instance is None or not isinstance(instance, Config):
93 return self
94 else:
95 value = instance._storage.get(self.name, None)
96 if value is None:
97 at = getCallStack()
98 at.insert(0, self.source)
99 self.__set__(instance, self.default, at=at, label="default")
100 return value
102 def __set__(self, instance, value, at=None, label="assignment"):
103 if instance._frozen:
104 raise FieldValidationError(self, instance,
105 "Cannot modify a frozen Config")
106 name = _joinNamePath(prefix=instance._name, name=self.name)
108 if value != self.dtype and type(value) != self.dtype:
109 msg = "Value %s is of incorrect type %s. Expected %s" % \
110 (value, _typeStr(value), _typeStr(self.dtype))
111 raise FieldValidationError(self, instance, msg)
113 if at is None:
114 at = getCallStack()
116 oldValue = instance._storage.get(self.name, None)
117 if oldValue is None:
118 if value == self.dtype:
119 instance._storage[self.name] = self.dtype(__name=name, __at=at, __label=label)
120 else:
121 instance._storage[self.name] = self.dtype(__name=name, __at=at,
122 __label=label, **value._storage)
123 else:
124 if value == self.dtype:
125 value = value()
126 oldValue.update(__at=at, __label=label, **value._storage)
127 history = instance._history.setdefault(self.name, [])
128 history.append(("config value set", at, label))
130 def rename(self, instance):
131 """Rename the field in a `~lsst.pex.config.Config` (for internal use
132 only).
134 Parameters
135 ----------
136 instance : `lsst.pex.config.Config`
137 The config instance that contains this field.
139 Notes
140 -----
141 This method is invoked by the `lsst.pex.config.Config` object that
142 contains this field and should not be called directly.
144 Renaming is only relevant for `~lsst.pex.config.Field` instances that
145 hold subconfigs. `~lsst.pex.config.Fields` that hold subconfigs should
146 rename each subconfig with the full field name as generated by
147 `lsst.pex.config.config._joinNamePath`.
148 """
149 value = self.__get__(instance)
150 value._rename(_joinNamePath(instance._name, self.name))
152 def _collectImports(self, instance, imports):
153 value = self.__get__(instance)
154 value._collectImports()
155 imports |= value._imports
157 def save(self, outfile, instance):
158 """Save this field to a file (for internal use only).
160 Parameters
161 ----------
162 outfile : file-like object
163 A writeable field handle.
164 instance : `Config`
165 The `Config` instance that contains this field.
167 Notes
168 -----
169 This method is invoked by the `~lsst.pex.config.Config` object that
170 contains this field and should not be called directly.
172 The output consists of the documentation string
173 (`lsst.pex.config.Field.doc`) formatted as a Python comment. The second
174 line is formatted as an assignment: ``{fullname}={value}``.
176 This output can be executed with Python.
177 """
178 value = self.__get__(instance)
179 value._save(outfile)
181 def freeze(self, instance):
182 """Make this field read-only.
184 Parameters
185 ----------
186 instance : `lsst.pex.config.Config`
187 The config instance that contains this field.
189 Notes
190 -----
191 Freezing is only relevant for fields that hold subconfigs. Fields which
192 hold subconfigs should freeze each subconfig.
194 **Subclasses should implement this method.**
195 """
196 value = self.__get__(instance)
197 value.freeze()
199 def toDict(self, instance):
200 """Convert the field value so that it can be set as the value of an
201 item in a `dict` (for internal use only).
203 Parameters
204 ----------
205 instance : `Config`
206 The `Config` that contains this field.
208 Returns
209 -------
210 value : object
211 The field's value. See *Notes*.
213 Notes
214 -----
215 This method invoked by the owning `~lsst.pex.config.Config` object and
216 should not be called directly.
218 Simple values are passed through. Complex data structures must be
219 manipulated. For example, a `~lsst.pex.config.Field` holding a
220 subconfig should, instead of the subconfig object, return a `dict`
221 where the keys are the field names in the subconfig, and the values are
222 the field values in the subconfig.
223 """
224 value = self.__get__(instance)
225 return value.toDict()
227 def validate(self, instance):
228 """Validate the field (for internal use only).
230 Parameters
231 ----------
232 instance : `lsst.pex.config.Config`
233 The config instance that contains this field.
235 Raises
236 ------
237 lsst.pex.config.FieldValidationError
238 Raised if verification fails.
240 Notes
241 -----
242 This method provides basic validation:
244 - Ensures that the value is not `None` if the field is not optional.
245 - Ensures type correctness.
246 - Ensures that the user-provided ``check`` function is valid.
248 Most `~lsst.pex.config.Field` subclasses should call
249 `lsst.pex.config.field.Field.validate` if they re-implement
250 `~lsst.pex.config.field.Field.validate`.
251 """
252 value = self.__get__(instance)
253 value.validate()
255 if self.check is not None and not self.check(value):
256 msg = "%s is not a valid value" % str(value)
257 raise FieldValidationError(self, instance, msg)
259 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
260 """Compare two fields for equality.
262 Used by `ConfigField.compare`.
264 Parameters
265 ----------
266 instance1 : `lsst.pex.config.Config`
267 Left-hand side config instance to compare.
268 instance2 : `lsst.pex.config.Config`
269 Right-hand side config instance to compare.
270 shortcut : `bool`
271 If `True`, this function returns as soon as an inequality if found.
272 rtol : `float`
273 Relative tolerance for floating point comparisons.
274 atol : `float`
275 Absolute tolerance for floating point comparisons.
276 output : callable
277 A callable that takes a string, used (possibly repeatedly) to
278 report inequalities.
280 Returns
281 -------
282 isEqual : bool
283 `True` if the fields are equal, `False` otherwise.
285 Notes
286 -----
287 Floating point comparisons are performed by `numpy.allclose`.
288 """
289 c1 = getattr(instance1, self.name)
290 c2 = getattr(instance2, self.name)
291 name = getComparisonName(
292 _joinNamePath(instance1._name, self.name),
293 _joinNamePath(instance2._name, self.name)
294 )
295 return compareConfigs(name, c1, c2, shortcut=shortcut, rtol=rtol, atol=atol, output=output)