lsst.pex.config  18.1.0-3-g6b74884
configField.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 program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 
22 __all__ = ["ConfigField"]
23 
24 from .config import Config, Field, FieldValidationError, _joinNamePath, _typeStr
25 from .comparison import compareConfigs, getComparisonName
26 from .callStack import getCallStack, getStackFrame
27 
28 
30  """A configuration field (`~lsst.pex.config.Field` subclass) that takes a
31  `~lsst.pex.config.Config`-type as a value.
32 
33  Parameters
34  ----------
35  doc : `str`
36  A description of the configuration field.
37  dtype : `lsst.pex.config.Config`-type
38  The type of the field, which must be a subclass of
39  `lsst.pex.config.Config`.
40  default : `lsst.pex.config.Config`, optional
41  If default is `None`, the field will default to a default-constructed
42  instance of ``dtype``. Additionally, to allow for fewer deep-copies,
43  assigning an instance of ``ConfigField`` to ``dtype`` itself, is
44  considered equivalent to assigning a default-constructed sub-config.
45  This means that the argument default can be ``dtype``, as well as an
46  instance of ``dtype``.
47  check : callable, optional
48  A callback function that validates the field's value, returning `True`
49  if the value is valid, and `False` otherwise.
50  deprecated : None or `str`, optional
51  A description of why this Field is deprecated, including removal date.
52  If not None, the string is appended to the docstring for this Field.
53 
54  See also
55  --------
56  ChoiceField
57  ConfigChoiceField
58  ConfigDictField
59  ConfigurableField
60  DictField
61  Field
62  ListField
63  RangeField
64  RegistryField
65 
66  Notes
67  -----
68  The behavior of this type of field is much like that of the base `Field`
69  type.
70 
71  Assigning to ``ConfigField`` will update all of the fields in the
72  configuration.
73  """
74 
75  def __init__(self, doc, dtype, default=None, check=None, deprecated=None):
76  if not issubclass(dtype, Config):
77  raise ValueError("dtype=%s is not a subclass of Config" %
78  _typeStr(dtype))
79  if default is None:
80  default = dtype
81  source = getStackFrame()
82  self._setup(doc=doc, dtype=dtype, default=default, check=check,
83  optional=False, source=source, deprecated=deprecated)
84 
85  def __get__(self, instance, owner=None):
86  if instance is None or not isinstance(instance, Config):
87  return self
88  else:
89  value = instance._storage.get(self.name, None)
90  if value is None:
91  at = getCallStack()
92  at.insert(0, self.source)
93  self.__set__(instance, self.default, at=at, label="default")
94  return value
95 
96  def __set__(self, instance, value, at=None, label="assignment"):
97  if instance._frozen:
98  raise FieldValidationError(self, instance,
99  "Cannot modify a frozen Config")
100  name = _joinNamePath(prefix=instance._name, name=self.name)
101 
102  if value != self.dtype and type(value) != self.dtype:
103  msg = "Value %s is of incorrect type %s. Expected %s" % \
104  (value, _typeStr(value), _typeStr(self.dtype))
105  raise FieldValidationError(self, instance, msg)
106 
107  if at is None:
108  at = getCallStack()
109 
110  oldValue = instance._storage.get(self.name, None)
111  if oldValue is None:
112  if value == self.dtype:
113  instance._storage[self.name] = self.dtype(__name=name, __at=at, __label=label)
114  else:
115  instance._storage[self.name] = self.dtype(__name=name, __at=at,
116  __label=label, **value._storage)
117  else:
118  if value == self.dtype:
119  value = value()
120  oldValue.update(__at=at, __label=label, **value._storage)
121  history = instance._history.setdefault(self.name, [])
122  history.append(("config value set", at, label))
123 
124  def rename(self, instance):
125  """Rename the field in a `~lsst.pex.config.Config` (for internal use
126  only).
127 
128  Parameters
129  ----------
130  instance : `lsst.pex.config.Config`
131  The config instance that contains this field.
132 
133  Notes
134  -----
135  This method is invoked by the `lsst.pex.config.Config` object that
136  contains this field and should not be called directly.
137 
138  Renaming is only relevant for `~lsst.pex.config.Field` instances that
139  hold subconfigs. `~lsst.pex.config.Fields` that hold subconfigs should
140  rename each subconfig with the full field name as generated by
141  `lsst.pex.config.config._joinNamePath`.
142  """
143  value = self.__get__(instance)
144  value._rename(_joinNamePath(instance._name, self.name))
145 
146  def _collectImports(self, instance, imports):
147  value = self.__get__(instance)
148  value._collectImports()
149  imports |= value._imports
150 
151  def save(self, outfile, instance):
152  """Save this field to a file (for internal use only).
153 
154  Parameters
155  ----------
156  outfile : file-like object
157  A writeable field handle.
158  instance : `Config`
159  The `Config` instance that contains this field.
160 
161  Notes
162  -----
163  This method is invoked by the `~lsst.pex.config.Config` object that
164  contains this field and should not be called directly.
165 
166  The output consists of the documentation string
167  (`lsst.pex.config.Field.doc`) formatted as a Python comment. The second
168  line is formatted as an assignment: ``{fullname}={value}``.
169 
170  This output can be executed with Python.
171  """
172  value = self.__get__(instance)
173  value._save(outfile)
174 
175  def freeze(self, instance):
176  """Make this field read-only.
177 
178  Parameters
179  ----------
180  instance : `lsst.pex.config.Config`
181  The config instance that contains this field.
182 
183  Notes
184  -----
185  Freezing is only relevant for fields that hold subconfigs. Fields which
186  hold subconfigs should freeze each subconfig.
187 
188  **Subclasses should implement this method.**
189  """
190  value = self.__get__(instance)
191  value.freeze()
192 
193  def toDict(self, instance):
194  """Convert the field value so that it can be set as the value of an
195  item in a `dict` (for internal use only).
196 
197  Parameters
198  ----------
199  instance : `Config`
200  The `Config` that contains this field.
201 
202  Returns
203  -------
204  value : object
205  The field's value. See *Notes*.
206 
207  Notes
208  -----
209  This method invoked by the owning `~lsst.pex.config.Config` object and
210  should not be called directly.
211 
212  Simple values are passed through. Complex data structures must be
213  manipulated. For example, a `~lsst.pex.config.Field` holding a
214  subconfig should, instead of the subconfig object, return a `dict`
215  where the keys are the field names in the subconfig, and the values are
216  the field values in the subconfig.
217  """
218  value = self.__get__(instance)
219  return value.toDict()
220 
221  def validate(self, instance):
222  """Validate the field (for internal use only).
223 
224  Parameters
225  ----------
226  instance : `lsst.pex.config.Config`
227  The config instance that contains this field.
228 
229  Raises
230  ------
231  lsst.pex.config.FieldValidationError
232  Raised if verification fails.
233 
234  Notes
235  -----
236  This method provides basic validation:
237 
238  - Ensures that the value is not `None` if the field is not optional.
239  - Ensures type correctness.
240  - Ensures that the user-provided ``check`` function is valid.
241 
242  Most `~lsst.pex.config.Field` subclasses should call
243  `lsst.pex.config.field.Field.validate` if they re-implement
244  `~lsst.pex.config.field.Field.validate`.
245  """
246  value = self.__get__(instance)
247  value.validate()
248 
249  if self.check is not None and not self.check(value):
250  msg = "%s is not a valid value" % str(value)
251  raise FieldValidationError(self, instance, msg)
252 
253  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
254  """Compare two fields for equality.
255 
256  Used by `ConfigField.compare`.
257 
258  Parameters
259  ----------
260  instance1 : `lsst.pex.config.Config`
261  Left-hand side config instance to compare.
262  instance2 : `lsst.pex.config.Config`
263  Right-hand side config instance to compare.
264  shortcut : `bool`
265  If `True`, this function returns as soon as an inequality if found.
266  rtol : `float`
267  Relative tolerance for floating point comparisons.
268  atol : `float`
269  Absolute tolerance for floating point comparisons.
270  output : callable
271  A callable that takes a string, used (possibly repeatedly) to report inequalities.
272 
273  Returns
274  -------
275  isEqual : bool
276  `True` if the fields are equal, `False` otherwise.
277 
278  Notes
279  -----
280  Floating point comparisons are performed by `numpy.allclose`.
281  """
282  c1 = getattr(instance1, self.name)
283  c2 = getattr(instance2, self.name)
284  name = getComparisonName(
285  _joinNamePath(instance1._name, self.name),
286  _joinNamePath(instance2._name, self.name)
287  )
288  return compareConfigs(name, c1, c2, shortcut=shortcut, rtol=rtol, atol=atol, output=output)
def getCallStack(skip=0)
Definition: callStack.py:168
def compareConfigs(name, c1, c2, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
Definition: comparison.py:105
def __get__(self, instance, owner=None)
Definition: configField.py:85
def getStackFrame(relative=0)
Definition: callStack.py:51
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:488
def __set__(self, instance, value, at=None, label='assignment')
Definition: config.py:506
def save(self, outfile, instance)
Definition: configField.py:151
def __init__(self, doc, dtype, default=None, check=None, deprecated=None)
Definition: configField.py:75
def getComparisonName(name1, name2)
Definition: comparison.py:34
def __set__(self, instance, value, at=None, label="assignment")
Definition: configField.py:96
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition: config.py:278