49 def makeConfigClass(ctrl, name=None, base=Config, doc=None, module=1, cls=None):
50 """A function that creates a Python config class that matches a C++ control object class.
52 @param ctrl C++ control class to wrap.
53 @param name Name of the new config class; defaults to the __name__ of the control
54 class with 'Control' replaced with 'Config'.
55 @param base Base class for the config class.
56 @param doc Docstring for the config class.
57 @param module Either a module object, a string specifying the name of the module, or an
58 integer specifying how far back in the stack to look for the module to use:
59 0 is pex.config.wrap, 1 is the immediate caller, etc. This will be used to
60 set __module__ for the new config class, and the class will also be added
61 to the module. Ignored if None or if cls is not None, but note that the default
62 is to use the callers' module.
63 @param cls An existing config class to use instead of creating a new one; name, base
64 doc, and module will be ignored if this is not None.
66 See the 'wrap' decorator as a way to use makeConfigClass that may be more convenient.
68 To use makeConfigClass, in C++, write a control object, using the LSST_CONTROL_FIELD macro in
69 lsst/pex/config.h (note that it must have sensible default constructor):
75 LSST_CONTROL_FIELD(wim, std::string, "documentation for field 'wim'");
79 LSST_CONTROL_FIELD(bar, int, "documentation for field 'bar'");
80 LSST_CONTROL_FIELD(baz, double, "documentation for field 'baz'");
81 LSST_NESTED_CONTROL_FIELD(zot, mySwigLib, InnerControl, "documentation for field 'zot'");
83 FooControl() : bar(0), baz(0.0) {}
88 You can use LSST_NESTED_CONTROL_FIELD to nest control objects. Now, Swig those control objects as
89 you would any other C++ class, but make sure you include lsst/pex/config.h before including the header
90 file where the control object class is defined:
97 %include "lsst/pex/config.h"
101 Now, in Python, do this:
105 import lsst.pex.config
106 InnerConfig = lsst.pex.config.makeConfigClass(mySwigLib.InnerControl)
107 FooConfig = lsst.pex.config.makeConfigClass(mySwigLib.FooControl)
110 This will add fully-fledged "bar", "baz", and "zot" fields to FooConfig, set
111 FooConfig.Control = FooControl, and inject makeControl and readControl
112 methods to create a FooControl and set the FooConfig from the FooControl,
113 respectively. In addition, if FooControl has a validate() member function,
114 a custom validate() method will be added to FooConfig that uses it. And,
115 of course, all of the above will be done for InnerControl/InnerConfig too.
117 Any field that would be injected that would clash with an existing attribute of the
118 class will be silently ignored; this allows the user to customize fields and
119 inherit them from wrapped control classes. However, these names will still be
120 processed when converting between config and control classes, so they should generally
121 be present as base class fields or other instance attributes or descriptors.
123 While LSST_CONTROL_FIELD will work for any C++ type, automatic Config generation
124 only supports bool, int, boost::int64_t, double, and std::string fields, along
125 with std::list and std::vectors of those types.
128 if "Control" not in ctrl.__name__:
129 raise ValueError(
"Cannot guess appropriate Config class name for %s." % ctrl)
130 name = ctrl.__name__.replace(
"Control",
"Config")
132 cls = type(name, (base,), {
"__doc__":doc})
133 if module
is not None:
136 if isinstance(module, int):
137 frame = inspect.stack()[module]
138 moduleObj = inspect.getmodule(frame[0])
139 moduleName = moduleObj.__name__
140 elif isinstance(module, basestring):
142 moduleObj = __import__(moduleName)
145 moduleName = moduleObj.__name__
146 cls.__module__ = moduleName
147 setattr(moduleObj, name, cls)
153 for attr
in dir(ctrl):
154 if attr.startswith(
"_type_"):
155 k = attr[len(
"_type_"):]
157 getModule =
"_module_" + k
159 if hasattr(ctrl, k)
and hasattr(ctrl, getDoc):
160 doc = getattr(ctrl, getDoc)()
161 ctype = getattr(ctrl, getType)()
162 if hasattr(ctrl, getModule):
163 nestedModuleName = getattr(ctrl, getModule)()
164 if nestedModuleName == moduleName:
165 nestedModuleObj = moduleObj
167 nestedModuleObj = importlib.import_module(nestedModuleName)
169 dtype = getattr(nestedModuleObj, ctype).ConfigClass
170 except AttributeError:
171 raise AttributeError(
"'%s.%s.ConfigClass' does not exist" % (moduleName, ctype))
172 fields[k] = ConfigField(doc=doc, dtype=dtype)
175 dtype = _dtypeMap[ctype]
179 m = _containerRegex.match(ctype)
181 dtype = _dtypeMap.get(m.group(
"type"),
None)
184 raise TypeError(
"Could not parse field type '%s'." % ctype)
185 fields[k] = FieldCls(doc=doc, dtype=dtype, optional=
True)
188 def makeControl(self):
189 """Construct a C++ Control object from this Config object.
191 Fields set to None will be ignored, and left at the values defined by the
192 Control object's default constructor.
195 for k, f
in fields.iteritems():
196 value = getattr(self, k)
197 if isinstance(f, ConfigField):
198 value = value.makeControl()
199 if value
is not None:
202 def readControl(self, control, __at=None, __label="readControl", __reset=False):
203 """Read values from a C++ Control object and assign them to self's fields.
205 The __at, __label, and __reset arguments are for internal use only; they are used to
206 remove internal calls from the history.
208 if __at
is None: __at = traceback.extract_stack()[:-1]
210 for k, f
in fields.iteritems():
211 if isinstance(f, ConfigField):
212 getattr(self, k).readControl(getattr(control, k),
213 __at=__at, __label=__label, __reset=__reset)
215 values[k] = getattr(control, k)
218 self.update(__at=__at, __label=__label, **values)
220 """Validate the config object by constructing a control object and using
221 a C++ validate() implementation."""
222 super(cls, self).validate()
223 r = self.makeControl()
225 def setDefaults(self):
226 """Initialize the config object, using the Control objects default ctor
227 to provide defaults."""
228 super(cls, self).setDefaults()
232 self.readControl(r, __at=[(ctrl.__name__ +
" C++", 0,
"setDefaults",
"")], __label=
"defaults",
237 ctrl.ConfigClass = cls
239 cls.makeControl = makeControl
240 cls.readControl = readControl
241 cls.setDefaults = setDefaults
242 if hasattr(ctrl,
"validate"):
243 cls.validate = validate
244 for k, field
in fields.iteritems():
245 if not hasattr(cls, k):
246 setattr(cls, k, field)