22 from past.builtins
import basestring
29 from .config
import Config, Field
30 from .listField
import ListField, List
31 from .configField
import ConfigField
33 __all__ = (
"wrap",
"makeConfigClass")
44 "std::string": basestring
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, myWrappedLib, 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, wrap 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:
93 Now, in Python, do this:
97 import lsst.pex.config
98 InnerConfig = lsst.pex.config.makeConfigClass(myWrappedLib.InnerControl)
99 FooConfig = lsst.pex.config.makeConfigClass(myWrappedLib.FooControl)
102 This will add fully-fledged "bar", "baz", and "zot" fields to FooConfig, set
103 FooConfig.Control = FooControl, and inject makeControl and readControl
104 methods to create a FooControl and set the FooConfig from the FooControl,
105 respectively. In addition, if FooControl has a validate() member function,
106 a custom validate() method will be added to FooConfig that uses it. And,
107 of course, all of the above will be done for InnerControl/InnerConfig too.
109 Any field that would be injected that would clash with an existing attribute of the
110 class will be silently ignored; this allows the user to customize fields and
111 inherit them from wrapped control classes. However, these names will still be
112 processed when converting between config and control classes, so they should generally
113 be present as base class fields or other instance attributes or descriptors.
115 While LSST_CONTROL_FIELD will work for any C++ type, automatic Config generation
116 only supports bool, int, std::int64_t, double, and std::string fields, along
117 with std::list and std::vectors of those types.
120 if "Control" not in ctrl.__name__:
121 raise ValueError(
"Cannot guess appropriate Config class name for %s." % ctrl)
122 name = ctrl.__name__.replace(
"Control",
"Config")
124 cls = type(name, (base,), {
"__doc__": doc})
125 if module
is not None:
128 if isinstance(module, int):
129 frame = inspect.stack()[module]
130 moduleObj = inspect.getmodule(frame[0])
131 moduleName = moduleObj.__name__
132 elif isinstance(module, basestring):
134 moduleObj = __import__(moduleName)
137 moduleName = moduleObj.__name__
138 cls.__module__ = moduleName
139 setattr(moduleObj, name, cls)
145 for attr
in dir(ctrl):
146 if attr.startswith(
"_type_"):
147 k = attr[len(
"_type_"):]
149 getModule =
"_module_" + k
151 if hasattr(ctrl, k)
and hasattr(ctrl, getDoc):
152 doc = getattr(ctrl, getDoc)()
153 ctype = getattr(ctrl, getType)()
154 if hasattr(ctrl, getModule):
155 nestedModuleName = getattr(ctrl, getModule)()
156 if nestedModuleName == moduleName:
157 nestedModuleObj = moduleObj
159 nestedModuleObj = importlib.import_module(nestedModuleName)
161 dtype = getattr(nestedModuleObj, ctype).ConfigClass
162 except AttributeError:
163 raise AttributeError(
"'%s.%s.ConfigClass' does not exist" % (moduleName, ctype))
164 fields[k] = ConfigField(doc=doc, dtype=dtype)
167 dtype = _dtypeMap[ctype]
171 m = _containerRegex.match(ctype)
173 dtype = _dtypeMap.get(m.group(
"type"),
None)
176 raise TypeError(
"Could not parse field type '%s'." % ctype)
177 fields[k] = FieldCls(doc=doc, dtype=dtype, optional=
True)
181 def makeControl(self):
182 """Construct a C++ Control object from this Config object.
184 Fields set to None will be ignored, and left at the values defined by the
185 Control object's default constructor.
188 for k, f
in fields.items():
189 value = getattr(self, k)
190 if isinstance(f, ConfigField):
191 value = value.makeControl()
192 if value
is not None:
193 if isinstance(value, List):
194 setattr(r, k, value._list)
199 def readControl(self, control, __at=None, __label="readControl", __reset=False):
200 """Read values from a C++ Control object and assign them to self's fields.
202 The __at, __label, and __reset arguments are for internal use only; they are used to
203 remove internal calls from the history.
206 __at = traceback.extract_stack()[:-1]
208 for k, f
in fields.items():
209 if isinstance(f, ConfigField):
210 getattr(self, k).readControl(getattr(control, k),
211 __at=__at, __label=__label, __reset=__reset)
213 values[k] = getattr(control, k)
216 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()
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.items():
245 if not hasattr(cls, k):
246 setattr(cls, k, field)
251 """A decorator that adds fields from a C++ control class to a Python config class.
255 @wrap(MyControlClass)
256 class MyConfigClass(Config):
259 See makeConfigClass for more information; this is equivalent to calling makeConfigClass
260 with the decorated class as the 'cls' argument.