LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
Functions | Variables
lsst.pex.config.wrap Namespace Reference

Functions

def makeConfigClass (ctrl, name=None, base=Config, doc=None, module=None, cls=None)
 
def wrap (ctrl)
 

Variables

 Control
 
 makeControl
 
 readControl
 
 setDefaults
 
 validate
 

Function Documentation

◆ makeConfigClass()

def lsst.pex.config.wrap.makeConfigClass (   ctrl,
  name = None,
  base = Config,
  doc = None,
  module = None,
  cls = None 
)
Create a `~lsst.pex.config.Config` class that matches a  C++ control
object class.

See the `wrap` decorator as a convenient interface to ``makeConfigClass``.

Parameters
----------
ctrl : class
    C++ control class to wrap.
name : `str`, optional
    Name of the new config class; defaults to the ``__name__`` of the
    control class with ``'Control'`` replaced with ``'Config'``.
base : `lsst.pex.config.Config`-type, optional
    Base class for the config class.
doc : `str`, optional
    Docstring for the config class.
module : object, `str`, `int`, or `None` optional
    Either a module object, a string specifying the name of the module, or
    an integer specifying how far back in the stack to look for the module
    to use: 0 is the immediate caller of `~lsst.pex.config.wrap`. This will
    be used to set ``__module__`` for the new config class, and the class
    will also be added to the module. Ignored if `None` or if ``cls`` is
    not `None`. Defaults to None in which case module is looked up from the
    module of ctrl.
cls : class
    An existing config class to use instead of creating a new one; name,
    base doc, and module will be ignored if this is not `None`.

Notes
-----
To use ``makeConfigClass``, write a control object in C++ using the
``LSST_CONTROL_FIELD`` macro in ``lsst/pex/config.h`` (note that it must
have sensible default constructor):

.. code-block:: cpp

   // myHeader.h

   struct InnerControl {
       LSST_CONTROL_FIELD(wim, std::string,
                          "documentation for field 'wim'");
   };

   struct FooControl {
       LSST_CONTROL_FIELD(bar, int, "documentation for field 'bar'");
       LSST_CONTROL_FIELD(baz, double, "documentation for field 'baz'");
       LSST_NESTED_CONTROL_FIELD(zot, myWrappedLib, InnerControl,
                                 "documentation for field 'zot'");

       FooControl() : bar(0), baz(0.0) {}
   };

You can use ``LSST_NESTED_CONTROL_FIELD`` to nest control objects. Wrap
those control objects as you would any other C++ class, but make sure you
include ``lsst/pex/config.h`` before including the header file where
the control object class is defined.

Next, in Python:

.. code-block:: py

   import lsst.pex.config
   import myWrappedLib

   InnerConfig = lsst.pex.config.makeConfigClass(myWrappedLib.InnerControl)
   FooConfig = lsst.pex.config.makeConfigClass(myWrappedLib.FooControl)

This does the following things:

- Adds ``bar``, ``baz``, and ``zot`` fields to ``FooConfig``.
- Set ``FooConfig.Control`` to ``FooControl``.
- Adds ``makeControl`` and ``readControl`` methods to create a
  ``FooControl`` and set the ``FooConfig`` from the ``FooControl``,
  respectively.
- If ``FooControl`` has a ``validate()`` member function,
  a custom ``validate()`` method will be added to ``FooConfig`` that uses
  it.

All of the above are done for ``InnerConfig`` as well.

Any field that would be injected that would clash with an existing
attribute of the class is be silently ignored. This allows you to
customize fields and inherit them from wrapped control classes. However,
these names are still be processed when converting between config and
control classes, so they should generally be present as base class fields
or other instance attributes or descriptors.

While ``LSST_CONTROL_FIELD`` will work for any C++ type, automatic
`~lsst.pex.config.Config` generation only supports ``bool``, ``int``,
``std::int64_t``, ``double``, and ``std::string`` fields, along with
``std::list`` and ``std::vectors`` of those types.

See also
--------
wrap

Definition at line 56 of file wrap.py.

56 def makeConfigClass(ctrl, name=None, base=Config, doc=None, module=None, cls=None):
57  """Create a `~lsst.pex.config.Config` class that matches a C++ control
58  object class.
59 
60  See the `wrap` decorator as a convenient interface to ``makeConfigClass``.
61 
62  Parameters
63  ----------
64  ctrl : class
65  C++ control class to wrap.
66  name : `str`, optional
67  Name of the new config class; defaults to the ``__name__`` of the
68  control class with ``'Control'`` replaced with ``'Config'``.
69  base : `lsst.pex.config.Config`-type, optional
70  Base class for the config class.
71  doc : `str`, optional
72  Docstring for the config class.
73  module : object, `str`, `int`, or `None` optional
74  Either a module object, a string specifying the name of the module, or
75  an integer specifying how far back in the stack to look for the module
76  to use: 0 is the immediate caller of `~lsst.pex.config.wrap`. This will
77  be used to set ``__module__`` for the new config class, and the class
78  will also be added to the module. Ignored if `None` or if ``cls`` is
79  not `None`. Defaults to None in which case module is looked up from the
80  module of ctrl.
81  cls : class
82  An existing config class to use instead of creating a new one; name,
83  base doc, and module will be ignored if this is not `None`.
84 
85  Notes
86  -----
87  To use ``makeConfigClass``, write a control object in C++ using the
88  ``LSST_CONTROL_FIELD`` macro in ``lsst/pex/config.h`` (note that it must
89  have sensible default constructor):
90 
91  .. code-block:: cpp
92 
93  // myHeader.h
94 
95  struct InnerControl {
96  LSST_CONTROL_FIELD(wim, std::string,
97  "documentation for field 'wim'");
98  };
99 
100  struct FooControl {
101  LSST_CONTROL_FIELD(bar, int, "documentation for field 'bar'");
102  LSST_CONTROL_FIELD(baz, double, "documentation for field 'baz'");
103  LSST_NESTED_CONTROL_FIELD(zot, myWrappedLib, InnerControl,
104  "documentation for field 'zot'");
105 
106  FooControl() : bar(0), baz(0.0) {}
107  };
108 
109  You can use ``LSST_NESTED_CONTROL_FIELD`` to nest control objects. Wrap
110  those control objects as you would any other C++ class, but make sure you
111  include ``lsst/pex/config.h`` before including the header file where
112  the control object class is defined.
113 
114  Next, in Python:
115 
116  .. code-block:: py
117 
118  import lsst.pex.config
119  import myWrappedLib
120 
121  InnerConfig = lsst.pex.config.makeConfigClass(myWrappedLib.InnerControl)
122  FooConfig = lsst.pex.config.makeConfigClass(myWrappedLib.FooControl)
123 
124  This does the following things:
125 
126  - Adds ``bar``, ``baz``, and ``zot`` fields to ``FooConfig``.
127  - Set ``FooConfig.Control`` to ``FooControl``.
128  - Adds ``makeControl`` and ``readControl`` methods to create a
129  ``FooControl`` and set the ``FooConfig`` from the ``FooControl``,
130  respectively.
131  - If ``FooControl`` has a ``validate()`` member function,
132  a custom ``validate()`` method will be added to ``FooConfig`` that uses
133  it.
134 
135  All of the above are done for ``InnerConfig`` as well.
136 
137  Any field that would be injected that would clash with an existing
138  attribute of the class is be silently ignored. This allows you to
139  customize fields and inherit them from wrapped control classes. However,
140  these names are still be processed when converting between config and
141  control classes, so they should generally be present as base class fields
142  or other instance attributes or descriptors.
143 
144  While ``LSST_CONTROL_FIELD`` will work for any C++ type, automatic
145  `~lsst.pex.config.Config` generation only supports ``bool``, ``int``,
146  ``std::int64_t``, ``double``, and ``std::string`` fields, along with
147  ``std::list`` and ``std::vectors`` of those types.
148 
149  See also
150  --------
151  wrap
152  """
153  if name is None:
154  if "Control" not in ctrl.__name__:
155  raise ValueError("Cannot guess appropriate Config class name for %s." % ctrl)
156  name = ctrl.__name__.replace("Control", "Config")
157  if cls is None:
158  cls = type(name, (base,), {"__doc__": doc})
159  if module is not None:
160  # Not only does setting __module__ make Python pretty-printers
161  # more useful, it's also necessary if we want to pickle Config
162  # objects.
163  if isinstance(module, int):
164  frame = getCallerFrame(module)
165  moduleObj = inspect.getmodule(frame)
166  moduleName = moduleObj.__name__
167  elif isinstance(module, str):
168  moduleName = module
169  moduleObj = __import__(moduleName)
170  else:
171  moduleObj = module
172  moduleName = moduleObj.__name__
173  cls.__module__ = moduleName
174  setattr(moduleObj, name, cls)
175  else:
176  cls.__module__ = ctrl.__module__
177  moduleName = ctrl.__module__
178  else:
179  moduleName = cls.__module__
180  if doc is None:
181  doc = ctrl.__doc__
182  fields = {}
183  # loop over all class attributes, looking for the special static methods
184  # that indicate a field defined by one of the macros in pex/config.h.
185  for attr in dir(ctrl):
186  if attr.startswith("_type_"):
187  k = attr[len("_type_"):]
188  getDoc = "_doc_" + k
189  getModule = "_module_" + k
190  getType = attr
191  if hasattr(ctrl, k) and hasattr(ctrl, getDoc):
192  doc = getattr(ctrl, getDoc)()
193  ctype = getattr(ctrl, getType)()
194  if hasattr(ctrl, getModule): # if this is present, it's a nested control object
195  nestedModuleName = getattr(ctrl, getModule)()
196  if nestedModuleName == moduleName:
197  nestedModuleObj = moduleObj
198  else:
199  nestedModuleObj = importlib.import_module(nestedModuleName)
200  try:
201  dtype = getattr(nestedModuleObj, ctype).ConfigClass
202  except AttributeError:
203  raise AttributeError("'%s.%s.ConfigClass' does not exist" % (moduleName, ctype))
204  fields[k] = ConfigField(doc=doc, dtype=dtype)
205  else:
206  try:
207  dtype = _dtypeMap[ctype]
208  FieldCls = Field
209  except KeyError:
210  dtype = None
211  m = _containerRegex.match(ctype)
212  if m:
213  dtype = _dtypeMap.get(m.group("type"), None)
214  FieldCls = ListField
215  if dtype is None:
216  raise TypeError("Could not parse field type '%s'." % ctype)
217  fields[k] = FieldCls(doc=doc, dtype=dtype, optional=True)
218 
219  # Define a number of methods to put in the new Config class. Note that
220  # these are "closures"; they have access to local variables defined in
221  # the makeConfigClass function (like the fields dict).
222  def makeControl(self):
223  """Construct a C++ Control object from this Config object.
224 
225  Fields set to `None` will be ignored, and left at the values defined
226  by the Control object's default constructor.
227  """
228  r = self.Control()
229  for k, f in fields.items():
230  value = getattr(self, k)
231  if isinstance(f, ConfigField):
232  value = value.makeControl()
233  if value is not None:
234  if isinstance(value, List):
235  setattr(r, k, value._list)
236  else:
237  setattr(r, k, value)
238  return r
239 
240  def readControl(self, control, __at=None, __label="readControl", __reset=False):
241  """Read values from a C++ Control object and assign them to self's
242  fields.
243 
244  Parameters
245  ----------
246  control
247  C++ Control object.
248 
249  Notes
250  -----
251  The ``__at``, ``__label``, and ``__reset`` arguments are for internal
252  use only; they are used to remove internal calls from the history.
253  """
254  if __at is None:
255  __at = getCallStack()
256  values = {}
257  for k, f in fields.items():
258  if isinstance(f, ConfigField):
259  getattr(self, k).readControl(getattr(control, k),
260  __at=__at, __label=__label, __reset=__reset)
261  else:
262  values[k] = getattr(control, k)
263  if __reset:
264  self._history = {}
265  self.update(__at=__at, __label=__label, **values)
266 
267  def validate(self):
268  """Validate the config object by constructing a control object and
269  using a C++ ``validate()`` implementation.
270  """
271  super(cls, self).validate()
272  r = self.makeControl()
273  r.validate()
274 
275  def setDefaults(self):
276  """Initialize the config object, using the Control objects default ctor
277  to provide defaults.
278  """
279  super(cls, self).setDefaults()
280  try:
281  r = self.Control()
282  # Indicate in the history that these values came from C++, even
283  # if we can't say which line
284  self.readControl(r, __at=[StackFrame(ctrl.__name__ + " C++", 0, "setDefaults", "")],
285  __label="defaults", __reset=True)
286  except Exception:
287  pass # if we can't instantiate the Control, don't set defaults
288 
289  ctrl.ConfigClass = cls
290  cls.Control = ctrl
291  cls.makeControl = makeControl
292  cls.readControl = readControl
293  cls.setDefaults = setDefaults
294  if hasattr(ctrl, "validate"):
295  cls.validate = validate
296  for k, field in fields.items():
297  if not hasattr(cls, k):
298  setattr(cls, k, field)
299  return cls
300 
301 
table::Key< int > type
Definition: Detector.cc:163
def getCallStack(skip=0)
Definition: callStack.py:175
def getCallerFrame(relative=0)
Definition: callStack.py:34
def makeConfigClass(ctrl, name=None, base=Config, doc=None, module=None, cls=None)
Definition: wrap.py:56

◆ wrap()

def lsst.pex.config.wrap.wrap (   ctrl)
Decorator that adds fields from a C++ control class to a
`lsst.pex.config.Config` class.

Parameters
----------
ctrl : object
    The C++ control class.

Notes
-----
See `makeConfigClass` for more information. This `wrap` decorator is
equivalent to calling `makeConfigClass` with the decorated class as the
``cls`` argument.

Examples
--------
Use `wrap` like this::

    @wrap(MyControlClass)
    class MyConfigClass(Config):
        pass

See also
--------
makeConfigClass

Definition at line 302 of file wrap.py.

302 def wrap(ctrl):
303  """Decorator that adds fields from a C++ control class to a
304  `lsst.pex.config.Config` class.
305 
306  Parameters
307  ----------
308  ctrl : object
309  The C++ control class.
310 
311  Notes
312  -----
313  See `makeConfigClass` for more information. This `wrap` decorator is
314  equivalent to calling `makeConfigClass` with the decorated class as the
315  ``cls`` argument.
316 
317  Examples
318  --------
319  Use `wrap` like this::
320 
321  @wrap(MyControlClass)
322  class MyConfigClass(Config):
323  pass
324 
325  See also
326  --------
327  makeConfigClass
328  """
329  def decorate(cls):
330  return makeConfigClass(ctrl, cls=cls)
331  return decorate
def wrap(ctrl)
Definition: wrap.py:302

Variable Documentation

◆ Control

lsst.pex.config.wrap.Control

Definition at line 290 of file wrap.py.

◆ makeControl

lsst.pex.config.wrap.makeControl

Definition at line 291 of file wrap.py.

◆ readControl

lsst.pex.config.wrap.readControl

Definition at line 292 of file wrap.py.

◆ setDefaults

lsst.pex.config.wrap.setDefaults

Definition at line 293 of file wrap.py.

◆ validate

lsst.pex.config.wrap.validate

Definition at line 295 of file wrap.py.