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*>")
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`, 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. 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`. 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): 89 LSST_CONTROL_FIELD(wim, std::string, "documentation for field 'wim'"); 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'"); 97 FooControl() : bar(0), baz(0.0) {} 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. 109 import lsst.pex.config 112 InnerConfig = lsst.pex.config.makeConfigClass(myWrappedLib.InnerControl) 113 FooConfig = lsst.pex.config.makeConfigClass(myWrappedLib.FooControl) 115 This does the following things: 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``, 122 - If ``FooControl`` has a ``validate()`` member function, 123 a custom ``validate()`` method will be added to ``FooConfig`` that uses 126 All of the above are done for ``InnerConfig`` as well. 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. 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. 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")
149 cls = type(name, (base,), {
"__doc__": doc})
150 if module
is not None:
153 if isinstance(module, int):
155 moduleObj = inspect.getmodule(frame)
156 moduleName = moduleObj.__name__
157 elif isinstance(module, str):
159 moduleObj = __import__(moduleName)
162 moduleName = moduleObj.__name__
163 cls.__module__ = moduleName
164 setattr(moduleObj, name, cls)
170 for attr
in dir(ctrl):
171 if attr.startswith(
"_type_"):
172 k = attr[len(
"_type_"):]
174 getModule =
"_module_" + k
176 if hasattr(ctrl, k)
and hasattr(ctrl, getDoc):
177 doc = getattr(ctrl, getDoc)()
178 ctype = getattr(ctrl, getType)()
179 if hasattr(ctrl, getModule):
180 nestedModuleName = getattr(ctrl, getModule)()
181 if nestedModuleName == moduleName:
182 nestedModuleObj = moduleObj
184 nestedModuleObj = importlib.import_module(nestedModuleName)
186 dtype = getattr(nestedModuleObj, ctype).ConfigClass
187 except AttributeError:
188 raise AttributeError(
"'%s.%s.ConfigClass' does not exist" % (moduleName, ctype))
192 dtype = _dtypeMap[ctype]
196 m = _containerRegex.match(ctype)
198 dtype = _dtypeMap.get(m.group(
"type"),
None)
201 raise TypeError(
"Could not parse field type '%s'." % ctype)
202 fields[k] = FieldCls(doc=doc, dtype=dtype, optional=
True)
207 """Construct a C++ Control object from this Config object. 209 Fields set to `None` will be ignored, and left at the values defined 210 by the Control object's default constructor. 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)
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 235 The ``__at``, ``__label``, and ``__reset`` arguments are for internal 236 use only; they are used to remove internal calls from the history. 241 for k, f
in fields.items():
242 if isinstance(f, ConfigField):
244 __at=__at, __label=__label, __reset=__reset)
246 values[k] = getattr(control, k)
249 self.update(__at=__at, __label=__label, **values)
252 """Validate the config object by constructing a control object and 253 using a C++ ``validate()`` implementation. 256 r = self.makeControl()
260 """Initialize the config object, using the Control objects default ctor 267 self.readControl(r, __at=[(ctrl.__name__ +
" C++", 0,
"setDefaults",
"")], __label=
"defaults",
272 ctrl.ConfigClass = cls
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)
286 """Decorator that adds fields from a C++ control class to a 287 `lsst.pex.config.Config` class. 292 The C++ control class. 296 See `makeConfigClass` for more information. This `wrap` decorator is 297 equivalent to calling `makeConfigClass` with the decorated class as the 302 Use `wrap` like this:: 304 @wrap(MyControlClass) 305 class MyConfigClass(Config):
def getCallerFrame(relative=0)
def makeConfigClass(ctrl, name=None, base=Config, doc=None, module=0, cls=None)