22 __all__ = (
"wrap",
"makeConfigClass")
28 from .config
import Config, Field
29 from .listField
import ListField, List
30 from .configField
import ConfigField
31 from .callStack
import getCallerFrame, getCallStack
45 _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 the immediate caller of pex.config.wrap. 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, myWrappedLib, 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, wrap 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: 91 Now, in Python, do this: 95 import lsst.pex.config 96 InnerConfig = lsst.pex.config.makeConfigClass(myWrappedLib.InnerControl) 97 FooConfig = lsst.pex.config.makeConfigClass(myWrappedLib.FooControl) 100 This will add fully-fledged "bar", "baz", and "zot" fields to FooConfig, set 101 FooConfig.Control = FooControl, and inject makeControl and readControl 102 methods to create a FooControl and set the FooConfig from the FooControl, 103 respectively. In addition, if FooControl has a validate() member function, 104 a custom validate() method will be added to FooConfig that uses it. And, 105 of course, all of the above will be done for InnerControl/InnerConfig too. 107 Any field that would be injected that would clash with an existing attribute of the 108 class will be silently ignored; this allows the user to customize fields and 109 inherit them from wrapped control classes. However, these names will still be 110 processed when converting between config and control classes, so they should generally 111 be present as base class fields or other instance attributes or descriptors. 113 While LSST_CONTROL_FIELD will work for any C++ type, automatic Config generation 114 only supports bool, int, std::int64_t, double, and std::string fields, along 115 with std::list and std::vectors of those types. 118 if "Control" not in ctrl.__name__:
119 raise ValueError(
"Cannot guess appropriate Config class name for %s." % ctrl)
120 name = ctrl.__name__.replace(
"Control",
"Config")
122 cls = type(name, (base,), {
"__doc__": doc})
123 if module
is not None:
126 if isinstance(module, int):
128 moduleObj = inspect.getmodule(frame)
129 moduleName = moduleObj.__name__
130 elif isinstance(module, str):
132 moduleObj = __import__(moduleName)
135 moduleName = moduleObj.__name__
136 cls.__module__ = moduleName
137 setattr(moduleObj, name, cls)
143 for attr
in dir(ctrl):
144 if attr.startswith(
"_type_"):
145 k = attr[len(
"_type_"):]
147 getModule =
"_module_" + k
149 if hasattr(ctrl, k)
and hasattr(ctrl, getDoc):
150 doc = getattr(ctrl, getDoc)()
151 ctype = getattr(ctrl, getType)()
152 if hasattr(ctrl, getModule):
153 nestedModuleName = getattr(ctrl, getModule)()
154 if nestedModuleName == moduleName:
155 nestedModuleObj = moduleObj
157 nestedModuleObj = importlib.import_module(nestedModuleName)
159 dtype = getattr(nestedModuleObj, ctype).ConfigClass
160 except AttributeError:
161 raise AttributeError(
"'%s.%s.ConfigClass' does not exist" % (moduleName, ctype))
165 dtype = _dtypeMap[ctype]
169 m = _containerRegex.match(ctype)
171 dtype = _dtypeMap.get(m.group(
"type"),
None)
174 raise TypeError(
"Could not parse field type '%s'." % ctype)
175 fields[k] = FieldCls(doc=doc, dtype=dtype, optional=
True)
180 """Construct a C++ Control object from this Config object. 182 Fields set to None will be ignored, and left at the values defined by the 183 Control object's default constructor. 186 for k, f
in fields.items():
187 value = getattr(self, k)
188 if isinstance(f, ConfigField):
189 value = value.makeControl()
190 if value
is not None:
191 if isinstance(value, List):
192 setattr(r, k, value._list)
197 def readControl(self, control, __at=None, __label="readControl", __reset=False):
198 """Read values from a C++ Control object and assign them to self's fields. 200 The __at, __label, and __reset arguments are for internal use only; they are used to 201 remove internal calls from the history. 206 for k, f
in fields.items():
207 if isinstance(f, ConfigField):
209 __at=__at, __label=__label, __reset=__reset)
211 values[k] = getattr(control, k)
214 self.update(__at=__at, __label=__label, **values)
217 """Validate the config object by constructing a control object and using 218 a C++ validate() implementation.""" 220 r = self.makeControl()
224 """Initialize the config object, using the Control objects default ctor 225 to provide defaults.""" 230 self.readControl(r, __at=[(ctrl.__name__ +
" C++", 0,
"setDefaults",
"")], __label=
"defaults",
235 ctrl.ConfigClass = cls
237 cls.makeControl = makeControl
238 cls.readControl = readControl
239 cls.setDefaults = setDefaults
240 if hasattr(ctrl,
"validate"):
241 cls.validate = validate
242 for k, field
in fields.items():
243 if not hasattr(cls, k):
244 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.
def getCallerFrame(relative=0)
def makeConfigClass(ctrl, name=None, base=Config, doc=None, module=0, cls=None)