22 __all__ = (
"wrap",
"makeConfigClass")
28 from .config
import Config, Field
29 from .listField
import ListField, List
30 from .configField
import ConfigField
31 from .callStack
import getCallerFrame, getCallStack
41 """Mapping from C++ types to Python type (`dict`) 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. 47 _containerRegex = re.compile(
r"(std::)?(vector|list)<\s*(?P<type>[a-z0-9_:]+)\s*>")
50 def makeConfigClass(ctrl, name=None, base=Config, doc=None, module=None, cls=None):
51 """Create a `~lsst.pex.config.Config` class that matches a C++ control 54 See the `wrap` decorator as a convenient interface to ``makeConfigClass``. 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. 66 Docstring for the config class. 67 module : object, `str`, `int`, or `None` 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`. Defaults to None in which case module is looked up from the 76 An existing config class to use instead of creating a new one; name, 77 base doc, and module will be ignored if this is not `None`. 81 To use ``makeConfigClass``, write a control object in C++ using the 82 ``LSST_CONTROL_FIELD`` macro in ``lsst/pex/config.h`` (note that it must 83 have sensible default constructor): 90 LSST_CONTROL_FIELD(wim, std::string, "documentation for field 'wim'"); 94 LSST_CONTROL_FIELD(bar, int, "documentation for field 'bar'"); 95 LSST_CONTROL_FIELD(baz, double, "documentation for field 'baz'"); 96 LSST_NESTED_CONTROL_FIELD(zot, myWrappedLib, InnerControl, "documentation for field 'zot'"); 98 FooControl() : bar(0), baz(0.0) {} 101 You can use ``LSST_NESTED_CONTROL_FIELD`` to nest control objects. Wrap 102 those control objects as you would any other C++ class, but make sure you 103 include ``lsst/pex/config.h`` before including the header file where 104 the control object class is defined. 110 import lsst.pex.config 113 InnerConfig = lsst.pex.config.makeConfigClass(myWrappedLib.InnerControl) 114 FooConfig = lsst.pex.config.makeConfigClass(myWrappedLib.FooControl) 116 This does the following things: 118 - Adds ``bar``, ``baz``, and ``zot`` fields to ``FooConfig``. 119 - Set ``FooConfig.Control`` to ``FooControl``. 120 - Adds ``makeControl`` and ``readControl`` methods to create a 121 ``FooControl`` and set the ``FooConfig`` from the ``FooControl``, 123 - If ``FooControl`` has a ``validate()`` member function, 124 a custom ``validate()`` method will be added to ``FooConfig`` that uses 127 All of the above are done for ``InnerConfig`` as well. 129 Any field that would be injected that would clash with an existing 130 attribute of the class is be silently ignored. This allows you to 131 customize fields and inherit them from wrapped control classes. However, 132 these names are still be processed when converting between config and 133 control classes, so they should generally be present as base class fields 134 or other instance attributes or descriptors. 136 While ``LSST_CONTROL_FIELD`` will work for any C++ type, automatic 137 `~lsst.pex.config.Config` generation only supports ``bool``, ``int``, 138 ``std::int64_t``, ``double``, and ``std::string`` fields, along with 139 ``std::list`` and ``std::vectors`` of those types. 146 if "Control" not in ctrl.__name__:
147 raise ValueError(
"Cannot guess appropriate Config class name for %s." % ctrl)
148 name = ctrl.__name__.replace(
"Control",
"Config")
150 cls =
type(name, (base,), {
"__doc__": doc})
151 if module
is not None:
154 if isinstance(module, int):
156 moduleObj = inspect.getmodule(frame)
157 moduleName = moduleObj.__name__
158 elif isinstance(module, str):
160 moduleObj = __import__(moduleName)
163 moduleName = moduleObj.__name__
164 cls.__module__ = moduleName
165 setattr(moduleObj, name, cls)
167 cls.__module__ = ctrl.__module__
168 moduleName = ctrl.__module__
170 moduleName = cls.__module__
176 for attr
in dir(ctrl):
177 if attr.startswith(
"_type_"):
178 k = attr[len(
"_type_"):]
180 getModule =
"_module_" + k
182 if hasattr(ctrl, k)
and hasattr(ctrl, getDoc):
183 doc = getattr(ctrl, getDoc)()
184 ctype = getattr(ctrl, getType)()
185 if hasattr(ctrl, getModule):
186 nestedModuleName = getattr(ctrl, getModule)()
187 if nestedModuleName == moduleName:
188 nestedModuleObj = moduleObj
190 nestedModuleObj = importlib.import_module(nestedModuleName)
192 dtype = getattr(nestedModuleObj, ctype).ConfigClass
193 except AttributeError:
194 raise AttributeError(
"'%s.%s.ConfigClass' does not exist" % (moduleName, ctype))
198 dtype = _dtypeMap[ctype]
202 m = _containerRegex.match(ctype)
204 dtype = _dtypeMap.get(m.group(
"type"),
None)
207 raise TypeError(
"Could not parse field type '%s'." % ctype)
208 fields[k] = FieldCls(doc=doc, dtype=dtype, optional=
True)
213 """Construct a C++ Control object from this Config object. 215 Fields set to `None` will be ignored, and left at the values defined 216 by the Control object's default constructor. 219 for k, f
in fields.items():
220 value = getattr(self, k)
221 if isinstance(f, ConfigField):
222 value = value.makeControl()
223 if value
is not None:
224 if isinstance(value, List):
225 setattr(r, k, value._list)
230 def readControl(self, control, __at=None, __label="readControl", __reset=False):
231 """Read values from a C++ Control object and assign them to self's 241 The ``__at``, ``__label``, and ``__reset`` arguments are for internal 242 use only; they are used to remove internal calls from the history. 247 for k, f
in fields.items():
248 if isinstance(f, ConfigField):
250 __at=__at, __label=__label, __reset=__reset)
252 values[k] = getattr(control, k)
255 self.update(__at=__at, __label=__label, **values)
258 """Validate the config object by constructing a control object and 259 using a C++ ``validate()`` implementation. 262 r = self.makeControl()
266 """Initialize the config object, using the Control objects default ctor 273 self.readControl(r, __at=[(ctrl.__name__ +
" C++", 0,
"setDefaults",
"")], __label=
"defaults",
278 ctrl.ConfigClass = cls
280 cls.makeControl = makeControl
281 cls.readControl = readControl
282 cls.setDefaults = setDefaults
283 if hasattr(ctrl,
"validate"):
284 cls.validate = validate
285 for k, field
in fields.items():
286 if not hasattr(cls, k):
287 setattr(cls, k, field)
292 """Decorator that adds fields from a C++ control class to a 293 `lsst.pex.config.Config` class. 298 The C++ control class. 302 See `makeConfigClass` for more information. This `wrap` decorator is 303 equivalent to calling `makeConfigClass` with the decorated class as the 308 Use `wrap` like this:: 310 @wrap(MyControlClass) 311 class MyConfigClass(Config):
def makeConfigClass(ctrl, name=None, base=Config, doc=None, module=None, cls=None)
def getCallerFrame(relative=0)