22 from past.builtins
import basestring
29 from .config
import Config, Field
30 from .listField
import ListField
31 from .configField
import ConfigField
33 __all__ = (
"wrap",
"makeConfigClass")
47 _containerRegex = re.compile(
r"(std::)?(vector|list)<\s*(?P<type>[a-z0-9_:]+)\s*>")
51 """A function that creates a Python config class that matches a C++ control object class.
53 @param ctrl C++ control class to wrap.
54 @param name Name of the new config class; defaults to the __name__ of the control
55 class with 'Control' replaced with 'Config'.
56 @param base Base class for the config class.
57 @param doc Docstring for the config class.
58 @param module Either a module object, a string specifying the name of the module, or an
59 integer specifying how far back in the stack to look for the module to use:
60 0 is pex.config.wrap, 1 is the immediate caller, etc. This will be used to
61 set __module__ for the new config class, and the class will also be added
62 to the module. Ignored if None or if cls is not None, but note that the default
63 is to use the callers' module.
64 @param cls An existing config class to use instead of creating a new one; name, base
65 doc, and module will be ignored if this is not None.
67 See the 'wrap' decorator as a way to use makeConfigClass that may be more convenient.
69 To use makeConfigClass, in C++, write a control object, using the LSST_CONTROL_FIELD macro in
70 lsst/pex/config.h (note that it must have sensible default constructor):
76 LSST_CONTROL_FIELD(wim, std::string, "documentation for field 'wim'");
80 LSST_CONTROL_FIELD(bar, int, "documentation for field 'bar'");
81 LSST_CONTROL_FIELD(baz, double, "documentation for field 'baz'");
82 LSST_NESTED_CONTROL_FIELD(zot, mySwigLib, InnerControl, "documentation for field 'zot'");
84 FooControl() : bar(0), baz(0.0) {}
89 You can use LSST_NESTED_CONTROL_FIELD to nest control objects. Now, Swig those control objects as
90 you would any other C++ class, but make sure you include lsst/pex/config.h before including the header
91 file where the control object class is defined:
98 %include "lsst/pex/config.h"
102 Now, in Python, do this:
106 import lsst.pex.config
107 InnerConfig = lsst.pex.config.makeConfigClass(mySwigLib.InnerControl)
108 FooConfig = lsst.pex.config.makeConfigClass(mySwigLib.FooControl)
111 This will add fully-fledged "bar", "baz", and "zot" fields to FooConfig, set
112 FooConfig.Control = FooControl, and inject makeControl and readControl
113 methods to create a FooControl and set the FooConfig from the FooControl,
114 respectively. In addition, if FooControl has a validate() member function,
115 a custom validate() method will be added to FooConfig that uses it. And,
116 of course, all of the above will be done for InnerControl/InnerConfig too.
118 Any field that would be injected that would clash with an existing attribute of the
119 class will be silently ignored; this allows the user to customize fields and
120 inherit them from wrapped control classes. However, these names will still be
121 processed when converting between config and control classes, so they should generally
122 be present as base class fields or other instance attributes or descriptors.
124 While LSST_CONTROL_FIELD will work for any C++ type, automatic Config generation
125 only supports bool, int, std::int64_t, double, and std::string fields, along
126 with std::list and std::vectors of those types.
129 if "Control" not in ctrl.__name__:
130 raise ValueError(
"Cannot guess appropriate Config class name for %s." % ctrl)
131 name = ctrl.__name__.replace(
"Control",
"Config")
133 cls = type(name, (base,), {
"__doc__": doc})
134 if module
is not None:
137 if isinstance(module, int):
138 frame = inspect.stack()[module]
139 moduleObj = inspect.getmodule(frame[0])
140 moduleName = moduleObj.__name__
141 elif isinstance(module, basestring):
143 moduleObj = __import__(moduleName)
146 moduleName = moduleObj.__name__
147 cls.__module__ = moduleName
148 setattr(moduleObj, name, cls)
154 for attr
in dir(ctrl):
155 if attr.startswith(
"_type_"):
156 k = attr[len(
"_type_"):]
158 getModule =
"_module_" + k
160 if hasattr(ctrl, k)
and hasattr(ctrl, getDoc):
161 doc = getattr(ctrl, getDoc)()
162 ctype = getattr(ctrl, getType)()
163 if hasattr(ctrl, getModule):
164 nestedModuleName = getattr(ctrl, getModule)()
165 if nestedModuleName == moduleName:
166 nestedModuleObj = moduleObj
168 nestedModuleObj = importlib.import_module(nestedModuleName)
170 dtype = getattr(nestedModuleObj, ctype).ConfigClass
171 except AttributeError:
172 raise AttributeError(
"'%s.%s.ConfigClass' does not exist" % (moduleName, ctype))
173 fields[k] = ConfigField(doc=doc, dtype=dtype)
176 dtype = _dtypeMap[ctype]
180 m = _containerRegex.match(ctype)
182 dtype = _dtypeMap.get(m.group(
"type"),
None)
185 raise TypeError(
"Could not parse field type '%s'." % ctype)
186 fields[k] = FieldCls(doc=doc, dtype=dtype, optional=
True)
190 def makeControl(self):
191 """Construct a C++ Control object from this Config object.
193 Fields set to None will be ignored, and left at the values defined by the
194 Control object's default constructor.
197 for k, f
in fields.items():
198 value = getattr(self, k)
199 if isinstance(f, ConfigField):
200 value = value.makeControl()
201 if value
is not None:
205 def readControl(self, control, __at=None, __label="readControl", __reset=False):
206 """Read values from a C++ Control object and assign them to self's fields.
208 The __at, __label, and __reset arguments are for internal use only; they are used to
209 remove internal calls from the history.
212 __at = traceback.extract_stack()[:-1]
214 for k, f
in fields.items():
215 if isinstance(f, ConfigField):
216 getattr(self, k).readControl(getattr(control, k),
217 __at=__at, __label=__label, __reset=__reset)
219 values[k] = getattr(control, k)
222 self.update(__at=__at, __label=__label, **values)
225 """Validate the config object by constructing a control object and using
226 a C++ validate() implementation."""
227 super(cls, self).validate()
228 r = self.makeControl()
231 def setDefaults(self):
232 """Initialize the config object, using the Control objects default ctor
233 to provide defaults."""
234 super(cls, self).setDefaults()
238 self.readControl(r, __at=[(ctrl.__name__ +
" C++", 0,
"setDefaults",
"")], __label=
"defaults",
243 ctrl.ConfigClass = cls
245 cls.makeControl = makeControl
246 cls.readControl = readControl
247 cls.setDefaults = setDefaults
248 if hasattr(ctrl,
"validate"):
249 cls.validate = validate
250 for k, field
in fields.items():
251 if not hasattr(cls, k):
252 setattr(cls, k, field)
257 """A decorator that adds fields from a C++ control class to a Python config class.
261 @wrap(MyControlClass)
262 class MyConfigClass(Config):
265 See makeConfigClass for more information; this is equivalent to calling makeConfigClass
266 with the decorated class as the 'cls' argument.