28 __all__ = (
"wrap",
"makeConfigClass")
34 from .config
import Config, Field
35 from .listField
import ListField, List
36 from .configField
import ConfigField
37 from .callStack
import getCallerFrame, getCallStack, StackFrame
47 """Mapping from C++ types to Python type (`dict`)
49 Tassumes we can round-trip between these using the usual pybind11 converters,
50 but doesn't require they be binary equivalent under-the-hood or anything.
53 _containerRegex = re.compile(
r"(std::)?(vector|list)<\s*(?P<type>[a-z0-9_:]+)\s*>")
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
60 See the `wrap` decorator as a convenient interface to ``makeConfigClass``.
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.
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
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`.
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):
96 LSST_CONTROL_FIELD(wim, std::string,
97 "documentation for field 'wim'");
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'");
106 FooControl() : bar(0), baz(0.0) {}
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.
118 import lsst.pex.config
121 InnerConfig = lsst.pex.config.makeConfigClass(myWrappedLib.InnerControl)
122 FooConfig = lsst.pex.config.makeConfigClass(myWrappedLib.FooControl)
124 This does the following things:
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``,
131 - If ``FooControl`` has a ``validate()`` member function,
132 a custom ``validate()`` method will be added to ``FooConfig`` that uses
135 All of the above are done for ``InnerConfig`` as well.
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.
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.
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")
158 cls =
type(name, (base,), {
"__doc__": doc})
159 if module
is not None:
163 if isinstance(module, int):
165 moduleObj = inspect.getmodule(frame)
166 moduleName = moduleObj.__name__
167 elif isinstance(module, str):
169 moduleObj = __import__(moduleName)
172 moduleName = moduleObj.__name__
173 cls.__module__ = moduleName
174 setattr(moduleObj, name, cls)
176 cls.__module__ = ctrl.__module__
177 moduleName = ctrl.__module__
179 moduleName = cls.__module__
185 for attr
in dir(ctrl):
186 if attr.startswith(
"_type_"):
187 k = attr[len(
"_type_"):]
189 getModule =
"_module_" + k
191 if hasattr(ctrl, k)
and hasattr(ctrl, getDoc):
192 doc = getattr(ctrl, getDoc)()
193 ctype = getattr(ctrl, getType)()
194 if hasattr(ctrl, getModule):
195 nestedModuleName = getattr(ctrl, getModule)()
196 if nestedModuleName == moduleName:
197 nestedModuleObj = moduleObj
199 nestedModuleObj = importlib.import_module(nestedModuleName)
201 dtype = getattr(nestedModuleObj, ctype).ConfigClass
202 except AttributeError:
203 raise AttributeError(
"'%s.%s.ConfigClass' does not exist" % (moduleName, ctype))
207 dtype = _dtypeMap[ctype]
211 m = _containerRegex.match(ctype)
213 dtype = _dtypeMap.get(m.group(
"type"),
None)
216 raise TypeError(
"Could not parse field type '%s'." % ctype)
217 fields[k] = FieldCls(doc=doc, dtype=dtype, optional=
True)
223 """Construct a C++ Control object from this Config object.
225 Fields set to `None` will be ignored, and left at the values defined
226 by the Control object's default constructor.
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)
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
251 The ``__at``, ``__label``, and ``__reset`` arguments are for internal
252 use only; they are used to remove internal calls from the history.
257 for k, f
in fields.items():
258 if isinstance(f, ConfigField):
260 __at=__at, __label=__label, __reset=__reset)
262 values[k] = getattr(control, k)
265 self.update(__at=__at, __label=__label, **values)
268 """Validate the config object by constructing a control object and
269 using a C++ ``validate()`` implementation.
272 r = self.makeControl()
276 """Initialize the config object, using the Control objects default ctor
284 self.readControl(r, __at=[
StackFrame(ctrl.__name__ +
" C++", 0,
"setDefaults",
"")],
285 __label=
"defaults", __reset=
True)
289 ctrl.ConfigClass = cls
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)
303 """Decorator that adds fields from a C++ control class to a
304 `lsst.pex.config.Config` class.
309 The C++ control class.
313 See `makeConfigClass` for more information. This `wrap` decorator is
314 equivalent to calling `makeConfigClass` with the decorated class as the
319 Use `wrap` like this::
321 @wrap(MyControlClass)
322 class MyConfigClass(Config):
def getCallerFrame(relative=0)
def makeConfigClass(ctrl, name=None, base=Config, doc=None, module=None, cls=None)