LSSTApplications  18.0.0+106,18.0.0+50,19.0.0,19.0.0+1,19.0.0+10,19.0.0+11,19.0.0+13,19.0.0+17,19.0.0+2,19.0.0-1-g20d9b18+6,19.0.0-1-g425ff20,19.0.0-1-g5549ca4,19.0.0-1-g580fafe+6,19.0.0-1-g6fe20d0+1,19.0.0-1-g7011481+9,19.0.0-1-g8c57eb9+6,19.0.0-1-gb5175dc+11,19.0.0-1-gdc0e4a7+9,19.0.0-1-ge272bc4+6,19.0.0-1-ge3aa853,19.0.0-10-g448f008b,19.0.0-12-g6990b2c,19.0.0-2-g0d9f9cd+11,19.0.0-2-g3d9e4fb2+11,19.0.0-2-g5037de4,19.0.0-2-gb96a1c4+3,19.0.0-2-gd955cfd+15,19.0.0-3-g2d13df8,19.0.0-3-g6f3c7dc,19.0.0-4-g725f80e+11,19.0.0-4-ga671dab3b+1,19.0.0-4-gad373c5+3,19.0.0-5-ga2acb9c+2,19.0.0-5-gfe96e6c+2,w.2020.01
LSSTDataManagementBasePackage
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 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__ = ["ConfigField"]
29 
30 from .config import Config, Field, FieldValidationError, _joinNamePath, _typeStr
31 from .comparison import compareConfigs, getComparisonName
32 from .callStack import getCallStack, getStackFrame
33 
34 
36  """A configuration field (`~lsst.pex.config.Field` subclass) that takes a
37  `~lsst.pex.config.Config`-type as a value.
38 
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.
59 
60  See also
61  --------
62  ChoiceField
63  ConfigChoiceField
64  ConfigDictField
65  ConfigurableField
66  DictField
67  Field
68  ListField
69  RangeField
70  RegistryField
71 
72  Notes
73  -----
74  The behavior of this type of field is much like that of the base `Field`
75  type.
76 
77  Assigning to ``ConfigField`` will update all of the fields in the
78  configuration.
79  """
80 
81  def __init__(self, doc, dtype, default=None, check=None, deprecated=None):
82  if not issubclass(dtype, Config):
83  raise ValueError("dtype=%s is not a subclass of Config" %
84  _typeStr(dtype))
85  if default is None:
86  default = dtype
87  source = getStackFrame()
88  self._setup(doc=doc, dtype=dtype, default=default, check=check,
89  optional=False, source=source, deprecated=deprecated)
90 
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
101 
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)
107 
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)
112 
113  if at is None:
114  at = getCallStack()
115 
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))
129 
130  def rename(self, instance):
131  """Rename the field in a `~lsst.pex.config.Config` (for internal use
132  only).
133 
134  Parameters
135  ----------
136  instance : `lsst.pex.config.Config`
137  The config instance that contains this field.
138 
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.
143 
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))
151 
152  def _collectImports(self, instance, imports):
153  value = self.__get__(instance)
154  value._collectImports()
155  imports |= value._imports
156 
157  def save(self, outfile, instance):
158  """Save this field to a file (for internal use only).
159 
160  Parameters
161  ----------
162  outfile : file-like object
163  A writeable field handle.
164  instance : `Config`
165  The `Config` instance that contains this field.
166 
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.
171 
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}``.
175 
176  This output can be executed with Python.
177  """
178  value = self.__get__(instance)
179  value._save(outfile)
180 
181  def freeze(self, instance):
182  """Make this field read-only.
183 
184  Parameters
185  ----------
186  instance : `lsst.pex.config.Config`
187  The config instance that contains this field.
188 
189  Notes
190  -----
191  Freezing is only relevant for fields that hold subconfigs. Fields which
192  hold subconfigs should freeze each subconfig.
193 
194  **Subclasses should implement this method.**
195  """
196  value = self.__get__(instance)
197  value.freeze()
198 
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).
202 
203  Parameters
204  ----------
205  instance : `Config`
206  The `Config` that contains this field.
207 
208  Returns
209  -------
210  value : object
211  The field's value. See *Notes*.
212 
213  Notes
214  -----
215  This method invoked by the owning `~lsst.pex.config.Config` object and
216  should not be called directly.
217 
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()
226 
227  def validate(self, instance):
228  """Validate the field (for internal use only).
229 
230  Parameters
231  ----------
232  instance : `lsst.pex.config.Config`
233  The config instance that contains this field.
234 
235  Raises
236  ------
237  lsst.pex.config.FieldValidationError
238  Raised if verification fails.
239 
240  Notes
241  -----
242  This method provides basic validation:
243 
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.
247 
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()
254 
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)
258 
259  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
260  """Compare two fields for equality.
261 
262  Used by `ConfigField.compare`.
263 
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.
279 
280  Returns
281  -------
282  isEqual : bool
283  `True` if the fields are equal, `False` otherwise.
284 
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)
def getCallStack(skip=0)
Definition: callStack.py:175
def compareConfigs(name, c1, c2, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
Definition: comparison.py:111
def __get__(self, instance, owner=None)
Definition: configField.py:91
def getStackFrame(relative=0)
Definition: callStack.py:58
table::Key< int > type
Definition: Detector.cc:163
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:498
def __set__(self, instance, value, at=None, label='assignment')
Definition: config.py:516
def save(self, outfile, instance)
Definition: configField.py:157
def __init__(self, doc, dtype, default=None, check=None, deprecated=None)
Definition: configField.py:81
def getComparisonName(name1, name2)
Definition: comparison.py:40
def __set__(self, instance, value, at=None, label="assignment")
Definition: configField.py:102
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition: config.py:284