28__all__ = (
"wrap",
"makeConfigClass")
34from .callStack
import StackFrame, getCallerFrame, getCallStack
35from .config
import Config, Field
36from .configField
import ConfigField
37from .listField
import List, ListField
47"""Mapping from C++ types to Python type (`dict`)
49Tassumes we can round-trip between these using the usual pybind11 converters,
50but 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*>")
56def 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'``.
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
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):
97 "documentation for field 'wim'");
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.
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
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):
164 frame = getCallerFrame(module)
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.
255 __at = getCallStack()
257 for k, f
in fields.items():
258 if isinstance(f, ConfigField):
259 getattr(self, k).
readControl(getattr(control, k), __at=__at, __label=__label, __reset=__reset)
261 values[k] = getattr(control, k)
264 self.update(__at=__at, __label=__label, **values)
267 """Validate the config object by constructing a control object and
268 using a C++ ``validate()`` implementation.
271 r = self.makeControl()
275 """Initialize the config object, using the Control objects default ctor
285 __at=[
StackFrame(ctrl.__name__ +
" C++", 0,
"setDefaults",
"")],
292 ctrl.ConfigClass = cls
294 cls.makeControl = makeControl
295 cls.readControl = readControl
296 cls.setDefaults = setDefaults
297 if hasattr(ctrl,
"validate"):
298 cls.validate = validate
299 for k, field
in fields.items():
300 if not hasattr(cls, k):
301 setattr(cls, k, field)
306 """Decorator that adds fields from a C++ control class to a
312 The C++ control
class.
316 See `makeConfigClass`
for more information. This `wrap` decorator
is
317 equivalent to calling `makeConfigClass`
with the decorated
class as the
322 Use `wrap` like this::
324 @wrap(MyControlClass)
325 class MyConfigClass(
Config):
334 return makeConfigClass(ctrl, cls=cls)
#define LSST_NESTED_CONTROL_FIELD(NAME, MODULE, TYPE, DOC)
A preprocessor macro used to define fields in C++ "control object" structs, for nested control object...
#define LSST_CONTROL_FIELD(NAME, TYPE, DOC)
A preprocessor macro used to define fields in C++ "control object" structs.