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# This file is part of pex_config. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

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

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

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

18# (at your option) any later version. 

19# 

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

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

22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

23# GNU General Public License for more details. 

24# 

25# You should have received a copy of the GNU General Public License 

26# along with this program. If not, see <http://www.gnu.org/licenses/>. 

27 

28__all__ = ("Registry", "makeRegistry", "RegistryField", "registerConfig", "registerConfigurable") 

29 

30import collections.abc 

31import copy 

32 

33from .config import Config, FieldValidationError, _typeStr 

34from .configChoiceField import ConfigInstanceDict, ConfigChoiceField 

35 

36 

37class ConfigurableWrapper: 

38 """A wrapper for configurables. 

39 

40 Used for configurables that don't contain a ``ConfigClass`` attribute, 

41 or contain one that is being overridden. 

42 """ 

43 

44 def __init__(self, target, ConfigClass): 

45 self.ConfigClass = ConfigClass 

46 self._target = target 

47 

48 def __call__(self, *args, **kwargs): 

49 return self._target(*args, **kwargs) 

50 

51 

52class Registry(collections.abc.Mapping): 

53 """A base class for global registries, which map names to configurables. 

54 

55 A registry acts like a read-only dictionary with an additional `register` 

56 method to add targets. Targets in the registry are configurables (see 

57 *Notes*). 

58 

59 Parameters 

60 ---------- 

61 configBaseType : `lsst.pex.config.Config`-type 

62 The base class for config classes in the registry. 

63 

64 Notes 

65 ----- 

66 A configurable is a callable with call signature ``(config, *args)`` 

67 Configurables typically create an algorithm or are themselves the 

68 algorithm. Often configurables are `lsst.pipe.base.Task` subclasses, but 

69 this is not required. 

70 

71 A ``Registry`` has these requirements: 

72 

73 - All configurables added to a particular registry have the same call 

74 signature. 

75 - All configurables in a registry typically share something important 

76 in common. For example, all configurables in ``psfMatchingRegistry`` 

77 return a PSF matching class that has a ``psfMatch`` method with a 

78 particular call signature. 

79 

80 Examples 

81 -------- 

82 This examples creates a configurable class ``Foo`` and adds it to a 

83 registry. First, creating the configurable: 

84 

85 >>> from lsst.pex.config import Registry, Config 

86 >>> class FooConfig(Config): 

87 ... val = Field(dtype=int, default=3, doc="parameter for Foo") 

88 ... 

89 >>> class Foo: 

90 ... ConfigClass = FooConfig 

91 ... def __init__(self, config): 

92 ... self.config = config 

93 ... def addVal(self, num): 

94 ... return self.config.val + num 

95 ... 

96 

97 Next, create a ``Registry`` instance called ``registry`` and register the 

98 ``Foo`` configurable under the ``"foo"`` key: 

99 

100 >>> registry = Registry() 

101 >>> registry.register("foo", Foo) 

102 >>> print(list(registry.keys())) 

103 ["foo"] 

104 

105 Now ``Foo`` is conveniently accessible from the registry itself. 

106 

107 Finally, use the registry to get the configurable class and create an 

108 instance of it: 

109 

110 >>> FooConfigurable = registry["foo"] 

111 >>> foo = FooConfigurable(FooConfigurable.ConfigClass()) 

112 >>> foo.addVal(5) 

113 8 

114 """ 

115 

116 def __init__(self, configBaseType=Config): 

117 if not issubclass(configBaseType, Config): 

118 raise TypeError("configBaseType=%s must be a subclass of Config" % _typeStr(configBaseType,)) 

119 self._configBaseType = configBaseType 

120 self._dict = {} 

121 

122 def register(self, name, target, ConfigClass=None): 

123 """Add a new configurable target to the registry. 

124 

125 Parameters 

126 ---------- 

127 name : `str` 

128 Name that the ``target`` is registered under. The target can 

129 be accessed later with `dict`-like patterns using ``name`` as 

130 the key. 

131 target : obj 

132 A configurable type, usually a subclass of `lsst.pipe.base.Task`. 

133 ConfigClass : `lsst.pex.config.Config`-type, optional 

134 A subclass of `lsst.pex.config.Config` used to configure the 

135 configurable. If `None` then the configurable's ``ConfigClass`` 

136 attribute is used. 

137 

138 Raises 

139 ------ 

140 RuntimeError 

141 Raised if an item with ``name`` is already in the registry. 

142 AttributeError 

143 Raised if ``ConfigClass`` is `None` and ``target`` does not have 

144 a ``ConfigClass`` attribute. 

145 

146 Notes 

147 ----- 

148 If ``ConfigClass`` is provided then the ``target`` configurable is 

149 wrapped in a new object that forwards function calls to it. Otherwise 

150 the original ``target`` is stored. 

151 """ 

152 if name in self._dict: 

153 raise RuntimeError("An item with name %r already exists" % name) 

154 if ConfigClass is None: 

155 wrapper = target 

156 else: 

157 wrapper = ConfigurableWrapper(target, ConfigClass) 

158 if not issubclass(wrapper.ConfigClass, self._configBaseType): 

159 raise TypeError("ConfigClass=%s is not a subclass of %r" % 

160 (_typeStr(wrapper.ConfigClass), _typeStr(self._configBaseType))) 

161 self._dict[name] = wrapper 

162 

163 def __getitem__(self, key): 

164 return self._dict[key] 

165 

166 def __len__(self): 

167 return len(self._dict) 

168 

169 def __iter__(self): 

170 return iter(self._dict) 

171 

172 def __contains__(self, key): 

173 return key in self._dict 

174 

175 def makeField(self, doc, default=None, optional=False, multi=False): 

176 """Create a `RegistryField` configuration field from this registry. 

177 

178 Parameters 

179 ---------- 

180 doc : `str` 

181 A description of the field. 

182 default : object, optional 

183 The default target for the field. 

184 optional : `bool`, optional 

185 When `False`, `lsst.pex.config.Config.validate` fails if the 

186 field's value is `None`. 

187 multi : `bool`, optional 

188 A flag to allow multiple selections in the `RegistryField` if 

189 `True`. 

190 

191 Returns 

192 ------- 

193 field : `lsst.pex.config.RegistryField` 

194 `~lsst.pex.config.RegistryField` Configuration field. 

195 """ 

196 return RegistryField(doc, self, default, optional, multi) 

197 

198 

199class RegistryAdaptor(collections.abc.Mapping): 

200 """Private class that makes a `Registry` behave like the thing a 

201 `~lsst.pex.config.ConfigChoiceField` expects. 

202 

203 Parameters 

204 ---------- 

205 registry : `Registry` 

206 `Registry` instance. 

207 """ 

208 

209 def __init__(self, registry): 

210 self.registry = registry 

211 

212 def __getitem__(self, k): 

213 return self.registry[k].ConfigClass 

214 

215 def __iter__(self): 

216 return iter(self.registry) 

217 

218 def __len__(self): 

219 return len(self.registry) 

220 

221 def __contains__(self, k): 

222 return k in self.registry 

223 

224 

225class RegistryInstanceDict(ConfigInstanceDict): 

226 """Dictionary of instantiated configs, used to populate a `RegistryField`. 

227 

228 Parameters 

229 ---------- 

230 config : `lsst.pex.config.Config` 

231 Configuration instance. 

232 field : `RegistryField` 

233 Configuration field. 

234 """ 

235 

236 def __init__(self, config, field): 

237 ConfigInstanceDict.__init__(self, config, field) 

238 self.registry = field.registry 

239 

240 def _getTarget(self): 

241 if self._field.multi: 

242 raise FieldValidationError(self._field, self._config, 

243 "Multi-selection field has no attribute 'target'") 

244 return self._field.typemap.registry[self._selection] 

245 

246 target = property(_getTarget) 

247 

248 def _getTargets(self): 

249 if not self._field.multi: 

250 raise FieldValidationError(self._field, self._config, 

251 "Single-selection field has no attribute 'targets'") 

252 return [self._field.typemap.registry[c] for c in self._selection] 

253 

254 targets = property(_getTargets) 

255 

256 def apply(self, *args, **kw): 

257 """Call the active target(s) with the active config as a keyword arg 

258 

259 If this is a multi-selection field, return a list obtained by calling 

260 each active target with its corresponding active config. 

261 

262 Additional arguments will be passed on to the configurable target(s) 

263 """ 

264 if self.active is None: 

265 msg = "No selection has been made. Options: %s" % \ 

266 (" ".join(list(self._field.typemap.registry.keys()))) 

267 raise FieldValidationError(self._field, self._config, msg) 

268 if self._field.multi: 

269 retvals = [] 

270 for c in self._selection: 

271 retvals.append(self._field.typemap.registry[c](*args, config=self[c], **kw)) 

272 return retvals 

273 else: 

274 return self._field.typemap.registry[self.name](*args, config=self[self.name], **kw) 

275 

276 def __setattr__(self, attr, value): 

277 if attr == "registry": 

278 object.__setattr__(self, attr, value) 

279 else: 

280 ConfigInstanceDict.__setattr__(self, attr, value) 

281 

282 

283class RegistryField(ConfigChoiceField): 

284 """A configuration field whose options are defined in a `Registry`. 

285 

286 Parameters 

287 ---------- 

288 doc : `str` 

289 A description of the field. 

290 registry : `Registry` 

291 The registry that contains this field. 

292 default : `str`, optional 

293 The default target key. 

294 optional : `bool`, optional 

295 When `False`, `lsst.pex.config.Config.validate` fails if the field's 

296 value is `None`. 

297 multi : `bool`, optional 

298 If `True`, the field allows multiple selections. The default is 

299 `False`. 

300 

301 See also 

302 -------- 

303 ChoiceField 

304 ConfigChoiceField 

305 ConfigDictField 

306 ConfigField 

307 ConfigurableField 

308 DictField 

309 Field 

310 ListField 

311 RangeField 

312 """ 

313 

314 instanceDictClass = RegistryInstanceDict 

315 """Class used to hold configurable instances in the field. 

316 """ 

317 

318 def __init__(self, doc, registry, default=None, optional=False, multi=False): 

319 types = RegistryAdaptor(registry) 

320 self.registry = registry 

321 ConfigChoiceField.__init__(self, doc, types, default, optional, multi) 

322 

323 def __deepcopy__(self, memo): 

324 """Customize deep-copying, want a reference to the original registry. 

325 

326 WARNING: this must be overridden by subclasses if they change the 

327 constructor signature! 

328 """ 

329 other = type(self)(doc=self.doc, registry=self.registry, 

330 default=copy.deepcopy(self.default), 

331 optional=self.optional, multi=self.multi) 

332 other.source = self.source 

333 return other 

334 

335 

336def makeRegistry(doc, configBaseType=Config): 

337 """Create a `Registry`. 

338 

339 Parameters 

340 ---------- 

341 doc : `str` 

342 Docstring for the created `Registry` (this is set as the ``__doc__`` 

343 attribute of the `Registry` instance. 

344 configBaseType : `lsst.pex.config.Config`-type 

345 Base type of config classes in the `Registry` 

346 (`lsst.pex.config.Registry.configBaseType`). 

347 

348 Returns 

349 ------- 

350 registry : `Registry` 

351 Registry with ``__doc__`` and `~Registry.configBaseType` attributes 

352 set. 

353 """ 

354 cls = type("Registry", (Registry,), {"__doc__": doc}) 

355 return cls(configBaseType=configBaseType) 

356 

357 

358def registerConfigurable(name, registry, ConfigClass=None): 

359 """A decorator that adds a class as a configurable in a `Registry` 

360 instance. 

361 

362 Parameters 

363 ---------- 

364 name : `str` 

365 Name of the target (the decorated class) in the ``registry``. 

366 registry : `Registry` 

367 The `Registry` instance that the decorated class is added to. 

368 ConfigClass : `lsst.pex.config.Config`-type, optional 

369 Config class associated with the configurable. If `None`, the class's 

370 ``ConfigClass`` attribute is used instead. 

371 

372 See also 

373 -------- 

374 registerConfig 

375 

376 Notes 

377 ----- 

378 Internally, this decorator runs `Registry.register`. 

379 """ 

380 def decorate(cls): 

381 registry.register(name, target=cls, ConfigClass=ConfigClass) 

382 return cls 

383 return decorate 

384 

385 

386def registerConfig(name, registry, target): 

387 """Decorator that adds a class as a ``ConfigClass`` in a `Registry` and 

388 associates it with the given configurable. 

389 

390 Parameters 

391 ---------- 

392 name : `str` 

393 Name of the ``target`` in the ``registry``. 

394 registry : `Registry` 

395 The registry containing the ``target``. 

396 target : obj 

397 A configurable type, such as a subclass of `lsst.pipe.base.Task`. 

398 

399 See also 

400 -------- 

401 registerConfigurable 

402 

403 Notes 

404 ----- 

405 Internally, this decorator runs `Registry.register`. 

406 """ 

407 def decorate(cls): 

408 registry.register(name, target=target, ConfigClass=cls) 

409 return cls 

410 return decorate