LSSTApplications  16.0-10-g0ee56ad+5,16.0-11-ga33d1f2+5,16.0-12-g3ef5c14+3,16.0-12-g71e5ef5+18,16.0-12-gbdf3636+3,16.0-13-g118c103+3,16.0-13-g8f68b0a+3,16.0-15-gbf5c1cb+4,16.0-16-gfd17674+3,16.0-17-g7c01f5c+3,16.0-18-g0a50484+1,16.0-20-ga20f992+8,16.0-21-g0e05fd4+6,16.0-21-g15e2d33+4,16.0-22-g62d8060+4,16.0-22-g847a80f+4,16.0-25-gf00d9b8+1,16.0-28-g3990c221+4,16.0-3-gf928089+3,16.0-32-g88a4f23+5,16.0-34-gd7987ad+3,16.0-37-gc7333cb+2,16.0-4-g10fc685+2,16.0-4-g18f3627+26,16.0-4-g5f3a788+26,16.0-5-gaf5c3d7+4,16.0-5-gcc1f4bb+1,16.0-6-g3b92700+4,16.0-6-g4412fcd+3,16.0-6-g7235603+4,16.0-69-g2562ce1b+2,16.0-8-g14ebd58+4,16.0-8-g2df868b+1,16.0-8-g4cec79c+6,16.0-8-gadf6c7a+1,16.0-8-gfc7ad86,16.0-82-g59ec2a54a+1,16.0-9-g5400cdc+2,16.0-9-ge6233d7+5,master-g2880f2d8cf+3,v17.0.rc1
LSSTDataManagementBasePackage
wrap.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 __all__ = ("wrap", "makeConfigClass")
23 
24 import inspect
25 import re
26 import importlib
27 
28 from .config import Config, Field
29 from .listField import ListField, List
30 from .configField import ConfigField
31 from .callStack import getCallerFrame, getCallStack
32 
33 _dtypeMap = {
34  "bool": bool,
35  "int": int,
36  "double": float,
37  "float": float,
38  "std::int64_t": int,
39  "std::string": str
40 }
41 """Mapping from C++ types to Python type (`dict`)
42 
43 Tassumes we can round-trip between these using the usual pybind11 converters,
44 but doesn't require they be binary equivalent under-the-hood or anything.
45 """
46 
47 _containerRegex = re.compile(r"(std::)?(vector|list)<\s*(?P<type>[a-z0-9_:]+)\s*>")
48 
49 
50 def makeConfigClass(ctrl, name=None, base=Config, doc=None, module=0, cls=None):
51  """Create a `~lsst.pex.config.Config` class that matches a C++ control
52  object class.
53 
54  See the `wrap` decorator as a convenient interface to ``makeConfigClass``.
55 
56  Parameters
57  ----------
58  ctrl : class
59  C++ control class to wrap.
60  name : `str`, optional
61  Name of the new config class; defaults to the ``__name__`` of the
62  control class with ``'Control'`` replaced with ``'Config'``.
63  base : `lsst.pex.config.Config`-type, optional
64  Base class for the config class.
65  doc : `str`, optional
66  Docstring for the config class.
67  module : object, `str`, or `int`, optional
68  Either a module object, a string specifying the name of the module, or
69  an integer specifying how far back in the stack to look for the module
70  to use: 0 is the immediate caller of `~lsst.pex.config.wrap`. This will
71  be used to set ``__module__`` for the new config class, and the class
72  will also be added to the module. Ignored if `None` or if ``cls`` is
73  not `None`, but note that the default is to use the callers' module.
74  cls : class
75  An existing config class to use instead of creating a new one; name,
76  base doc, and module will be ignored if this is not `None`.
77 
78  Notes
79  -----
80  To use ``makeConfigClass``, write a control object in C++ using the
81  ``LSST_CONTROL_FIELD`` macro in ``lsst/pex/config.h`` (note that it must
82  have sensible default constructor):
83 
84  .. code-block:: cpp
85 
86  // myHeader.h
87 
88  struct InnerControl {
89  LSST_CONTROL_FIELD(wim, std::string, "documentation for field 'wim'");
90  };
91 
92  struct FooControl {
93  LSST_CONTROL_FIELD(bar, int, "documentation for field 'bar'");
94  LSST_CONTROL_FIELD(baz, double, "documentation for field 'baz'");
95  LSST_NESTED_CONTROL_FIELD(zot, myWrappedLib, InnerControl, "documentation for field 'zot'");
96 
97  FooControl() : bar(0), baz(0.0) {}
98  };
99 
100  You can use ``LSST_NESTED_CONTROL_FIELD`` to nest control objects. Wrap
101  those control objects as you would any other C++ class, but make sure you
102  include ``lsst/pex/config.h`` before including the header file where
103  the control object class is defined.
104 
105  Next, in Python:
106 
107  .. code-block:: py
108 
109  import lsst.pex.config
110  import myWrappedLib
111 
112  InnerConfig = lsst.pex.config.makeConfigClass(myWrappedLib.InnerControl)
113  FooConfig = lsst.pex.config.makeConfigClass(myWrappedLib.FooControl)
114 
115  This does the following things:
116 
117  - Adds ``bar``, ``baz``, and ``zot`` fields to ``FooConfig``.
118  - Set ``FooConfig.Control`` to ``FooControl``.
119  - Adds ``makeControl`` and ``readControl`` methods to create a
120  ``FooControl`` and set the ``FooConfig`` from the ``FooControl``,
121  respectively.
122  - If ``FooControl`` has a ``validate()`` member function,
123  a custom ``validate()`` method will be added to ``FooConfig`` that uses
124  it.
125 
126  All of the above are done for ``InnerConfig`` as well.
127 
128  Any field that would be injected that would clash with an existing
129  attribute of the class is be silently ignored. This allows you to
130  customize fields and inherit them from wrapped control classes. However,
131  these names are still be processed when converting between config and
132  control classes, so they should generally be present as base class fields
133  or other instance attributes or descriptors.
134 
135  While ``LSST_CONTROL_FIELD`` will work for any C++ type, automatic
136  `~lsst.pex.config.Config` generation only supports ``bool``, ``int``,
137  ``std::int64_t``, ``double``, and ``std::string`` fields, along with
138  ``std::list`` and ``std::vectors`` of those types.
139 
140  See also
141  --------
142  wrap
143  """
144  if name is None:
145  if "Control" not in ctrl.__name__:
146  raise ValueError("Cannot guess appropriate Config class name for %s." % ctrl)
147  name = ctrl.__name__.replace("Control", "Config")
148  if cls is None:
149  cls = type(name, (base,), {"__doc__": doc})
150  if module is not None:
151  # Not only does setting __module__ make Python pretty-printers more useful,
152  # it's also necessary if we want to pickle Config objects.
153  if isinstance(module, int):
154  frame = getCallerFrame(module)
155  moduleObj = inspect.getmodule(frame)
156  moduleName = moduleObj.__name__
157  elif isinstance(module, str):
158  moduleName = module
159  moduleObj = __import__(moduleName)
160  else:
161  moduleObj = module
162  moduleName = moduleObj.__name__
163  cls.__module__ = moduleName
164  setattr(moduleObj, name, cls)
165  if doc is None:
166  doc = ctrl.__doc__
167  fields = {}
168  # loop over all class attributes, looking for the special static methods that indicate a field
169  # defined by one of the macros in pex/config.h.
170  for attr in dir(ctrl):
171  if attr.startswith("_type_"):
172  k = attr[len("_type_"):]
173  getDoc = "_doc_" + k
174  getModule = "_module_" + k
175  getType = attr
176  if hasattr(ctrl, k) and hasattr(ctrl, getDoc):
177  doc = getattr(ctrl, getDoc)()
178  ctype = getattr(ctrl, getType)()
179  if hasattr(ctrl, getModule): # if this is present, it's a nested control object
180  nestedModuleName = getattr(ctrl, getModule)()
181  if nestedModuleName == moduleName:
182  nestedModuleObj = moduleObj
183  else:
184  nestedModuleObj = importlib.import_module(nestedModuleName)
185  try:
186  dtype = getattr(nestedModuleObj, ctype).ConfigClass
187  except AttributeError:
188  raise AttributeError("'%s.%s.ConfigClass' does not exist" % (moduleName, ctype))
189  fields[k] = ConfigField(doc=doc, dtype=dtype)
190  else:
191  try:
192  dtype = _dtypeMap[ctype]
193  FieldCls = Field
194  except KeyError:
195  dtype = None
196  m = _containerRegex.match(ctype)
197  if m:
198  dtype = _dtypeMap.get(m.group("type"), None)
199  FieldCls = ListField
200  if dtype is None:
201  raise TypeError("Could not parse field type '%s'." % ctype)
202  fields[k] = FieldCls(doc=doc, dtype=dtype, optional=True)
203 
204  # Define a number of methods to put in the new Config class. Note that these are "closures";
205  # they have access to local variables defined in the makeConfigClass function (like the fields dict).
206  def makeControl(self):
207  """Construct a C++ Control object from this Config object.
208 
209  Fields set to `None` will be ignored, and left at the values defined
210  by the Control object's default constructor.
211  """
212  r = self.Control()
213  for k, f in fields.items():
214  value = getattr(self, k)
215  if isinstance(f, ConfigField):
216  value = value.makeControl()
217  if value is not None:
218  if isinstance(value, List):
219  setattr(r, k, value._list)
220  else:
221  setattr(r, k, value)
222  return r
223 
224  def readControl(self, control, __at=None, __label="readControl", __reset=False):
225  """Read values from a C++ Control object and assign them to self's
226  fields.
227 
228  Parameters
229  ----------
230  control
231  C++ Control object.
232 
233  Notes
234  -----
235  The ``__at``, ``__label``, and ``__reset`` arguments are for internal
236  use only; they are used to remove internal calls from the history.
237  """
238  if __at is None:
239  __at = getCallStack()
240  values = {}
241  for k, f in fields.items():
242  if isinstance(f, ConfigField):
243  getattr(self, k).readControl(getattr(control, k),
244  __at=__at, __label=__label, __reset=__reset)
245  else:
246  values[k] = getattr(control, k)
247  if __reset:
248  self._history = {}
249  self.update(__at=__at, __label=__label, **values)
250 
251  def validate(self):
252  """Validate the config object by constructing a control object and
253  using a C++ ``validate()`` implementation.
254  """
255  super(cls, self).validate()
256  r = self.makeControl()
257  r.validate()
258 
259  def setDefaults(self):
260  """Initialize the config object, using the Control objects default ctor
261  to provide defaults.
262  """
263  super(cls, self).setDefaults()
264  try:
265  r = self.Control()
266  # Indicate in the history that these values came from C++, even if we can't say which line
267  self.readControl(r, __at=[(ctrl.__name__ + " C++", 0, "setDefaults", "")], __label="defaults",
268  __reset=True)
269  except Exception:
270  pass # if we can't instantiate the Control, don't set defaults
271 
272  ctrl.ConfigClass = cls
273  cls.Control = ctrl
274  cls.makeControl = makeControl
275  cls.readControl = readControl
276  cls.setDefaults = setDefaults
277  if hasattr(ctrl, "validate"):
278  cls.validate = validate
279  for k, field in fields.items():
280  if not hasattr(cls, k):
281  setattr(cls, k, field)
282  return cls
283 
284 
285 def wrap(ctrl):
286  """Decorator that adds fields from a C++ control class to a
287  `lsst.pex.config.Config` class.
288 
289  Parameters
290  ----------
291  ctrl : object
292  The C++ control class.
293 
294  Notes
295  -----
296  See `makeConfigClass` for more information. This `wrap` decorator is
297  equivalent to calling `makeConfigClass` with the decorated class as the
298  ``cls`` argument.
299 
300  Examples
301  --------
302  Use `wrap` like this::
303 
304  @wrap(MyControlClass)
305  class MyConfigClass(Config):
306  pass
307 
308  See also
309  --------
310  makeConfigClass
311  """
312  def decorate(cls):
313  return makeConfigClass(ctrl, cls=cls)
314  return decorate
def wrap(ctrl)
Definition: wrap.py:285
def getCallStack(skip=0)
Definition: callStack.py:169
table::Key< int > type
Definition: Detector.cc:164
def getCallerFrame(relative=0)
Definition: callStack.py:29
def makeConfigClass(ctrl, name=None, base=Config, doc=None, module=0, cls=None)
Definition: wrap.py:50