lsst.pex.config  13.0-4-gbcd7061+9
configurableField.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010 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 from __future__ import print_function
23 from builtins import str
24 from builtins import object
25 
26 import copy
27 
28 from .config import Config, Field, _joinNamePath, _typeStr, FieldValidationError
29 from .comparison import compareConfigs, getComparisonName
30 from .callStack import getCallStack, getStackFrame
31 
32 
33 class ConfigurableInstance(object):
34  def __initValue(self, at, label):
35  """
36  if field.default is an instance of ConfigClass, custom construct
37  _value with the correct values from default.
38  otherwise call ConfigClass constructor
39  """
40  name = _joinNamePath(self._config._name, self._field.name)
41  if type(self._field.default) == self.ConfigClass:
42  storage = self._field.default._storage
43  else:
44  storage = {}
45  value = self._ConfigClass(__name=name, __at=at, __label=label, **storage)
46  object.__setattr__(self, "_value", value)
47 
48  def __init__(self, config, field, at=None, label="default"):
49  object.__setattr__(self, "_config", config)
50  object.__setattr__(self, "_field", field)
51  object.__setattr__(self, "__doc__", config)
52  object.__setattr__(self, "_target", field.target)
53  object.__setattr__(self, "_ConfigClass", field.ConfigClass)
54  object.__setattr__(self, "_value", None)
55 
56  if at is None:
57  at = getCallStack()
58  at += [self._field.source]
59  self.__initValue(at, label)
60 
61  history = config._history.setdefault(field.name, [])
62  history.append(("Targeted and initialized from defaults", at, label))
63 
64  """
65  Read-only access to the targeted configurable
66  """
67  target = property(lambda x: x._target)
68  """
69  Read-only access to the ConfigClass
70  """
71  ConfigClass = property(lambda x: x._ConfigClass)
72 
73  """
74  Read-only access to the ConfigClass instance
75  """
76  value = property(lambda x: x._value)
77 
78  def apply(self, *args, **kw):
79  """
80  Call the confirurable.
81  With argument config=self.value along with any positional and kw args
82  """
83  return self.target(*args, config=self.value, **kw)
84 
85  """
86  Target a new configurable and ConfigClass
87  """
88  def retarget(self, target, ConfigClass=None, at=None, label="retarget"):
89  if self._config._frozen:
90  raise FieldValidationError(self._field, self._config, "Cannot modify a frozen Config")
91 
92  try:
93  ConfigClass = self._field.validateTarget(target, ConfigClass)
94  except BaseException as e:
95  raise FieldValidationError(self._field, self._config, e.message)
96 
97  if at is None:
98  at = getCallStack()
99  object.__setattr__(self, "_target", target)
100  if ConfigClass != self.ConfigClass:
101  object.__setattr__(self, "_ConfigClass", ConfigClass)
102  self.__initValue(at, label)
103 
104  history = self._config._history.setdefault(self._field.name, [])
105  msg = "retarget(target=%s, ConfigClass=%s)" % (_typeStr(target), _typeStr(ConfigClass))
106  history.append((msg, at, label))
107 
108  def __getattr__(self, name):
109  return getattr(self._value, name)
110 
111  def __setattr__(self, name, value, at=None, label="assignment"):
112  """
113  Pretend to be an isntance of ConfigClass.
114  Attributes defined by ConfigurableInstance will shadow those defined in ConfigClass
115  """
116  if self._config._frozen:
117  raise FieldValidationError(self._field, self._config, "Cannot modify a frozen Config")
118 
119  if name in self.__dict__:
120  # attribute exists in the ConfigurableInstance wrapper
121  object.__setattr__(self, name, value)
122  else:
123  if at is None:
124  at = getCallStack()
125  self._value.__setattr__(name, value, at=at, label=label)
126 
127  def __delattr__(self, name, at=None, label="delete"):
128  """
129  Pretend to be an isntance of ConfigClass.
130  Attributes defiend by ConfigurableInstance will shadow those defined in ConfigClass
131  """
132  if self._config._frozen:
133  raise FieldValidationError(self._field, self._config, "Cannot modify a frozen Config")
134 
135  try:
136  # attribute exists in the ConfigurableInstance wrapper
137  object.__delattr__(self, name)
138  except AttributeError:
139  if at is None:
140  at = getCallStack()
141  self._value.__delattr__(name, at=at, label=label)
142 
143 
145  """
146  A variant of a ConfigField which has a known configurable target
147 
148  Behaves just like a ConfigField except that it can be 'retargeted' to point
149  at a different configurable. Further you can 'apply' to construct a fully
150  configured configurable.
151 
152 
153  """
154 
155  def validateTarget(self, target, ConfigClass):
156  if ConfigClass is None:
157  try:
158  ConfigClass = target.ConfigClass
159  except:
160  raise AttributeError("'target' must define attribute 'ConfigClass'")
161  if not issubclass(ConfigClass, Config):
162  raise TypeError("'ConfigClass' is of incorrect type %s."
163  "'ConfigClass' must be a subclass of Config" % _typeStr(ConfigClass))
164  if not hasattr(target, '__call__'):
165  raise ValueError("'target' must be callable")
166  if not hasattr(target, '__module__') or not hasattr(target, '__name__'):
167  raise ValueError("'target' must be statically defined"
168  "(must have '__module__' and '__name__' attributes)")
169  return ConfigClass
170 
171  def __init__(self, doc, target, ConfigClass=None, default=None, check=None):
172  """
173  @param target is the configurable target. Must be callable, and the first
174  parameter will be the value of this field
175  @param ConfigClass is the class of Config object expected by the target.
176  If not provided by target.ConfigClass it must be provided explicitly in this argument
177  """
178  ConfigClass = self.validateTarget(target, ConfigClass)
179 
180  if default is None:
181  default = ConfigClass
182  if default != ConfigClass and type(default) != ConfigClass:
183  raise TypeError("'default' is of incorrect type %s. Expected %s" %
184  (_typeStr(default), _typeStr(ConfigClass)))
185 
186  source = getStackFrame()
187  self._setup(doc=doc, dtype=ConfigurableInstance, default=default,
188  check=check, optional=False, source=source)
189  self.target = target
190  self.ConfigClass = ConfigClass
191 
192  def __getOrMake(self, instance, at=None, label="default"):
193  value = instance._storage.get(self.name, None)
194  if value is None:
195  if at is None:
196  at = getCallStack(1)
197  value = ConfigurableInstance(instance, self, at=at, label=label)
198  instance._storage[self.name] = value
199  return value
200 
201  def __get__(self, instance, owner=None, at=None, label="default"):
202  if instance is None or not isinstance(instance, Config):
203  return self
204  else:
205  return self.__getOrMake(instance, at=at, label=label)
206 
207  def __set__(self, instance, value, at=None, label="assignment"):
208  if instance._frozen:
209  raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
210  if at is None:
211  at = getCallStack()
212  oldValue = self.__getOrMake(instance, at=at)
213 
214  if isinstance(value, ConfigurableInstance):
215  oldValue.retarget(value.target, value.ConfigClass, at, label)
216  oldValue.update(__at=at, __label=label, **value._storage)
217  elif type(value) == oldValue._ConfigClass:
218  oldValue.update(__at=at, __label=label, **value._storage)
219  elif value == oldValue.ConfigClass:
220  value = oldValue.ConfigClass()
221  oldValue.update(__at=at, __label=label, **value._storage)
222  else:
223  msg = "Value %s is of incorrect type %s. Expected %s" % \
224  (value, _typeStr(value), _typeStr(oldValue.ConfigClass))
225  raise FieldValidationError(self, instance, msg)
226 
227  def rename(self, instance):
228  fullname = _joinNamePath(instance._name, self.name)
229  value = self.__getOrMake(instance)
230  value._rename(fullname)
231 
232  def save(self, outfile, instance):
233  fullname = _joinNamePath(instance._name, self.name)
234  value = self.__getOrMake(instance)
235  target = value.target
236 
237  if target != self.target:
238  # not targeting the field-default target.
239  # save target information
240  ConfigClass = value.ConfigClass
241  for module in set([target.__module__, ConfigClass.__module__]):
242  outfile.write(u"import {}\n".format(module))
243  outfile.write(u"{}.retarget(target={}, ConfigClass={})\n\n".format(fullname,
244  _typeStr(target),
245  _typeStr(ConfigClass)))
246  # save field values
247  value._save(outfile)
248 
249  def freeze(self, instance):
250  value = self.__getOrMake(instance)
251  value.freeze()
252 
253  def toDict(self, instance):
254  value = self.__get__(instance)
255  return value.toDict()
256 
257  def validate(self, instance):
258  value = self.__get__(instance)
259  value.validate()
260 
261  if self.check is not None and not self.check(value):
262  msg = "%s is not a valid value" % str(value)
263  raise FieldValidationError(self, instance, msg)
264 
265  def __deepcopy__(self, memo):
266  """Customize deep-copying, because we always want a reference to the original typemap.
267 
268  WARNING: this must be overridden by subclasses if they change the constructor signature!
269  """
270  return type(self)(doc=self.doc, target=self.target, ConfigClass=self.ConfigClass,
271  default=copy.deepcopy(self.default))
272 
273  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
274  """Helper function for Config.compare; used to compare two fields for equality.
275 
276  @param[in] instance1 LHS Config instance to compare.
277  @param[in] instance2 RHS Config instance to compare.
278  @param[in] shortcut If True, return as soon as an inequality is found.
279  @param[in] rtol Relative tolerance for floating point comparisons.
280  @param[in] atol Absolute tolerance for floating point comparisons.
281  @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
282  to report inequalities.
283 
284  Floating point comparisons are performed by numpy.allclose; refer to that for details.
285  """
286  c1 = getattr(instance1, self.name)._value
287  c2 = getattr(instance2, self.name)._value
288  name = getComparisonName(
289  _joinNamePath(instance1._name, self.name),
290  _joinNamePath(instance2._name, self.name)
291  )
292  return compareConfigs(name, c1, c2, shortcut=shortcut, rtol=rtol, atol=atol, output=output)
def __setattr__(self, name, value, at=None, label="assignment")
def compareConfigs(name, c1, c2, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
Definition: comparison.py:67
def __init__(self, doc, target, ConfigClass=None, default=None, check=None)
def getCallStack(skip=0)
Definition: callStack.py:157
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:287
def _setup(self, doc, dtype, default, check, optional, source)
Definition: config.py:188
def getStackFrame(relative=0)
Definition: callStack.py:54
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:134
def retarget(self, target, ConfigClass=None, at=None, label="retarget")
def __init__(self, config, field, at=None, label="default")
def __getOrMake(self, instance, at=None, label="default")
def __delattr__(self, name, at=None, label="delete")
def __get__(self, instance, owner=None, at=None, label="default")
def __set__(self, instance, value, at=None, label="assignment")
def getComparisonName(name1, name2)
Definition: comparison.py:35