Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

# 

# LSST Data Management System 

# Copyright 2008, 2009, 2010 LSST Corporation. 

# 

# This product includes software developed by the 

# LSST Project (http://www.lsst.org/). 

# 

# This program is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# This program is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the LSST License Statement and 

# the GNU General Public License along with this program. If not, 

# see <http://www.lsstcorp.org/LegalNotices/>. 

# 

from past.builtins import basestring 

 

import inspect 

import re 

import importlib 

 

from .config import Config, Field 

from .listField import ListField, List 

from .configField import ConfigField 

from .callStack import getCallerFrame, getCallStack 

 

__all__ = ("wrap", "makeConfigClass") 

 

# Mapping from C++ types to Python type: assumes we can round-trip between these using 

# the usual pybind11 converters, but doesn't require they be binary equivalent under-the-hood 

# or anything. 

_dtypeMap = { 

"bool": bool, 

"int": int, 

"double": float, 

"float": float, 

"std::int64_t": int, 

"std::string": basestring 

} 

 

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

 

 

def makeConfigClass(ctrl, name=None, base=Config, doc=None, module=0, 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 the immediate caller of pex.config.wrap. 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, myWrappedLib, InnerControl, "documentation for field 'zot'"); 

 

FooControl() : bar(0), baz(0.0) {} 

}; 

@endcode 

 

 

You can use LSST_NESTED_CONTROL_FIELD to nest control objects. Now, wrap 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: 

 

Now, in Python, do this: 

 

@code 

import myWrappedLib 

import lsst.pex.config 

InnerConfig = lsst.pex.config.makeConfigClass(myWrappedLib.InnerControl) 

FooConfig = lsst.pex.config.makeConfigClass(myWrappedLib.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, std::int64_t, double, and std::string fields, along 

with std::list and std::vectors of those types. 

""" 

119 ↛ 123line 119 didn't jump to line 123, because the condition on line 119 was never false if name is None: 

120 ↛ 121line 120 didn't jump to line 121, because the condition on line 120 was never true if "Control" not in ctrl.__name__: 

raise ValueError("Cannot guess appropriate Config class name for %s." % ctrl) 

name = ctrl.__name__.replace("Control", "Config") 

123 ↛ 140line 123 didn't jump to line 140, because the condition on line 123 was never false if cls is None: 

cls = type(name, (base,), {"__doc__": doc}) 

125 ↛ 140line 125 didn't jump to line 140, because the condition on line 125 was never false if module is not None: 

# Not only does setting __module__ make Python pretty-printers more useful, 

# it's also necessary if we want to pickle Config objects. 

128 ↛ 132line 128 didn't jump to line 132, because the condition on line 128 was never false if isinstance(module, int): 

frame = getCallerFrame(module) 

moduleObj = inspect.getmodule(frame) 

moduleName = moduleObj.__name__ 

elif isinstance(module, basestring): 

moduleName = module 

moduleObj = __import__(moduleName) 

else: 

moduleObj = module 

moduleName = moduleObj.__name__ 

cls.__module__ = moduleName 

setattr(moduleObj, name, cls) 

140 ↛ 142line 140 didn't jump to line 142, because the condition on line 140 was never false if doc is None: 

doc = ctrl.__doc__ 

fields = {} 

# loop over all class attributes, looking for the special static methods that indicate a field 

# defined by one of the macros in pex/config.h. 

for attr in dir(ctrl): 

if attr.startswith("_type_"): 

k = attr[len("_type_"):] 

getDoc = "_doc_" + k 

getModule = "_module_" + k 

getType = attr 

151 ↛ 145line 151 didn't jump to line 145, because the condition on line 151 was never false if hasattr(ctrl, k) and hasattr(ctrl, getDoc): 

doc = getattr(ctrl, getDoc)() 

ctype = getattr(ctrl, getType)() 

if hasattr(ctrl, getModule): # if this is present, it's a nested control object 

nestedModuleName = getattr(ctrl, getModule)() 

156 ↛ 159line 156 didn't jump to line 159, because the condition on line 156 was never false if nestedModuleName == moduleName: 

nestedModuleObj = moduleObj 

else: 

nestedModuleObj = importlib.import_module(nestedModuleName) 

try: 

dtype = getattr(nestedModuleObj, ctype).ConfigClass 

except AttributeError: 

raise AttributeError("'%s.%s.ConfigClass' does not exist" % (moduleName, ctype)) 

fields[k] = ConfigField(doc=doc, dtype=dtype) 

else: 

try: 

dtype = _dtypeMap[ctype] 

FieldCls = Field 

except KeyError: 

dtype = None 

m = _containerRegex.match(ctype) 

172 ↛ 175line 172 didn't jump to line 175, because the condition on line 172 was never false if m: 

dtype = _dtypeMap.get(m.group("type"), None) 

FieldCls = ListField 

175 ↛ 176line 175 didn't jump to line 176, because the condition on line 175 was never true if dtype is None: 

raise TypeError("Could not parse field type '%s'." % ctype) 

fields[k] = FieldCls(doc=doc, dtype=dtype, optional=True) 

 

# Define a number of methods to put in the new Config class. Note that these are "closures"; 

# they have access to local variables defined in the makeConfigClass function (like the fields dict). 

def makeControl(self): 

"""Construct a C++ Control object from this Config object. 

 

Fields set to None will be ignored, and left at the values defined by the 

Control object's default constructor. 

""" 

r = self.Control() 

for k, f in fields.items(): 

value = getattr(self, k) 

if isinstance(f, ConfigField): 

value = value.makeControl() 

192 ↛ 188line 192 didn't jump to line 188, because the condition on line 192 was never false if value is not None: 

if isinstance(value, List): 

setattr(r, k, value._list) 

else: 

setattr(r, k, value) 

return r 

 

def readControl(self, control, __at=None, __label="readControl", __reset=False): 

"""Read values from a C++ Control object and assign them to self's fields. 

 

The __at, __label, and __reset arguments are for internal use only; they are used to 

remove internal calls from the history. 

""" 

if __at is None: 

__at = getCallStack() 

values = {} 

for k, f in fields.items(): 

if isinstance(f, ConfigField): 

getattr(self, k).readControl(getattr(control, k), 

__at=__at, __label=__label, __reset=__reset) 

else: 

values[k] = getattr(control, k) 

if __reset: 

self._history = {} 

self.update(__at=__at, __label=__label, **values) 

 

def validate(self): 

"""Validate the config object by constructing a control object and using 

a C++ validate() implementation.""" 

super(cls, self).validate() 

r = self.makeControl() 

r.validate() 

 

def setDefaults(self): 

"""Initialize the config object, using the Control objects default ctor 

to provide defaults.""" 

super(cls, self).setDefaults() 

try: 

r = self.Control() 

# Indicate in the history that these values came from C++, even if we can't say which line 

self.readControl(r, __at=[(ctrl.__name__ + " C++", 0, "setDefaults", "")], __label="defaults", 

__reset=True) 

except Exception: 

pass # if we can't instantiate the Control, don't set defaults 

 

ctrl.ConfigClass = cls 

cls.Control = ctrl 

cls.makeControl = makeControl 

cls.readControl = readControl 

cls.setDefaults = setDefaults 

242 ↛ 243line 242 didn't jump to line 243, because the condition on line 242 was never true if hasattr(ctrl, "validate"): 

cls.validate = validate 

for k, field in fields.items(): 

245 ↛ 244line 245 didn't jump to line 244, because the condition on line 245 was never false if not hasattr(cls, k): 

setattr(cls, k, field) 

return cls 

 

 

def 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. 

""" 

def decorate(cls): 

return makeConfigClass(ctrl, cls=cls) 

return decorate