LSSTApplications  10.0-2-g4f67435,11.0.rc2+1,11.0.rc2+12,11.0.rc2+3,11.0.rc2+4,11.0.rc2+5,11.0.rc2+6,11.0.rc2+7,11.0.rc2+8
LSSTDataManagementBasePackage
Functions | Variables
lsst.pex.config.wrap Namespace Reference

Functions

def makeConfigClass
 
def wrap
 

Variables

tuple __all__ = ("wrap", "makeConfigClass")
 
dictionary _dtypeMap
 
tuple _containerRegex = re.compile(r"(std::)?(vector|list)<\s*(?P<type>[a-z0-9_:]+)\s*>")
 
 _history
 

Function Documentation

def lsst.pex.config.wrap.makeConfigClass (   ctrl,
  name = None,
  base = Config,
  doc = None,
  module = 1,
  cls = None 
)
A function that creates a Python config class that matches a  C++ control object class.

@param ctrl        C++ control class to wrap.
@param name        Name of the new config class; defaults to the __name__ of the control
                   class with 'Control' replaced with 'Config'.
@param base        Base class for the config class.
@param doc         Docstring for the config class.
@param module      Either a module object, a string specifying the name of the module, or an
                   integer specifying how far back in the stack to look for the module to use:
                   0 is pex.config.wrap, 1 is the immediate caller, etc.  This will be used to
                   set __module__ for the new config class, and the class will also be added
                   to the module.  Ignored if None or if cls is not None, but note that the default
                   is to use the callers' module.
@param cls         An existing config class to use instead of creating a new one; name, base
                   doc, and module will be ignored if this is not None.

See the 'wrap' decorator as a way to use makeConfigClass that may be more convenient.

To use makeConfigClass, in C++, write a control object, using the LSST_CONTROL_FIELD macro in
lsst/pex/config.h (note that it must have sensible default constructor):

@code
// myHeader.h

struct InnerControl {
    LSST_CONTROL_FIELD(wim, std::string, "documentation for field 'wim'");
};

struct FooControl {
    LSST_CONTROL_FIELD(bar, int, "documentation for field 'bar'");
    LSST_CONTROL_FIELD(baz, double, "documentation for field 'baz'");
    LSST_NESTED_CONTROL_FIELD(zot, mySwigLib, InnerControl, "documentation for field 'zot'");

    FooControl() : bar(0), baz(0.0) {}
};
@endcode


You can use LSST_NESTED_CONTROL_FIELD to nest control objects.  Now, Swig those control objects as
you would any other C++ class, but make sure you include lsst/pex/config.h before including the header
file where the control object class is defined:
@code
// mySwigLib.i
%{
#include "myHeader.h"
%}

%include "lsst/pex/config.h"
%include "myHeader.h"
@endcode

Now, in Python, do this:

@code
import mySwigLib
import lsst.pex.config
InnerConfig = lsst.pex.config.makeConfigClass(mySwigLib.InnerControl)
FooConfig = lsst.pex.config.makeConfigClass(mySwigLib.FooControl)
@endcode

This will add fully-fledged "bar", "baz", and "zot" fields to FooConfig, set
FooConfig.Control = FooControl, and inject makeControl and readControl
methods to create a FooControl and set the FooConfig from the FooControl,
respectively.  In addition, if FooControl has a validate() member function,
a custom validate() method will be added to FooConfig that uses it.   And,
of course, all of the above will be done for InnerControl/InnerConfig too.

Any field that would be injected that would clash with an existing attribute of the
class will be silently ignored; this allows the user to customize fields and
inherit them from wrapped control classes.  However, these names will still be
processed when converting between config and control classes, so they should generally
be present as base class fields or other instance attributes or descriptors.

While LSST_CONTROL_FIELD will work for any C++ type, automatic Config generation
only supports bool, int, boost::int64_t, double, and std::string  fields, along
with std::list and std::vectors of those types.

Definition at line 48 of file wrap.py.

48 
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.
51 
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.
65 
66  See the 'wrap' decorator as a way to use makeConfigClass that may be more convenient.
67 
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):
70 
71  @code
72  // myHeader.h
73 
74  struct InnerControl {
75  LSST_CONTROL_FIELD(wim, std::string, "documentation for field 'wim'");
76  };
77 
78  struct FooControl {
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'");
82 
83  FooControl() : bar(0), baz(0.0) {}
84  };
85  @endcode
86 
87 
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:
91  @code
92  // mySwigLib.i
93  %{
94  #include "myHeader.h"
95  %}
96 
97  %include "lsst/pex/config.h"
98  %include "myHeader.h"
99  @endcode
100 
101  Now, in Python, do this:
102 
103  @code
104  import mySwigLib
105  import lsst.pex.config
106  InnerConfig = lsst.pex.config.makeConfigClass(mySwigLib.InnerControl)
107  FooConfig = lsst.pex.config.makeConfigClass(mySwigLib.FooControl)
108  @endcode
109 
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.
116 
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.
122 
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.
126  """
127  if name is None:
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")
131  if cls is None:
132  cls = type(name, (base,), {"__doc__":doc})
133  if module is not None:
134  # Not only does setting __module__ make Python pretty-printers more useful,
135  # it's also necessary if we want to pickle Config objects.
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):
141  moduleName = module
142  moduleObj = __import__(moduleName)
143  else:
144  moduleObj = module
145  moduleName = moduleObj.__name__
146  cls.__module__ = moduleName
147  setattr(moduleObj, name, cls)
148  if doc is None:
149  doc = ctrl.__doc__
150  fields = {}
151  # loop over all class attributes, looking for the special static methods that indicate a field
152  # defined by one of the macros in pex/config.h.
153  for attr in dir(ctrl):
154  if attr.startswith("_type_"):
155  k = attr[len("_type_"):]
156  getDoc = "_doc_" + k
157  getModule = "_module_" + k
158  getType = attr
159  if hasattr(ctrl, k) and hasattr(ctrl, getDoc):
160  doc = getattr(ctrl, getDoc)()
161  ctype = getattr(ctrl, getType)()
162  if hasattr(ctrl, getModule): # if this is present, it's a nested control object
163  nestedModuleName = getattr(ctrl, getModule)()
164  if nestedModuleName == moduleName:
165  nestedModuleObj = moduleObj
166  else:
167  nestedModuleObj = importlib.import_module(nestedModuleName)
168  try:
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)
173  else:
174  try:
175  dtype = _dtypeMap[ctype]
176  FieldCls = Field
177  except KeyError:
178  dtype = None
179  m = _containerRegex.match(ctype)
180  if m:
181  dtype = _dtypeMap.get(m.group("type"), None)
182  FieldCls = ListField
183  if dtype is None:
184  raise TypeError("Could not parse field type '%s'." % ctype)
185  fields[k] = FieldCls(doc=doc, dtype=dtype, optional=True)
186  # Define a number of methods to put in the new Config class. Note that these are "closures";
187  # they have access to local variables defined in the makeConfigClass function (like the fields dict).
188  def makeControl(self):
189  """Construct a C++ Control object from this Config object.
190 
191  Fields set to None will be ignored, and left at the values defined by the
192  Control object's default constructor.
193  """
194  r = self.Control()
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:
200  setattr(r, k, value)
201  return r
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.
204 
205  The __at, __label, and __reset arguments are for internal use only; they are used to
206  remove internal calls from the history.
207  """
208  if __at is None: __at = traceback.extract_stack()[:-1]
209  values = {}
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)
214  else:
215  values[k] = getattr(control, k)
216  if __reset:
217  self._history = {}
218  self.update(__at=__at, __label=__label, **values)
219  def validate(self):
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()
224  r.validate()
225  def setDefaults(self):
226  """Initialize the config object, using the Control objects default ctor
227  to provide defaults."""
228  super(cls, self).setDefaults()
229  try:
230  r = self.Control()
231  # Indicate in the history that these values came from C++, even if we can't say which line
232  self.readControl(r, __at=[(ctrl.__name__ + " C++", 0, "setDefaults", "")], __label="defaults",
233  __reset=True)
234  except:
235  pass # if we can't instantiate the Control, don't set defaults
236 
237  ctrl.ConfigClass = cls
238  cls.Control = ctrl
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)
247  return cls
def lsst.pex.config.wrap.wrap (   ctrl)
A decorator that adds fields from a C++ control class to a Python config class.

Used like this:

@wrap(MyControlClass)
class MyConfigClass(Config):
    pass

See makeConfigClass for more information; this is equivalent to calling makeConfigClass
with the decorated class as the 'cls' argument.

Definition at line 248 of file wrap.py.

249 def wrap(ctrl):
250  """A decorator that adds fields from a C++ control class to a Python config class.
251 
252  Used like this:
253 
254  @wrap(MyControlClass)
255  class MyConfigClass(Config):
256  pass
257 
258  See makeConfigClass for more information; this is equivalent to calling makeConfigClass
259  with the decorated class as the 'cls' argument.
260  """
261  def decorate(cls):
262  return makeConfigClass(ctrl, cls=cls)
263  return decorate

Variable Documentation

tuple lsst.pex.config.wrap.__all__ = ("wrap", "makeConfigClass")

Definition at line 32 of file wrap.py.

tuple lsst.pex.config.wrap._containerRegex = re.compile(r"(std::)?(vector|list)<\s*(?P<type>[a-z0-9_:]+)\s*>")

Definition at line 46 of file wrap.py.

dictionary lsst.pex.config.wrap._dtypeMap
Initial value:
1 = {
2  "bool": bool,
3  "int": int,
4  "double": float,
5  "float": float,
6  "boost::int64_t": int,
7  "std::string": str
8 }

Definition at line 37 of file wrap.py.

lsst.pex.config.wrap._history

Definition at line 216 of file wrap.py.