28 from .config
import Config, Field
29 from .listField
import ListField
30 from .configField
import ConfigField
32 __all__ = (
"wrap",
"makeConfigClass")
42 "boost::int64_t": int,
46 _containerRegex = re.compile(
r"(std::)?(vector|list)<\s*(?P<type>[a-z0-9_:]+)\s*>")
49 """A function that creates a Python config class that matches a C++ control object class.
51 @param ctrl C++ control class to wrap.
52 @param name Name of the new config class; defaults to the __name__ of the control
53 class with 'Control' replaced with 'Config'.
54 @param base Base class for the config class.
55 @param doc Docstring for the config class.
56 @param module Either a module object, a string specifying the name of the module, or an
57 integer specifying how far back in the stack to look for the module to use:
58 0 is pex.config.wrap, 1 is the immediate caller, etc. This will be used to
59 set __module__ for the new config class, and the class will also be added
60 to the module. Ignored if None or if cls is not None, but note that the default
61 is to use the callers' module.
62 @param cls An existing config class to use instead of creating a new one; name, base
63 doc, and module will be ignored if this is not None.
65 See the 'wrap' decorator as a way to use makeConfigClass that may be more convenient.
67 To use makeConfigClass, in C++, write a control object, using the LSST_CONTROL_FIELD macro in
68 lsst/pex/config.h (note that it must have sensible default constructor):
74 LSST_CONTROL_FIELD(wim, std::string, "documentation for field 'wim'");
78 LSST_CONTROL_FIELD(bar, int, "documentation for field 'bar'");
79 LSST_CONTROL_FIELD(baz, double, "documentation for field 'baz'");
80 LSST_NESTED_CONTROL_FIELD(zot, mySwigLib, InnerControl, "documentation for field 'zot'");
82 FooControl() : bar(0), baz(0.0) {}
87 You can use LSST_NESTED_CONTROL_FIELD to nest control objects. Now, Swig those control objects as
88 you would any other C++ class, but make sure you include lsst/pex/config.h before including the header
89 file where the control object class is defined:
96 %include "lsst/pex/config.h"
100 Now, in Python, do this:
104 import lsst.pex.config
105 InnerConfig = lsst.pex.config.makeConfigClass(mySwigLib.InnerControl)
106 FooConfig = lsst.pex.config.makeConfigClass(mySwigLib.FooControl)
109 This will add fully-fledged "bar", "baz", and "zot" fields to FooConfig, set
110 FooConfig.Control = FooControl, and inject makeControl and readControl
111 methods to create a FooControl and set the FooConfig from the FooControl,
112 respectively. In addition, if FooControl has a validate() member function,
113 a custom validate() method will be added to FooConfig that uses it. And,
114 of course, all of the above will be done for InnerControl/InnerConfig too.
116 Any field that would be injected that would clash with an existing attribute of the
117 class will be silently ignored; this allows the user to customize fields and
118 inherit them from wrapped control classes. However, these names will still be
119 processed when converting between config and control classes, so they should generally
120 be present as base class fields or other instance attributes or descriptors.
122 While LSST_CONTROL_FIELD will work for any C++ type, automatic Config generation
123 only supports bool, int, boost::int64_t, double, and std::string fields, along
124 with std::list and std::vectors of those types.
127 if "Control" not in ctrl.__name__:
128 raise ValueError(
"Cannot guess appropriate Config class name for %s." % ctrl)
129 name = ctrl.__name__.replace(
"Control",
"Config")
131 cls = type(name, (base,), {
"__doc__":doc})
132 if module
is not None:
135 if isinstance(module, int):
136 frame = inspect.stack()[module]
137 moduleObj = inspect.getmodule(frame[0])
138 moduleName = moduleObj.__name__
139 elif isinstance(module, basestring):
141 moduleObj = __import__(moduleName)
144 moduleName = moduleObj.__name__
145 cls.__module__ = moduleName
146 setattr(moduleObj, name, cls)
152 for attr
in dir(ctrl):
153 if attr.startswith(
"_type_"):
154 k = attr[len(
"_type_"):]
156 getModule =
"_module_" + k
158 if hasattr(ctrl, k)
and hasattr(ctrl, getDoc):
159 doc = getattr(ctrl, getDoc)()
160 ctype = getattr(ctrl, getType)()
161 if hasattr(ctrl, getModule):
162 nestedModuleName = getattr(ctrl, getModule)()
163 if nestedModuleName == moduleName:
164 nestedModuleObj = moduleObj
166 nestedModuleObj = importlib.import_module(nestedModuleName)
168 dtype = getattr(nestedModuleObj, ctype).ConfigClass
169 except AttributeError:
170 raise AttributeError(
"'%s.%s.ConfigClass' does not exist" % (moduleName, ctype))
171 fields[k] = ConfigField(doc=doc, dtype=dtype)
174 dtype = _dtypeMap[ctype]
178 m = _containerRegex.match(ctype)
180 dtype = _dtypeMap.get(m.group(
"type"),
None)
183 raise TypeError(
"Could not parse field type '%s'." % ctype)
184 fields[k] = FieldCls(doc=doc, dtype=dtype, optional=
True)
187 def makeControl(self):
188 """Construct a C++ Control object from this Config object.
190 Fields set to None will be ignored, and left at the values defined by the
191 Control object's default constructor.
194 for k, f
in fields.iteritems():
195 value = getattr(self, k)
196 if isinstance(f, ConfigField):
197 value = value.makeControl()
198 if value
is not None:
201 def readControl(self, control, __at=None, __label="readControl", __reset=False):
202 """Read values from a C++ Control object and assign them to self's fields.
204 The __at, __label, and __reset arguments are for internal use only; they are used to
205 remove internal calls from the history.
207 if __at
is None: __at = traceback.extract_stack()[:-1]
209 for k, f
in fields.iteritems():
210 if isinstance(f, ConfigField):
211 getattr(self, k).readControl(getattr(control, k),
212 __at=__at, __label=__label, __reset=__reset)
214 values[k] = getattr(control, k)
217 self.update(__at=__at, __label=__label, **values)
219 """Validate the config object by constructing a control object and using
220 a C++ validate() implementation."""
221 super(cls, self).validate()
222 r = self.makeControl()
224 def setDefaults(self):
225 """Initialize the config object, using the Control objects default ctor
226 to provide defaults."""
227 super(cls, self).setDefaults()
231 self.readControl(r, __at=[(ctrl.__name__ +
" C++", 0,
"setDefaults",
"")], __label=
"defaults",
236 ctrl.ConfigClass = cls
238 cls.makeControl = makeControl
239 cls.readControl = readControl
240 cls.setDefaults = setDefaults
241 if hasattr(ctrl,
"validate"):
242 cls.validate = validate
243 for k, field
in fields.iteritems():
244 if not hasattr(cls, k):
245 setattr(cls, k, field)
249 """A decorator that adds fields from a C++ control class to a Python config class.
253 @wrap(MyControlClass)
254 class MyConfigClass(Config):
257 See makeConfigClass for more information; this is equivalent to calling makeConfigClass
258 with the decorated class as the 'cls' argument.