Coverage for python/lsst/pex/config/config.py: 61%

Shortcuts 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

394 statements  

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__ = ("Config", "ConfigMeta", "Field", "FieldValidationError") 

29 

30import io 

31import importlib 

32import os 

33import re 

34import sys 

35import math 

36import copy 

37import tempfile 

38import shutil 

39import warnings 

40 

41# if YAML is not available that's fine and we simply don't register 

42# the yaml representer since we know it won't be used. 

43try: 

44 import yaml 

45except ImportError: 

46 yaml = None 

47 YamlLoaders = () 

48 doImport = None 

49 

50from .comparison import getComparisonName, compareScalars, compareConfigs 

51from .callStack import getStackFrame, getCallStack 

52 

53if yaml: 53 ↛ 64line 53 didn't jump to line 64, because the condition on line 53 was never false

54 YamlLoaders = (yaml.Loader, yaml.FullLoader, yaml.SafeLoader, yaml.UnsafeLoader) 

55 

56 try: 

57 # CLoader is not always available 

58 from yaml import CLoader 

59 YamlLoaders += (CLoader,) 

60 except ImportError: 

61 pass 

62 

63 

64def _joinNamePath(prefix=None, name=None, index=None): 

65 """Generate nested configuration names. 

66 """ 

67 if not prefix and not name: 67 ↛ 68line 67 didn't jump to line 68, because the condition on line 67 was never true

68 raise ValueError("Invalid name: cannot be None") 

69 elif not name: 69 ↛ 70line 69 didn't jump to line 70, because the condition on line 69 was never true

70 name = prefix 

71 elif prefix and name: 71 ↛ 74line 71 didn't jump to line 74, because the condition on line 71 was never false

72 name = prefix + "." + name 

73 

74 if index is not None: 74 ↛ 75line 74 didn't jump to line 75, because the condition on line 74 was never true

75 return "%s[%r]" % (name, index) 

76 else: 

77 return name 

78 

79 

80def _autocast(x, dtype): 

81 """Cast a value to a type, if appropriate. 

82 

83 Parameters 

84 ---------- 

85 x : object 

86 A value. 

87 dtype : tpye 

88 Data type, such as `float`, `int`, or `str`. 

89 

90 Returns 

91 ------- 

92 values : object 

93 If appropriate, the returned value is ``x`` cast to the given type 

94 ``dtype``. If the cast cannot be performed the original value of 

95 ``x`` is returned. 

96 """ 

97 if dtype == float and isinstance(x, int): 

98 return float(x) 

99 return x 

100 

101 

102def _typeStr(x): 

103 """Generate a fully-qualified type name. 

104 

105 Returns 

106 ------- 

107 `str` 

108 Fully-qualified type name. 

109 

110 Notes 

111 ----- 

112 This function is used primarily for writing config files to be executed 

113 later upon with the 'load' function. 

114 """ 

115 if hasattr(x, '__module__') and hasattr(x, '__name__'): 

116 xtype = x 

117 else: 

118 xtype = type(x) 

119 if (sys.version_info.major <= 2 and xtype.__module__ == '__builtin__') or xtype.__module__ == 'builtins': 119 ↛ 120line 119 didn't jump to line 120, because the condition on line 119 was never true

120 return xtype.__name__ 

121 else: 

122 return "%s.%s" % (xtype.__module__, xtype.__name__) 

123 

124 

125if yaml: 125 ↛ 157line 125 didn't jump to line 157, because the condition on line 125 was never false

126 def _yaml_config_representer(dumper, data): 

127 """Represent a Config object in a form suitable for YAML. 

128 

129 Stores the serialized stream as a scalar block string. 

130 """ 

131 stream = io.StringIO() 

132 data.saveToStream(stream) 

133 config_py = stream.getvalue() 

134 

135 # Strip multiple newlines from the end of the config 

136 # This simplifies the YAML to use | and not |+ 

137 config_py = config_py.rstrip() + "\n" 

138 

139 # Trailing spaces force pyyaml to use non-block form. 

140 # Remove the trailing spaces so it has no choice 

141 config_py = re.sub(r"\s+$", "\n", config_py, flags=re.MULTILINE) 

142 

143 # Store the Python as a simple scalar 

144 return dumper.represent_scalar("lsst.pex.config.Config", config_py, style="|") 

145 

146 def _yaml_config_constructor(loader, node): 

147 """Construct a config from YAML""" 

148 config_py = loader.construct_scalar(node) 

149 return Config._fromPython(config_py) 

150 

151 # Register a generic constructor for Config and all subclasses 

152 # Need to register for all the loaders we would like to use 

153 for loader in YamlLoaders: 

154 yaml.add_constructor("lsst.pex.config.Config", _yaml_config_constructor, Loader=loader) 

155 

156 

157class ConfigMeta(type): 

158 """A metaclass for `lsst.pex.config.Config`. 

159 

160 Notes 

161 ----- 

162 ``ConfigMeta`` adds a dictionary containing all `~lsst.pex.config.Field` 

163 class attributes as a class attribute called ``_fields``, and adds 

164 the name of each field as an instance variable of the field itself (so you 

165 don't have to pass the name of the field to the field constructor). 

166 """ 

167 

168 def __init__(cls, name, bases, dict_): 

169 type.__init__(cls, name, bases, dict_) 

170 cls._fields = {} 

171 cls._source = getStackFrame() 

172 

173 def getFields(classtype): 

174 fields = {} 

175 bases = list(classtype.__bases__) 

176 bases.reverse() 

177 for b in bases: 

178 fields.update(getFields(b)) 

179 

180 for k, v in classtype.__dict__.items(): 

181 if isinstance(v, Field): 

182 fields[k] = v 

183 return fields 

184 

185 fields = getFields(cls) 

186 for k, v in fields.items(): 

187 setattr(cls, k, copy.deepcopy(v)) 

188 

189 def __setattr__(cls, name, value): 

190 if isinstance(value, Field): 

191 value.name = name 

192 cls._fields[name] = value 

193 type.__setattr__(cls, name, value) 

194 

195 

196class FieldValidationError(ValueError): 

197 """Raised when a ``~lsst.pex.config.Field`` is not valid in a 

198 particular ``~lsst.pex.config.Config``. 

199 

200 Parameters 

201 ---------- 

202 field : `lsst.pex.config.Field` 

203 The field that was not valid. 

204 config : `lsst.pex.config.Config` 

205 The config containing the invalid field. 

206 msg : `str` 

207 Text describing why the field was not valid. 

208 """ 

209 

210 def __init__(self, field, config, msg): 

211 self.fieldType = type(field) 

212 """Type of the `~lsst.pex.config.Field` that incurred the error. 

213 """ 

214 

215 self.fieldName = field.name 

216 """Name of the `~lsst.pex.config.Field` instance that incurred the 

217 error (`str`). 

218 

219 See also 

220 -------- 

221 lsst.pex.config.Field.name 

222 """ 

223 

224 self.fullname = _joinNamePath(config._name, field.name) 

225 """Fully-qualified name of the `~lsst.pex.config.Field` instance 

226 (`str`). 

227 """ 

228 

229 self.history = config.history.setdefault(field.name, []) 

230 """Full history of all changes to the `~lsst.pex.config.Field` 

231 instance. 

232 """ 

233 

234 self.fieldSource = field.source 

235 """File and line number of the `~lsst.pex.config.Field` definition. 

236 """ 

237 

238 self.configSource = config._source 

239 error = "%s '%s' failed validation: %s\n"\ 

240 "For more information see the Field definition at:\n%s"\ 

241 " and the Config definition at:\n%s" % \ 

242 (self.fieldType.__name__, self.fullname, msg, 

243 self.fieldSource.format(), self.configSource.format()) 

244 super().__init__(error) 

245 

246 

247class Field: 

248 """A field in a `~lsst.pex.config.Config` that supports `int`, `float`, 

249 `complex`, `bool`, and `str` data types. 

250 

251 Parameters 

252 ---------- 

253 doc : `str` 

254 A description of the field for users. 

255 dtype : type 

256 The field's data type. ``Field`` only supports basic data types: 

257 `int`, `float`, `complex`, `bool`, and `str`. See 

258 `Field.supportedTypes`. 

259 default : object, optional 

260 The field's default value. 

261 check : callable, optional 

262 A callable that is called with the field's value. This callable should 

263 return `False` if the value is invalid. More complex inter-field 

264 validation can be written as part of the 

265 `lsst.pex.config.Config.validate` method. 

266 optional : `bool`, optional 

267 This sets whether the field is considered optional, and therefore 

268 doesn't need to be set by the user. When `False`, 

269 `lsst.pex.config.Config.validate` fails if the field's value is `None`. 

270 deprecated : None or `str`, optional 

271 A description of why this Field is deprecated, including removal date. 

272 If not None, the string is appended to the docstring for this Field. 

273 

274 Raises 

275 ------ 

276 ValueError 

277 Raised when the ``dtype`` parameter is not one of the supported types 

278 (see `Field.supportedTypes`). 

279 

280 See also 

281 -------- 

282 ChoiceField 

283 ConfigChoiceField 

284 ConfigDictField 

285 ConfigField 

286 ConfigurableField 

287 DictField 

288 ListField 

289 RangeField 

290 RegistryField 

291 

292 Notes 

293 ----- 

294 ``Field`` instances (including those of any subclass of ``Field``) are used 

295 as class attributes of `~lsst.pex.config.Config` subclasses (see the 

296 example, below). ``Field`` attributes work like the `property` attributes 

297 of classes that implement custom setters and getters. `Field` attributes 

298 belong to the class, but operate on the instance. Formally speaking, 

299 `Field` attributes are `descriptors 

300 <https://docs.python.org/3/howto/descriptor.html>`_. 

301 

302 When you access a `Field` attribute on a `Config` instance, you don't 

303 get the `Field` instance itself. Instead, you get the value of that field, 

304 which might be a simple type (`int`, `float`, `str`, `bool`) or a custom 

305 container type (like a `lsst.pex.config.List`) depending on the field's 

306 type. See the example, below. 

307 

308 Examples 

309 -------- 

310 Instances of ``Field`` should be used as class attributes of 

311 `lsst.pex.config.Config` subclasses: 

312 

313 >>> from lsst.pex.config import Config, Field 

314 >>> class Example(Config): 

315 ... myInt = Field("An integer field.", int, default=0) 

316 ... 

317 >>> print(config.myInt) 

318 0 

319 >>> config.myInt = 5 

320 >>> print(config.myInt) 

321 5 

322 """ 

323 

324 supportedTypes = set((str, bool, float, int, complex)) 

325 """Supported data types for field values (`set` of types). 

326 """ 

327 

328 def __init__(self, doc, dtype, default=None, check=None, optional=False, deprecated=None): 

329 if dtype not in self.supportedTypes: 329 ↛ 330line 329 didn't jump to line 330, because the condition on line 329 was never true

330 raise ValueError("Unsupported Field dtype %s" % _typeStr(dtype)) 

331 

332 source = getStackFrame() 

333 self._setup(doc=doc, dtype=dtype, default=default, check=check, optional=optional, source=source, 

334 deprecated=deprecated) 

335 

336 def _setup(self, doc, dtype, default, check, optional, source, deprecated): 

337 """Set attributes, usually during initialization. 

338 """ 

339 self.dtype = dtype 

340 """Data type for the field. 

341 """ 

342 

343 # append the deprecation message to the docstring. 

344 if deprecated is not None: 

345 doc = f"{doc} Deprecated: {deprecated}" 

346 self.doc = doc 

347 """A description of the field (`str`). 

348 """ 

349 

350 self.deprecated = deprecated 

351 """If not None, a description of why this field is deprecated (`str`). 

352 """ 

353 

354 self.__doc__ = f"{doc} (`{dtype.__name__}`" 

355 if optional or default is not None: 

356 self.__doc__ += f", default ``{default!r}``" 

357 self.__doc__ += ")" 

358 

359 self.default = default 

360 """Default value for this field. 

361 """ 

362 

363 self.check = check 

364 """A user-defined function that validates the value of the field. 

365 """ 

366 

367 self.optional = optional 

368 """Flag that determines if the field is required to be set (`bool`). 

369 

370 When `False`, `lsst.pex.config.Config.validate` will fail if the 

371 field's value is `None`. 

372 """ 

373 

374 self.source = source 

375 """The stack frame where this field is defined (`list` of 

376 `lsst.pex.config.callStack.StackFrame`). 

377 """ 

378 

379 def rename(self, instance): 

380 """Rename the field in a `~lsst.pex.config.Config` (for internal use 

381 only). 

382 

383 Parameters 

384 ---------- 

385 instance : `lsst.pex.config.Config` 

386 The config instance that contains this field. 

387 

388 Notes 

389 ----- 

390 This method is invoked by the `lsst.pex.config.Config` object that 

391 contains this field and should not be called directly. 

392 

393 Renaming is only relevant for `~lsst.pex.config.Field` instances that 

394 hold subconfigs. `~lsst.pex.config.Fields` that hold subconfigs should 

395 rename each subconfig with the full field name as generated by 

396 `lsst.pex.config.config._joinNamePath`. 

397 """ 

398 pass 

399 

400 def validate(self, instance): 

401 """Validate the field (for internal use only). 

402 

403 Parameters 

404 ---------- 

405 instance : `lsst.pex.config.Config` 

406 The config instance that contains this field. 

407 

408 Raises 

409 ------ 

410 lsst.pex.config.FieldValidationError 

411 Raised if verification fails. 

412 

413 Notes 

414 ----- 

415 This method provides basic validation: 

416 

417 - Ensures that the value is not `None` if the field is not optional. 

418 - Ensures type correctness. 

419 - Ensures that the user-provided ``check`` function is valid. 

420 

421 Most `~lsst.pex.config.Field` subclasses should call 

422 `lsst.pex.config.field.Field.validate` if they re-implement 

423 `~lsst.pex.config.field.Field.validate`. 

424 """ 

425 value = self.__get__(instance) 

426 if not self.optional and value is None: 

427 raise FieldValidationError(self, instance, "Required value cannot be None") 

428 

429 def freeze(self, instance): 

430 """Make this field read-only (for internal use only). 

431 

432 Parameters 

433 ---------- 

434 instance : `lsst.pex.config.Config` 

435 The config instance that contains this field. 

436 

437 Notes 

438 ----- 

439 Freezing is only relevant for fields that hold subconfigs. Fields which 

440 hold subconfigs should freeze each subconfig. 

441 

442 **Subclasses should implement this method.** 

443 """ 

444 pass 

445 

446 def _validateValue(self, value): 

447 """Validate a value. 

448 

449 Parameters 

450 ---------- 

451 value : object 

452 The value being validated. 

453 

454 Raises 

455 ------ 

456 TypeError 

457 Raised if the value's type is incompatible with the field's 

458 ``dtype``. 

459 ValueError 

460 Raised if the value is rejected by the ``check`` method. 

461 """ 

462 if value is None: 462 ↛ 463line 462 didn't jump to line 463, because the condition on line 462 was never true

463 return 

464 

465 if not isinstance(value, self.dtype): 465 ↛ 466line 465 didn't jump to line 466, because the condition on line 465 was never true

466 msg = "Value %s is of incorrect type %s. Expected type %s" % \ 

467 (value, _typeStr(value), _typeStr(self.dtype)) 

468 raise TypeError(msg) 

469 if self.check is not None and not self.check(value): 469 ↛ 470line 469 didn't jump to line 470, because the condition on line 469 was never true

470 msg = "Value %s is not a valid value" % str(value) 

471 raise ValueError(msg) 

472 

473 def _collectImports(self, instance, imports): 

474 """This function should call the _collectImports method on all config 

475 objects the field may own, and union them with the supplied imports 

476 set. 

477 

478 Parameters 

479 ---------- 

480 instance : instance or subclass of `lsst.pex.config.Config` 

481 A config object that has this field defined on it 

482 imports : `set` 

483 Set of python modules that need imported after persistence 

484 """ 

485 pass 

486 

487 def save(self, outfile, instance): 

488 """Save this field to a file (for internal use only). 

489 

490 Parameters 

491 ---------- 

492 outfile : file-like object 

493 A writeable field handle. 

494 instance : `Config` 

495 The `Config` instance that contains this field. 

496 

497 Notes 

498 ----- 

499 This method is invoked by the `~lsst.pex.config.Config` object that 

500 contains this field and should not be called directly. 

501 

502 The output consists of the documentation string 

503 (`lsst.pex.config.Field.doc`) formatted as a Python comment. The second 

504 line is formatted as an assignment: ``{fullname}={value}``. 

505 

506 This output can be executed with Python. 

507 """ 

508 value = self.__get__(instance) 

509 fullname = _joinNamePath(instance._name, self.name) 

510 

511 if self.deprecated and value == self.default: 511 ↛ 512line 511 didn't jump to line 512, because the condition on line 511 was never true

512 return 

513 

514 # write full documentation string as comment lines 

515 # (i.e. first character is #) 

516 doc = "# " + str(self.doc).replace("\n", "\n# ") 

517 if isinstance(value, float) and not math.isfinite(value): 517 ↛ 519line 517 didn't jump to line 519, because the condition on line 517 was never true

518 # non-finite numbers need special care 

519 outfile.write(u"{}\n{}=float('{!r}')\n\n".format(doc, fullname, value)) 

520 else: 

521 outfile.write(u"{}\n{}={!r}\n\n".format(doc, fullname, value)) 

522 

523 def toDict(self, instance): 

524 """Convert the field value so that it can be set as the value of an 

525 item in a `dict` (for internal use only). 

526 

527 Parameters 

528 ---------- 

529 instance : `Config` 

530 The `Config` that contains this field. 

531 

532 Returns 

533 ------- 

534 value : object 

535 The field's value. See *Notes*. 

536 

537 Notes 

538 ----- 

539 This method invoked by the owning `~lsst.pex.config.Config` object and 

540 should not be called directly. 

541 

542 Simple values are passed through. Complex data structures must be 

543 manipulated. For example, a `~lsst.pex.config.Field` holding a 

544 subconfig should, instead of the subconfig object, return a `dict` 

545 where the keys are the field names in the subconfig, and the values are 

546 the field values in the subconfig. 

547 """ 

548 return self.__get__(instance) 

549 

550 def __get__(self, instance, owner=None, at=None, label="default"): 

551 """Define how attribute access should occur on the Config instance 

552 This is invoked by the owning config object and should not be called 

553 directly 

554 

555 When the field attribute is accessed on a Config class object, it 

556 returns the field object itself in order to allow inspection of 

557 Config classes. 

558 

559 When the field attribute is access on a config instance, the actual 

560 value described by the field (and held by the Config instance) is 

561 returned. 

562 """ 

563 if instance is None or not isinstance(instance, Config): 563 ↛ 564line 563 didn't jump to line 564, because the condition on line 563 was never true

564 return self 

565 else: 

566 return instance._storage[self.name] 

567 

568 def __set__(self, instance, value, at=None, label='assignment'): 

569 """Set an attribute on the config instance. 

570 

571 Parameters 

572 ---------- 

573 instance : `lsst.pex.config.Config` 

574 The config instance that contains this field. 

575 value : obj 

576 Value to set on this field. 

577 at : `list` of `lsst.pex.config.callStack.StackFrame` 

578 The call stack (created by 

579 `lsst.pex.config.callStack.getCallStack`). 

580 label : `str`, optional 

581 Event label for the history. 

582 

583 Notes 

584 ----- 

585 This method is invoked by the owning `lsst.pex.config.Config` object 

586 and should not be called directly. 

587 

588 Derived `~lsst.pex.config.Field` classes may need to override the 

589 behavior. When overriding ``__set__``, `~lsst.pex.config.Field` authors 

590 should follow the following rules: 

591 

592 - Do not allow modification of frozen configs. 

593 - Validate the new value **before** modifying the field. Except if the 

594 new value is `None`. `None` is special and no attempt should be made 

595 to validate it until `lsst.pex.config.Config.validate` is called. 

596 - Do not modify the `~lsst.pex.config.Config` instance to contain 

597 invalid values. 

598 - If the field is modified, update the history of the 

599 `lsst.pex.config.field.Field` to reflect the changes. 

600 

601 In order to decrease the need to implement this method in derived 

602 `~lsst.pex.config.Field` types, value validation is performed in the 

603 `lsst.pex.config.Field._validateValue`. If only the validation step 

604 differs in the derived `~lsst.pex.config.Field`, it is simpler to 

605 implement `lsst.pex.config.Field._validateValue` than to reimplement 

606 ``__set__``. More complicated behavior, however, may require 

607 reimplementation. 

608 """ 

609 if instance._frozen: 609 ↛ 610line 609 didn't jump to line 610, because the condition on line 609 was never true

610 raise FieldValidationError(self, instance, "Cannot modify a frozen Config") 

611 

612 history = instance._history.setdefault(self.name, []) 

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

614 value = _autocast(value, self.dtype) 

615 try: 

616 self._validateValue(value) 

617 except BaseException as e: 

618 raise FieldValidationError(self, instance, str(e)) 

619 

620 instance._storage[self.name] = value 

621 if at is None: 621 ↛ 622line 621 didn't jump to line 622, because the condition on line 621 was never true

622 at = getCallStack() 

623 history.append((value, at, label)) 

624 

625 def __delete__(self, instance, at=None, label='deletion'): 

626 """Delete an attribute from a `lsst.pex.config.Config` instance. 

627 

628 Parameters 

629 ---------- 

630 instance : `lsst.pex.config.Config` 

631 The config instance that contains this field. 

632 at : `list` of `lsst.pex.config.callStack.StackFrame` 

633 The call stack (created by 

634 `lsst.pex.config.callStack.getCallStack`). 

635 label : `str`, optional 

636 Event label for the history. 

637 

638 Notes 

639 ----- 

640 This is invoked by the owning `~lsst.pex.config.Config` object and 

641 should not be called directly. 

642 """ 

643 if at is None: 

644 at = getCallStack() 

645 self.__set__(instance, None, at=at, label=label) 

646 

647 def _compare(self, instance1, instance2, shortcut, rtol, atol, output): 

648 """Compare a field (named `Field.name`) in two 

649 `~lsst.pex.config.Config` instances for equality. 

650 

651 Parameters 

652 ---------- 

653 instance1 : `lsst.pex.config.Config` 

654 Left-hand side `Config` instance to compare. 

655 instance2 : `lsst.pex.config.Config` 

656 Right-hand side `Config` instance to compare. 

657 shortcut : `bool`, optional 

658 **Unused.** 

659 rtol : `float`, optional 

660 Relative tolerance for floating point comparisons. 

661 atol : `float`, optional 

662 Absolute tolerance for floating point comparisons. 

663 output : callable, optional 

664 A callable that takes a string, used (possibly repeatedly) to 

665 report inequalities. 

666 

667 Notes 

668 ----- 

669 This method must be overridden by more complex `Field` subclasses. 

670 

671 See also 

672 -------- 

673 lsst.pex.config.compareScalars 

674 """ 

675 v1 = getattr(instance1, self.name) 

676 v2 = getattr(instance2, self.name) 

677 name = getComparisonName( 

678 _joinNamePath(instance1._name, self.name), 

679 _joinNamePath(instance2._name, self.name) 

680 ) 

681 return compareScalars(name, v1, v2, dtype=self.dtype, rtol=rtol, atol=atol, output=output) 

682 

683 

684class RecordingImporter: 

685 """Importer (for `sys.meta_path`) that records which modules are being 

686 imported. 

687 

688 *This class does not do any importing itself.* 

689 

690 Examples 

691 -------- 

692 Use this class as a context manager to ensure it is properly uninstalled 

693 when done: 

694 

695 >>> with RecordingImporter() as importer: 

696 ... # import stuff 

697 ... import numpy as np 

698 ... print("Imported: " + importer.getModules()) 

699 """ 

700 

701 def __init__(self): 

702 self._modules = set() 

703 

704 def __enter__(self): 

705 self.origMetaPath = sys.meta_path 

706 sys.meta_path = [self] + sys.meta_path 

707 return self 

708 

709 def __exit__(self, *args): 

710 self.uninstall() 

711 return False # Don't suppress exceptions 

712 

713 def uninstall(self): 

714 """Uninstall the importer. 

715 """ 

716 sys.meta_path = self.origMetaPath 

717 

718 def find_module(self, fullname, path=None): 

719 """Called as part of the ``import`` chain of events. 

720 """ 

721 self._modules.add(fullname) 

722 # Return None because we don't do any importing. 

723 return None 

724 

725 def getModules(self): 

726 """Get the set of modules that were imported. 

727 

728 Returns 

729 ------- 

730 modules : `set` of `str` 

731 Set of imported module names. 

732 """ 

733 return self._modules 

734 

735 

736class Config(metaclass=ConfigMeta): 

737 """Base class for configuration (*config*) objects. 

738 

739 Notes 

740 ----- 

741 A ``Config`` object will usually have several `~lsst.pex.config.Field` 

742 instances as class attributes. These are used to define most of the base 

743 class behavior. 

744 

745 ``Config`` implements a mapping API that provides many `dict`-like methods, 

746 such as `keys`, `values`, `items`, `iteritems`, `iterkeys`, and 

747 `itervalues`. ``Config`` instances also support the ``in`` operator to 

748 test if a field is in the config. Unlike a `dict`, ``Config`` classes are 

749 not subscriptable. Instead, access individual fields as attributes of the 

750 configuration instance. 

751 

752 Examples 

753 -------- 

754 Config classes are subclasses of ``Config`` that have 

755 `~lsst.pex.config.Field` instances (or instances of 

756 `~lsst.pex.config.Field` subclasses) as class attributes: 

757 

758 >>> from lsst.pex.config import Config, Field, ListField 

759 >>> class DemoConfig(Config): 

760 ... intField = Field(doc="An integer field", dtype=int, default=42) 

761 ... listField = ListField(doc="List of favorite beverages.", dtype=str, 

762 ... default=['coffee', 'green tea', 'water']) 

763 ... 

764 >>> config = DemoConfig() 

765 

766 Configs support many `dict`-like APIs: 

767 

768 >>> config.keys() 

769 ['intField', 'listField'] 

770 >>> 'intField' in config 

771 True 

772 

773 Individual fields can be accessed as attributes of the configuration: 

774 

775 >>> config.intField 

776 42 

777 >>> config.listField.append('earl grey tea') 

778 >>> print(config.listField) 

779 ['coffee', 'green tea', 'water', 'earl grey tea'] 

780 """ 

781 

782 def __iter__(self): 

783 """Iterate over fields. 

784 """ 

785 return self._fields.__iter__() 

786 

787 def keys(self): 

788 """Get field names. 

789 

790 Returns 

791 ------- 

792 names : `list` 

793 List of `lsst.pex.config.Field` names. 

794 

795 See also 

796 -------- 

797 lsst.pex.config.Config.iterkeys 

798 """ 

799 return list(self._storage.keys()) 

800 

801 def values(self): 

802 """Get field values. 

803 

804 Returns 

805 ------- 

806 values : `list` 

807 List of field values. 

808 

809 See also 

810 -------- 

811 lsst.pex.config.Config.itervalues 

812 """ 

813 return list(self._storage.values()) 

814 

815 def items(self): 

816 """Get configurations as ``(field name, field value)`` pairs. 

817 

818 Returns 

819 ------- 

820 items : `list` 

821 List of tuples for each configuration. Tuple items are: 

822 

823 0. Field name. 

824 1. Field value. 

825 

826 See also 

827 -------- 

828 lsst.pex.config.Config.iteritems 

829 """ 

830 return list(self._storage.items()) 

831 

832 def iteritems(self): 

833 """Iterate over (field name, field value) pairs. 

834 

835 Yields 

836 ------ 

837 item : `tuple` 

838 Tuple items are: 

839 

840 0. Field name. 

841 1. Field value. 

842 

843 See also 

844 -------- 

845 lsst.pex.config.Config.items 

846 """ 

847 return iter(self._storage.items()) 

848 

849 def itervalues(self): 

850 """Iterate over field values. 

851 

852 Yields 

853 ------ 

854 value : obj 

855 A field value. 

856 

857 See also 

858 -------- 

859 lsst.pex.config.Config.values 

860 """ 

861 return iter(self.storage.values()) 

862 

863 def iterkeys(self): 

864 """Iterate over field names 

865 

866 Yields 

867 ------ 

868 key : `str` 

869 A field's key (attribute name). 

870 

871 See also 

872 -------- 

873 lsst.pex.config.Config.values 

874 """ 

875 return iter(self.storage.keys()) 

876 

877 def __contains__(self, name): 

878 """!Return True if the specified field exists in this config 

879 

880 @param[in] name field name to test for 

881 """ 

882 return self._storage.__contains__(name) 

883 

884 def __new__(cls, *args, **kw): 

885 """Allocate a new `lsst.pex.config.Config` object. 

886 

887 In order to ensure that all Config object are always in a proper state 

888 when handed to users or to derived `~lsst.pex.config.Config` classes, 

889 some attributes are handled at allocation time rather than at 

890 initialization. 

891 

892 This ensures that even if a derived `~lsst.pex.config.Config` class 

893 implements ``__init__``, its author does not need to be concerned about 

894 when or even the base ``Config.__init__`` should be called. 

895 """ 

896 name = kw.pop("__name", None) 

897 at = kw.pop("__at", getCallStack()) 

898 # remove __label and ignore it 

899 kw.pop("__label", "default") 

900 

901 instance = object.__new__(cls) 

902 instance._frozen = False 

903 instance._name = name 

904 instance._storage = {} 

905 instance._history = {} 

906 instance._imports = set() 

907 # load up defaults 

908 for field in instance._fields.values(): 

909 instance._history[field.name] = [] 

910 field.__set__(instance, field.default, at=at + [field.source], label="default") 

911 # set custom default-overides 

912 instance.setDefaults() 

913 # set constructor overides 

914 instance.update(__at=at, **kw) 

915 return instance 

916 

917 def __reduce__(self): 

918 """Reduction for pickling (function with arguments to reproduce). 

919 

920 We need to condense and reconstitute the `~lsst.pex.config.Config`, 

921 since it may contain lambdas (as the ``check`` elements) that cannot 

922 be pickled. 

923 """ 

924 # The stream must be in characters to match the API but pickle 

925 # requires bytes 

926 stream = io.StringIO() 

927 self.saveToStream(stream) 

928 return (unreduceConfig, (self.__class__, stream.getvalue().encode())) 

929 

930 def setDefaults(self): 

931 """Subclass hook for computing defaults. 

932 

933 Notes 

934 ----- 

935 Derived `~lsst.pex.config.Config` classes that must compute defaults 

936 rather than using the `~lsst.pex.config.Field` instances's defaults 

937 should do so here. To correctly use inherited defaults, 

938 implementations of ``setDefaults`` must call their base class's 

939 ``setDefaults``. 

940 """ 

941 pass 

942 

943 def update(self, **kw): 

944 """Update values of fields specified by the keyword arguments. 

945 

946 Parameters 

947 ---------- 

948 kw 

949 Keywords are configuration field names. Values are configuration 

950 field values. 

951 

952 Notes 

953 ----- 

954 The ``__at`` and ``__label`` keyword arguments are special internal 

955 keywords. They are used to strip out any internal steps from the 

956 history tracebacks of the config. Do not modify these keywords to 

957 subvert a `~lsst.pex.config.Config` instance's history. 

958 

959 Examples 

960 -------- 

961 This is a config with three fields: 

962 

963 >>> from lsst.pex.config import Config, Field 

964 >>> class DemoConfig(Config): 

965 ... fieldA = Field(doc='Field A', dtype=int, default=42) 

966 ... fieldB = Field(doc='Field B', dtype=bool, default=True) 

967 ... fieldC = Field(doc='Field C', dtype=str, default='Hello world') 

968 ... 

969 >>> config = DemoConfig() 

970 

971 These are the default values of each field: 

972 

973 >>> for name, value in config.iteritems(): 

974 ... print(f"{name}: {value}") 

975 ... 

976 fieldA: 42 

977 fieldB: True 

978 fieldC: 'Hello world' 

979 

980 Using this method to update ``fieldA`` and ``fieldC``: 

981 

982 >>> config.update(fieldA=13, fieldC='Updated!') 

983 

984 Now the values of each field are: 

985 

986 >>> for name, value in config.iteritems(): 

987 ... print(f"{name}: {value}") 

988 ... 

989 fieldA: 13 

990 fieldB: True 

991 fieldC: 'Updated!' 

992 """ 

993 at = kw.pop("__at", getCallStack()) 

994 label = kw.pop("__label", "update") 

995 

996 for name, value in kw.items(): 

997 try: 

998 field = self._fields[name] 

999 field.__set__(self, value, at=at, label=label) 

1000 except KeyError: 

1001 raise KeyError("No field of name %s exists in config type %s" % (name, _typeStr(self))) 

1002 

1003 def load(self, filename, root="config"): 

1004 """Modify this config in place by executing the Python code in a 

1005 configuration file. 

1006 

1007 Parameters 

1008 ---------- 

1009 filename : `str` 

1010 Name of the configuration file. A configuration file is Python 

1011 module. 

1012 root : `str`, optional 

1013 Name of the variable in file that refers to the config being 

1014 overridden. 

1015 

1016 For example, the value of root is ``"config"`` and the file 

1017 contains:: 

1018 

1019 config.myField = 5 

1020 

1021 Then this config's field ``myField`` is set to ``5``. 

1022 

1023 **Deprecated:** For backwards compatibility, older config files 

1024 that use ``root="root"`` instead of ``root="config"`` will be 

1025 loaded with a warning printed to `sys.stderr`. This feature will be 

1026 removed at some point. 

1027 

1028 See also 

1029 -------- 

1030 lsst.pex.config.Config.loadFromStream 

1031 lsst.pex.config.Config.save 

1032 lsst.pex.config.Config.saveFromStream 

1033 """ 

1034 with open(filename, "r") as f: 

1035 code = compile(f.read(), filename=filename, mode="exec") 

1036 self.loadFromStream(stream=code, root=root, filename=filename) 

1037 

1038 def loadFromStream(self, stream, root="config", filename=None): 

1039 """Modify this Config in place by executing the Python code in the 

1040 provided stream. 

1041 

1042 Parameters 

1043 ---------- 

1044 stream : file-like object, `str`, or compiled string 

1045 Stream containing configuration override code. 

1046 root : `str`, optional 

1047 Name of the variable in file that refers to the config being 

1048 overridden. 

1049 

1050 For example, the value of root is ``"config"`` and the file 

1051 contains:: 

1052 

1053 config.myField = 5 

1054 

1055 Then this config's field ``myField`` is set to ``5``. 

1056 

1057 **Deprecated:** For backwards compatibility, older config files 

1058 that use ``root="root"`` instead of ``root="config"`` will be 

1059 loaded with a warning printed to `sys.stderr`. This feature will be 

1060 removed at some point. 

1061 filename : `str`, optional 

1062 Name of the configuration file, or `None` if unknown or contained 

1063 in the stream. Used for error reporting. 

1064 

1065 See also 

1066 -------- 

1067 lsst.pex.config.Config.load 

1068 lsst.pex.config.Config.save 

1069 lsst.pex.config.Config.saveFromStream 

1070 """ 

1071 with RecordingImporter() as importer: 

1072 globals = {"__file__": filename} 

1073 try: 

1074 local = {root: self} 

1075 exec(stream, globals, local) 

1076 except NameError as e: 

1077 if root == "config" and "root" in e.args[0]: 

1078 if filename is None: 

1079 # try to determine the file name; a compiled string 

1080 # has attribute "co_filename", 

1081 # an open file has attribute "name", else give up 

1082 filename = getattr(stream, "co_filename", None) 

1083 if filename is None: 

1084 filename = getattr(stream, "name", "?") 

1085 print(f"Config override file {filename!r}" 

1086 " appears to use 'root' instead of 'config'; trying with 'root'", file=sys.stderr) 

1087 local = {"root": self} 

1088 exec(stream, globals, local) 

1089 else: 

1090 raise 

1091 

1092 self._imports.update(importer.getModules()) 

1093 

1094 def save(self, filename, root="config"): 

1095 """Save a Python script to the named file, which, when loaded, 

1096 reproduces this config. 

1097 

1098 Parameters 

1099 ---------- 

1100 filename : `str` 

1101 Desination filename of this configuration. 

1102 root : `str`, optional 

1103 Name to use for the root config variable. The same value must be 

1104 used when loading (see `lsst.pex.config.Config.load`). 

1105 

1106 See also 

1107 -------- 

1108 lsst.pex.config.Config.saveToStream 

1109 lsst.pex.config.Config.load 

1110 lsst.pex.config.Config.loadFromStream 

1111 """ 

1112 d = os.path.dirname(filename) 

1113 with tempfile.NamedTemporaryFile(mode="w", delete=False, dir=d) as outfile: 

1114 self.saveToStream(outfile, root) 

1115 # tempfile is hardcoded to create files with mode '0600' 

1116 # for an explantion of these antics see: 

1117 # https://stackoverflow.com/questions/10291131/how-to-use-os-umask-in-python 

1118 umask = os.umask(0o077) 

1119 os.umask(umask) 

1120 os.chmod(outfile.name, (~umask & 0o666)) 

1121 # chmod before the move so we get quasi-atomic behavior if the 

1122 # source and dest. are on the same filesystem. 

1123 # os.rename may not work across filesystems 

1124 shutil.move(outfile.name, filename) 

1125 

1126 def saveToStream(self, outfile, root="config", skipImports=False): 

1127 """Save a configuration file to a stream, which, when loaded, 

1128 reproduces this config. 

1129 

1130 Parameters 

1131 ---------- 

1132 outfile : file-like object 

1133 Destination file object write the config into. Accepts strings not 

1134 bytes. 

1135 root 

1136 Name to use for the root config variable. The same value must be 

1137 used when loading (see `lsst.pex.config.Config.load`). 

1138 skipImports : `bool`, optional 

1139 If `True` then do not include ``import`` statements in output, 

1140 this is to support human-oriented output from ``pipetask`` where 

1141 additional clutter is not useful. 

1142 

1143 See also 

1144 -------- 

1145 lsst.pex.config.Config.save 

1146 lsst.pex.config.Config.load 

1147 lsst.pex.config.Config.loadFromStream 

1148 """ 

1149 tmp = self._name 

1150 self._rename(root) 

1151 try: 

1152 if not skipImports: 1152 ↛ 1164line 1152 didn't jump to line 1164, because the condition on line 1152 was never false

1153 self._collectImports() 

1154 # Remove self from the set, as it is handled explicitly below 

1155 self._imports.remove(self.__module__) 

1156 configType = type(self) 

1157 typeString = _typeStr(configType) 

1158 outfile.write(f"import {configType.__module__}\n") 

1159 outfile.write(f"assert type({root})=={typeString}, 'config is of type %s.%s instead of " 

1160 f"{typeString}' % (type({root}).__module__, type({root}).__name__)\n") 

1161 for imp in self._imports: 1161 ↛ 1162line 1161 didn't jump to line 1162, because the loop on line 1161 never started

1162 if imp in sys.modules and sys.modules[imp] is not None: 

1163 outfile.write(u"import {}\n".format(imp)) 

1164 self._save(outfile) 

1165 finally: 

1166 self._rename(tmp) 

1167 

1168 def freeze(self): 

1169 """Make this config, and all subconfigs, read-only. 

1170 """ 

1171 self._frozen = True 

1172 for field in self._fields.values(): 

1173 field.freeze(self) 

1174 

1175 def _save(self, outfile): 

1176 """Save this config to an open stream object. 

1177 

1178 Parameters 

1179 ---------- 

1180 outfile : file-like object 

1181 Destination file object write the config into. Accepts strings not 

1182 bytes. 

1183 """ 

1184 for field in self._fields.values(): 

1185 field.save(outfile, self) 

1186 

1187 def _collectImports(self): 

1188 """Adds module containing self to the list of things to import and 

1189 then loops over all the fields in the config calling a corresponding 

1190 collect method. The field method will call _collectImports on any 

1191 configs it may own and return the set of things to import. This 

1192 returned set will be merged with the set of imports for this config 

1193 class. 

1194 """ 

1195 self._imports.add(self.__module__) 

1196 for name, field in self._fields.items(): 

1197 field._collectImports(self, self._imports) 

1198 

1199 def toDict(self): 

1200 """Make a dictionary of field names and their values. 

1201 

1202 Returns 

1203 ------- 

1204 dict_ : `dict` 

1205 Dictionary with keys that are `~lsst.pex.config.Field` names. 

1206 Values are `~lsst.pex.config.Field` values. 

1207 

1208 See also 

1209 -------- 

1210 lsst.pex.config.Field.toDict 

1211 

1212 Notes 

1213 ----- 

1214 This method uses the `~lsst.pex.config.Field.toDict` method of 

1215 individual fields. Subclasses of `~lsst.pex.config.Field` may need to 

1216 implement a ``toDict`` method for *this* method to work. 

1217 """ 

1218 dict_ = {} 

1219 for name, field in self._fields.items(): 

1220 dict_[name] = field.toDict(self) 

1221 return dict_ 

1222 

1223 def names(self): 

1224 """Get all the field names in the config, recursively. 

1225 

1226 Returns 

1227 ------- 

1228 names : `list` of `str` 

1229 Field names. 

1230 """ 

1231 # 

1232 # Rather than sort out the recursion all over again use the 

1233 # pre-existing saveToStream() 

1234 # 

1235 with io.StringIO() as strFd: 

1236 self.saveToStream(strFd, "config") 

1237 contents = strFd.getvalue() 

1238 strFd.close() 

1239 # 

1240 # Pull the names out of the dumped config 

1241 # 

1242 keys = [] 

1243 for line in contents.split("\n"): 

1244 if re.search(r"^((assert|import)\s+|\s*$|#)", line): 

1245 continue 

1246 

1247 mat = re.search(r"^(?:config\.)?([^=]+)\s*=\s*.*", line) 

1248 if mat: 

1249 keys.append(mat.group(1)) 

1250 

1251 return keys 

1252 

1253 def _rename(self, name): 

1254 """Rename this config object in its parent `~lsst.pex.config.Config`. 

1255 

1256 Parameters 

1257 ---------- 

1258 name : `str` 

1259 New name for this config in its parent `~lsst.pex.config.Config`. 

1260 

1261 Notes 

1262 ----- 

1263 This method uses the `~lsst.pex.config.Field.rename` method of 

1264 individual `lsst.pex.config.Field` instances. 

1265 `lsst.pex.config.Field` subclasses may need to implement a ``rename`` 

1266 method for *this* method to work. 

1267 

1268 See also 

1269 -------- 

1270 lsst.pex.config.Field.rename 

1271 """ 

1272 self._name = name 

1273 for field in self._fields.values(): 

1274 field.rename(self) 

1275 

1276 def validate(self): 

1277 """Validate the Config, raising an exception if invalid. 

1278 

1279 Raises 

1280 ------ 

1281 lsst.pex.config.FieldValidationError 

1282 Raised if verification fails. 

1283 

1284 Notes 

1285 ----- 

1286 The base class implementation performs type checks on all fields by 

1287 calling their `~lsst.pex.config.Field.validate` methods. 

1288 

1289 Complex single-field validation can be defined by deriving new Field 

1290 types. For convenience, some derived `lsst.pex.config.Field`-types 

1291 (`~lsst.pex.config.ConfigField` and 

1292 `~lsst.pex.config.ConfigChoiceField`) are defined in `lsst.pex.config` 

1293 that handle recursing into subconfigs. 

1294 

1295 Inter-field relationships should only be checked in derived 

1296 `~lsst.pex.config.Config` classes after calling this method, and base 

1297 validation is complete. 

1298 """ 

1299 for field in self._fields.values(): 

1300 field.validate(self) 

1301 

1302 def formatHistory(self, name, **kwargs): 

1303 """Format a configuration field's history to a human-readable format. 

1304 

1305 Parameters 

1306 ---------- 

1307 name : `str` 

1308 Name of a `~lsst.pex.config.Field` in this config. 

1309 kwargs 

1310 Keyword arguments passed to `lsst.pex.config.history.format`. 

1311 

1312 Returns 

1313 ------- 

1314 history : `str` 

1315 A string containing the formatted history. 

1316 

1317 See also 

1318 -------- 

1319 lsst.pex.config.history.format 

1320 """ 

1321 import lsst.pex.config.history as pexHist 

1322 return pexHist.format(self, name, **kwargs) 

1323 

1324 history = property(lambda x: x._history) 1324 ↛ exitline 1324 didn't run the lambda on line 1324

1325 """Read-only history. 

1326 """ 

1327 

1328 def __setattr__(self, attr, value, at=None, label="assignment"): 

1329 """Set an attribute (such as a field's value). 

1330 

1331 Notes 

1332 ----- 

1333 Unlike normal Python objects, `~lsst.pex.config.Config` objects are 

1334 locked such that no additional attributes nor properties may be added 

1335 to them dynamically. 

1336 

1337 Although this is not the standard Python behavior, it helps to protect 

1338 users from accidentally mispelling a field name, or trying to set a 

1339 non-existent field. 

1340 """ 

1341 if attr in self._fields: 

1342 if self._fields[attr].deprecated is not None: 1342 ↛ 1343line 1342 didn't jump to line 1343, because the condition on line 1342 was never true

1343 fullname = _joinNamePath(self._name, self._fields[attr].name) 

1344 warnings.warn(f"Config field {fullname} is deprecated: {self._fields[attr].deprecated}", 

1345 FutureWarning, stacklevel=2) 

1346 if at is None: 1346 ↛ 1349line 1346 didn't jump to line 1349, because the condition on line 1346 was never false

1347 at = getCallStack() 

1348 # This allows Field descriptors to work. 

1349 self._fields[attr].__set__(self, value, at=at, label=label) 

1350 elif hasattr(getattr(self.__class__, attr, None), '__set__'): 1350 ↛ 1352line 1350 didn't jump to line 1352, because the condition on line 1350 was never true

1351 # This allows properties and other non-Field descriptors to work. 

1352 return object.__setattr__(self, attr, value) 

1353 elif attr in self.__dict__ or attr in ("_name", "_history", "_storage", "_frozen", "_imports"): 1353 ↛ 1358line 1353 didn't jump to line 1358, because the condition on line 1353 was never false

1354 # This allows specific private attributes to work. 

1355 self.__dict__[attr] = value 

1356 else: 

1357 # We throw everything else. 

1358 raise AttributeError("%s has no attribute %s" % (_typeStr(self), attr)) 

1359 

1360 def __delattr__(self, attr, at=None, label="deletion"): 

1361 if attr in self._fields: 

1362 if at is None: 

1363 at = getCallStack() 

1364 self._fields[attr].__delete__(self, at=at, label=label) 

1365 else: 

1366 object.__delattr__(self, attr) 

1367 

1368 def __eq__(self, other): 

1369 if type(other) == type(self): 1369 ↛ 1370line 1369 didn't jump to line 1370, because the condition on line 1369 was never true

1370 for name in self._fields: 

1371 thisValue = getattr(self, name) 

1372 otherValue = getattr(other, name) 

1373 if isinstance(thisValue, float) and math.isnan(thisValue): 

1374 if not math.isnan(otherValue): 

1375 return False 

1376 elif thisValue != otherValue: 

1377 return False 

1378 return True 

1379 return False 

1380 

1381 def __ne__(self, other): 

1382 return not self.__eq__(other) 

1383 

1384 def __str__(self): 

1385 return str(self.toDict()) 

1386 

1387 def __repr__(self): 

1388 return "%s(%s)" % ( 

1389 _typeStr(self), 

1390 ", ".join("%s=%r" % (k, v) for k, v in self.toDict().items() if v is not None) 

1391 ) 

1392 

1393 def compare(self, other, shortcut=True, rtol=1E-8, atol=1E-8, output=None): 

1394 """Compare this configuration to another `~lsst.pex.config.Config` for 

1395 equality. 

1396 

1397 Parameters 

1398 ---------- 

1399 other : `lsst.pex.config.Config` 

1400 Other `~lsst.pex.config.Config` object to compare against this 

1401 config. 

1402 shortcut : `bool`, optional 

1403 If `True`, return as soon as an inequality is found. Default is 

1404 `True`. 

1405 rtol : `float`, optional 

1406 Relative tolerance for floating point comparisons. 

1407 atol : `float`, optional 

1408 Absolute tolerance for floating point comparisons. 

1409 output : callable, optional 

1410 A callable that takes a string, used (possibly repeatedly) to 

1411 report inequalities. 

1412 

1413 Returns 

1414 ------- 

1415 isEqual : `bool` 

1416 `True` when the two `lsst.pex.config.Config` instances are equal. 

1417 `False` if there is an inequality. 

1418 

1419 See also 

1420 -------- 

1421 lsst.pex.config.compareConfigs 

1422 

1423 Notes 

1424 ----- 

1425 Unselected targets of `~lsst.pex.config.RegistryField` fields and 

1426 unselected choices of `~lsst.pex.config.ConfigChoiceField` fields 

1427 are not considered by this method. 

1428 

1429 Floating point comparisons are performed by `numpy.allclose`. 

1430 """ 

1431 name1 = self._name if self._name is not None else "config" 

1432 name2 = other._name if other._name is not None else "config" 

1433 name = getComparisonName(name1, name2) 

1434 return compareConfigs(name, self, other, shortcut=shortcut, 

1435 rtol=rtol, atol=atol, output=output) 

1436 

1437 @classmethod 

1438 def __init_subclass__(cls, **kwargs): 

1439 """Run initialization for every subclass. 

1440 

1441 Specifically registers the subclass with a YAML representer 

1442 and YAML constructor (if pyyaml is available) 

1443 """ 

1444 super().__init_subclass__(**kwargs) 

1445 

1446 if not yaml: 1446 ↛ 1447line 1446 didn't jump to line 1447, because the condition on line 1446 was never true

1447 return 

1448 

1449 yaml.add_representer(cls, _yaml_config_representer) 

1450 

1451 @classmethod 

1452 def _fromPython(cls, config_py): 

1453 """Instantiate a `Config`-subclass from serialized Python form. 

1454 

1455 Parameters 

1456 ---------- 

1457 config_py : `str` 

1458 A serialized form of the Config as created by 

1459 `Config.saveToStream`. 

1460 

1461 Returns 

1462 ------- 

1463 config : `Config` 

1464 Reconstructed `Config` instant. 

1465 """ 

1466 cls = _classFromPython(config_py) 

1467 return unreduceConfig(cls, config_py) 

1468 

1469 

1470def _classFromPython(config_py): 

1471 """Return the Config subclass required by this Config serialization. 

1472 

1473 Parameters 

1474 ---------- 

1475 config_py : `str` 

1476 A serialized form of the Config as created by 

1477 `Config.saveToStream`. 

1478 

1479 Returns 

1480 ------- 

1481 cls : `type` 

1482 The `Config` subclass associated with this config. 

1483 """ 

1484 # standard serialization has the form: 

1485 # import config.class 

1486 # assert type(config)==config.class.Config, ... 

1487 # We want to parse these two lines so we can get the class itself 

1488 

1489 # Do a single regex to avoid large string copies when splitting a 

1490 # large config into separate lines. 

1491 matches = re.search(r"^import ([\w.]+)\nassert .*==(.*?),", config_py) 

1492 

1493 if not matches: 

1494 first_line, second_line, _ = config_py.split("\n", 2) 

1495 raise ValueError("First two lines did not match expected form. Got:\n" 

1496 f" - {first_line}\n" 

1497 f" - {second_line}") 

1498 

1499 module_name = matches.group(1) 

1500 module = importlib.import_module(module_name) 

1501 

1502 # Second line 

1503 full_name = matches.group(2) 

1504 

1505 # Remove the module name from the full name 

1506 if not full_name.startswith(module_name): 

1507 raise ValueError(f"Module name ({module_name}) inconsistent with full name ({full_name})") 

1508 

1509 # if module name is a.b.c and full name is a.b.c.d.E then 

1510 # we need to remove a.b.c. and iterate over the remainder 

1511 # The +1 is for the extra dot after a.b.c 

1512 remainder = full_name[len(module_name)+1:] 

1513 components = remainder.split(".") 

1514 pytype = module 

1515 for component in components: 

1516 pytype = getattr(pytype, component) 

1517 return pytype 

1518 

1519 

1520def unreduceConfig(cls, stream): 

1521 """Create a `~lsst.pex.config.Config` from a stream. 

1522 

1523 Parameters 

1524 ---------- 

1525 cls : `lsst.pex.config.Config`-type 

1526 A `lsst.pex.config.Config` type (not an instance) that is instantiated 

1527 with configurations in the ``stream``. 

1528 stream : file-like object, `str`, or compiled string 

1529 Stream containing configuration override code. 

1530 

1531 Returns 

1532 ------- 

1533 config : `lsst.pex.config.Config` 

1534 Config instance. 

1535 

1536 See also 

1537 -------- 

1538 lsst.pex.config.Config.loadFromStream 

1539 """ 

1540 config = cls() 

1541 config.loadFromStream(stream) 

1542 return config