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