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

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 program is free software: you can redistribute it and/or modify 

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

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

12# (at your option) any later version. 

13# 

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

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

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

17# GNU General Public License for more details. 

18# 

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

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

21 

22import unittest 

23import os 

24import contextlib 

25import collections 

26import itertools 

27import shutil 

28import tempfile 

29 

30from lsst.daf.butler import ConfigSubset, Config 

31 

32 

33@contextlib.contextmanager 

34def modified_environment(**environ): 

35 """ 

36 Temporarily set environment variables. 

37 

38 >>> with modified_environment(DAF_BUTLER_CONFIG_PATHS="/somewhere"): 

39 ... os.environ["DAF_BUTLER_CONFIG_PATHS"] == "/somewhere" 

40 True 

41 

42 >>> "DAF_BUTLER_CONFIG_PATHS" != "/somewhere" 

43 True 

44 

45 Parameters 

46 ---------- 

47 environ : `dict` 

48 Key value pairs of environment variables to temporarily set. 

49 """ 

50 old_environ = dict(os.environ) 

51 os.environ.update(environ) 

52 try: 

53 yield 

54 finally: 

55 os.environ.clear() 

56 os.environ.update(old_environ) 

57 

58 

59class ExampleWithConfigFileReference: 

60 defaultConfigFile = "viacls.yaml" 

61 

62 

63class ExampleWithConfigFileReference2: 

64 defaultConfigFile = "viacls2.yaml" 

65 

66 

67class ConfigTest(ConfigSubset): 

68 component = "comp" 

69 requiredKeys = ("item1", "item2") 

70 defaultConfigFile = "testconfig.yaml" 

71 

72 

73class ConfigTestEmpty(ConfigTest): 

74 defaultConfigFile = "testconfig_empty.yaml" 

75 requiredKeys = () 

76 

77 

78class ConfigTestButlerDir(ConfigTest): 

79 defaultConfigFile = "testConfigs/testconfig.yaml" 

80 

81 

82class ConfigTestNoDefaults(ConfigTest): 

83 defaultConfigFile = None 

84 requiredKeys = () 

85 

86 

87class ConfigTestAbsPath(ConfigTest): 

88 defaultConfigFile = None 

89 requiredKeys = () 

90 

91 

92class ConfigTestCls(ConfigTest): 

93 defaultConfigFile = "withcls.yaml" 

94 

95 

96class ConfigTestCase(unittest.TestCase): 

97 """Tests of simple Config""" 

98 

99 def testBadConfig(self): 

100 for badArg in ([], "file.fits"): 

101 with self.assertRaises(RuntimeError): 

102 Config(badArg) 

103 

104 def testBasics(self): 

105 c = Config({"1": 2, "3": 4, "key3": 6, "dict": {"a": 1, "b": 2}}) 

106 pretty = c.ppprint() 

107 self.assertIn("key3", pretty) 

108 r = repr(c) 

109 self.assertIn("key3", r) 

110 regex = r"^Config\(\{.*\}\)$" 

111 self.assertRegex(r, regex) 

112 c2 = eval(r) 

113 self.assertIn("1", c) 

114 for n in c.names(): 

115 self.assertEqual(c2[n], c[n]) 

116 self.assertEqual(c, c2) 

117 s = str(c) 

118 self.assertIn("\n", s) 

119 self.assertNotRegex(s, regex) 

120 

121 self.assertCountEqual(c.keys(), ["1", "3", "key3", "dict"]) 

122 self.assertEqual(list(c), list(c.keys())) 

123 self.assertEqual(list(c.values()), [c[k] for k in c.keys()]) 

124 self.assertEqual(list(c.items()), [(k, c[k]) for k in c.keys()]) 

125 

126 newKeys = ("key4", ".dict.q", ("dict", "r"), "5") 

127 oldKeys = ("key3", ".dict.a", ("dict", "b"), "3") 

128 remainingKey = "1" 

129 

130 # Check get with existing key 

131 for k in oldKeys: 

132 self.assertEqual(c.get(k, "missing"), c[k]) 

133 

134 # Check get, pop with nonexistent key 

135 for k in newKeys: 

136 self.assertEqual(c.get(k, "missing"), "missing") 

137 self.assertEqual(c.pop(k, "missing"), "missing") 

138 

139 # Check setdefault with existing key 

140 for k in oldKeys: 

141 c.setdefault(k, 8) 

142 self.assertNotEqual(c[k], 8) 

143 

144 # Check setdefault with nonexistent key (mutates c, adding newKeys) 

145 for k in newKeys: 

146 c.setdefault(k, 8) 

147 self.assertEqual(c[k], 8) 

148 

149 # Check pop with existing key (mutates c, removing newKeys) 

150 for k in newKeys: 

151 v = c[k] 

152 self.assertEqual(c.pop(k, "missing"), v) 

153 

154 # Check deletion (mutates c, removing oldKeys) 

155 for k in ("key3", ".dict.a", ("dict", "b"), "3"): 

156 self.assertIn(k, c) 

157 del c[k] 

158 self.assertNotIn(k, c) 

159 

160 # Check that `dict` still exists, but is now empty (then remove 

161 # it, mutatic c) 

162 self.assertIn("dict", c) 

163 del c["dict"] 

164 

165 # Check popitem (mutates c, removing remainingKey) 

166 v = c[remainingKey] 

167 self.assertEqual(c.popitem(), (remainingKey, v)) 

168 

169 # Check that c is now empty 

170 self.assertFalse(c) 

171 

172 def testDict(self): 

173 """Test toDict()""" 

174 c1 = Config({"a": {"b": 1}, "c": 2}) 

175 self.assertIsInstance(c1["a"], Config) 

176 d1 = c1.toDict() 

177 self.assertIsInstance(d1["a"], dict) 

178 self.assertEqual(d1["a"], c1["a"]) 

179 

180 # Modifying one does not change the other 

181 d1["a"]["c"] = 2 

182 self.assertNotEqual(d1["a"], c1["a"]) 

183 

184 def assertSplit(self, answer, *args): 

185 """Helper function to compare string splitting""" 

186 for s in (answer, *args): 

187 split = Config._splitIntoKeys(s) 

188 self.assertEqual(split, answer) 

189 

190 def testSplitting(self): 

191 """Test of the internal splitting API.""" 

192 # Try lots of keys that will return the same answer 

193 answer = ["a", "b", "c", "d"] 

194 self.assertSplit(answer, ".a.b.c.d", ":a:b:c:d", "\ta\tb\tc\td", "\ra\rb\rc\rd") 

195 

196 answer = ["a", "calexp.wcs", "b"] 

197 self.assertSplit(answer, r".a.calexp\.wcs.b", ":a:calexp.wcs:b") 

198 

199 self.assertSplit(["a.b.c"]) 

200 self.assertSplit(["a", r"b\.c"], r"_a_b\.c") 

201 

202 # Escaping a backslash before a delimiter currently fails 

203 with self.assertRaises(ValueError): 

204 Config._splitIntoKeys(r".a.calexp\\.wcs.b") 

205 

206 # The next two fail because internally \r is magic when escaping 

207 # a delimiter. 

208 with self.assertRaises(ValueError): 

209 Config._splitIntoKeys("\ra\rcalexp\\\rwcs\rb") 

210 

211 with self.assertRaises(ValueError): 

212 Config._splitIntoKeys(".a.cal\rexp\\.wcs.b") 

213 

214 def testEscape(self): 

215 c = Config({"a": {"foo.bar": 1}, "b😂c": {"bar_baz": 2}}) 

216 self.assertEqual(c[r".a.foo\.bar"], 1) 

217 self.assertEqual(c[":a:foo.bar"], 1) 

218 self.assertEqual(c[".b😂c.bar_baz"], 2) 

219 self.assertEqual(c[r"😂b\😂c😂bar_baz"], 2) 

220 self.assertEqual(c[r"\a\foo.bar"], 1) 

221 self.assertEqual(c["\ra\rfoo.bar"], 1) 

222 with self.assertRaises(ValueError): 

223 c[".a.foo\\.bar\r"] 

224 

225 def testOperators(self): 

226 c1 = Config({"a": {"b": 1}, "c": 2}) 

227 c2 = c1.copy() 

228 self.assertEqual(c1, c2) 

229 self.assertIsInstance(c2, Config) 

230 c2[".a.b"] = 5 

231 self.assertNotEqual(c1, c2) 

232 

233 def testUpdate(self): 

234 c = Config({"a": {"b": 1}}) 

235 c.update({"a": {"c": 2}}) 

236 self.assertEqual(c[".a.b"], 1) 

237 self.assertEqual(c[".a.c"], 2) 

238 c.update({"a": {"d": [3, 4]}}) 

239 self.assertEqual(c[".a.d.0"], 3) 

240 c.update({"z": [5, 6, {"g": 2, "h": 3}]}) 

241 self.assertEqual(c[".z.1"], 6) 

242 

243 # This is detached from parent 

244 c2 = c[".z.2"] 

245 self.assertEqual(c2["g"], 2) 

246 c2.update({"h": 4, "j": 5}) 

247 self.assertEqual(c2["h"], 4) 

248 self.assertNotIn(".z.2.j", c) 

249 self.assertNotEqual(c[".z.2.h"], 4) 

250 

251 with self.assertRaises(RuntimeError): 

252 c.update([1, 2, 3]) 

253 

254 def testHierarchy(self): 

255 c = Config() 

256 

257 # Simple dict 

258 c["a"] = {"z": 52, "x": "string"} 

259 self.assertIn(".a.z", c) 

260 self.assertEqual(c[".a.x"], "string") 

261 

262 # Try different delimiters 

263 self.assertEqual(c["⇛a⇛z"], 52) 

264 self.assertEqual(c[("a", "z")], 52) 

265 self.assertEqual(c["a", "z"], 52) 

266 

267 c[".b.new.thing1"] = "thing1" 

268 c[".b.new.thing2"] = "thing2" 

269 c[".b.new.thing3.supp"] = "supplemental" 

270 self.assertEqual(c[".b.new.thing1"], "thing1") 

271 tmp = c[".b.new"] 

272 self.assertEqual(tmp["thing2"], "thing2") 

273 self.assertEqual(c[".b.new.thing3.supp"], "supplemental") 

274 

275 # Test that we can index into lists 

276 c[".a.b.c"] = [1, "7", 3, {"1": 4, "5": "Five"}, "hello"] 

277 self.assertIn(".a.b.c.3.5", c) 

278 self.assertNotIn(".a.b.c.10", c) 

279 self.assertNotIn(".a.b.c.10.d", c) 

280 self.assertEqual(c[".a.b.c.3.5"], "Five") 

281 # Is the value in the list? 

282 self.assertIn(".a.b.c.hello", c) 

283 self.assertNotIn(".a.b.c.hello.not", c) 

284 

285 # And assign to an element in the list 

286 self.assertEqual(c[".a.b.c.1"], "7") 

287 c[".a.b.c.1"] = 8 

288 self.assertEqual(c[".a.b.c.1"], 8) 

289 self.assertIsInstance(c[".a.b.c"], collections.abc.Sequence) 

290 

291 # Test we do get lists back from asArray 

292 a = c.asArray(".a.b.c") 

293 self.assertIsInstance(a, list) 

294 

295 # Is it the *same* list as in the config 

296 a.append("Sentinel") 

297 self.assertIn("Sentinel", c[".a.b.c"]) 

298 self.assertIn(".a.b.c.Sentinel", c) 

299 

300 # Test we always get a list 

301 for k in c.names(): 

302 a = c.asArray(k) 

303 self.assertIsInstance(a, list) 

304 

305 # Check we get the same top level keys 

306 self.assertEqual(set(c.names(topLevelOnly=True)), set(c._data.keys())) 

307 

308 # Check that we can iterate through items 

309 for k, v in c.items(): 

310 self.assertEqual(c[k], v) 

311 

312 # Check that lists still work even if assigned a dict 

313 c = Config({"cls": "lsst.daf.butler", 

314 "formatters": {"calexp.wcs": "{component}", 

315 "calexp": "{datasetType}"}, 

316 "datastores": [{"datastore": {"cls": "datastore1"}}, 

317 {"datastore": {"cls": "datastore2"}}]}) 

318 c[".datastores.1.datastore"] = {"cls": "datastore2modified"} 

319 self.assertEqual(c[".datastores.0.datastore.cls"], "datastore1") 

320 self.assertEqual(c[".datastores.1.datastore.cls"], "datastore2modified") 

321 self.assertIsInstance(c["datastores"], collections.abc.Sequence) 

322 

323 # Test that we can get all the listed names. 

324 # and also that they are marked as "in" the Config 

325 # Try delimited names and tuples 

326 for n in itertools.chain(c.names(), c.nameTuples()): 

327 val = c[n] 

328 self.assertIsNotNone(val) 

329 self.assertIn(n, c) 

330 

331 names = c.names() 

332 nameTuples = c.nameTuples() 

333 self.assertEqual(len(names), len(nameTuples)) 

334 self.assertEqual(len(names), 11) 

335 self.assertEqual(len(nameTuples), 11) 

336 

337 # Test that delimiter escaping works 

338 names = c.names(delimiter=".") 

339 for n in names: 

340 self.assertIn(n, c) 

341 self.assertIn(".formatters.calexp\\.wcs", names) 

342 

343 # Use a name that includes the internal default delimiter 

344 # to test automatic adjustment of delimiter 

345 strangeKey = f"calexp{c._D}wcs" 

346 c["formatters", strangeKey] = "dynamic" 

347 names = c.names() 

348 self.assertIn(strangeKey, "-".join(names)) 

349 self.assertFalse(names[0].startswith(c._D)) 

350 for n in names: 

351 self.assertIn(n, c) 

352 

353 top = c.nameTuples(topLevelOnly=True) 

354 self.assertIsInstance(top[0], tuple) 

355 

356 # Investigate a possible delimeter in a key 

357 c = Config({"formatters": {"calexp.wcs": 2, "calexp": 3}}) 

358 self.assertEqual(c[":formatters:calexp.wcs"], 2) 

359 self.assertEqual(c[":formatters:calexp"], 3) 

360 for k, v in c["formatters"].items(): 

361 self.assertEqual(c["formatters", k], v) 

362 

363 # Check internal delimiter inheritance 

364 c._D = "." 

365 c2 = c["formatters"] 

366 self.assertEqual(c._D, c2._D) # Check that the child inherits 

367 self.assertNotEqual(c2._D, Config._D) 

368 

369 def testStringYaml(self): 

370 """Test that we can create configs from strings""" 

371 

372 c = Config.fromYaml(""" 

373testing: hello 

374formatters: 

375 calexp: 3""") 

376 self.assertEqual(c["formatters", "calexp"], 3) 

377 self.assertEqual(c["testing"], "hello") 

378 

379 

380class ConfigSubsetTestCase(unittest.TestCase): 

381 """Tests for ConfigSubset 

382 """ 

383 

384 def setUp(self): 

385 self.testDir = os.path.abspath(os.path.dirname(__file__)) 

386 self.configDir = os.path.join(self.testDir, "config", "testConfigs") 

387 self.configDir2 = os.path.join(self.testDir, "config", "testConfigs", "test2") 

388 self.configDir3 = os.path.join(self.testDir, "config", "testConfigs", "test3") 

389 

390 def testEmpty(self): 

391 """Ensure that we can read an empty file.""" 

392 c = ConfigTestEmpty(searchPaths=(self.configDir,)) 

393 self.assertIsInstance(c, ConfigSubset) 

394 

395 def testDefaults(self): 

396 """Read of defaults""" 

397 

398 # Supply the search path explicitly 

399 c = ConfigTest(searchPaths=(self.configDir,)) 

400 self.assertIsInstance(c, ConfigSubset) 

401 self.assertIn("item3", c) 

402 self.assertEqual(c["item3"], 3) 

403 

404 # Use environment 

405 with modified_environment(DAF_BUTLER_CONFIG_PATH=self.configDir): 

406 c = ConfigTest() 

407 self.assertIsInstance(c, ConfigSubset) 

408 self.assertEqual(c["item3"], 3) 

409 

410 # No default so this should fail 

411 with self.assertRaises(KeyError): 

412 c = ConfigTest() 

413 

414 def testExternalOverride(self): 

415 """Ensure that external values win""" 

416 c = ConfigTest({"item3": "newval"}, searchPaths=(self.configDir,)) 

417 self.assertIn("item3", c) 

418 self.assertEqual(c["item3"], "newval") 

419 

420 def testSearchPaths(self): 

421 """Two search paths""" 

422 c = ConfigTest(searchPaths=(self.configDir2, self.configDir)) 

423 self.assertIsInstance(c, ConfigSubset) 

424 self.assertIn("item3", c) 

425 self.assertEqual(c["item3"], "override") 

426 self.assertEqual(c["item4"], "new") 

427 

428 c = ConfigTest(searchPaths=(self.configDir, self.configDir2)) 

429 self.assertIsInstance(c, ConfigSubset) 

430 self.assertIn("item3", c) 

431 self.assertEqual(c["item3"], 3) 

432 self.assertEqual(c["item4"], "new") 

433 

434 def testExternalHierarchy(self): 

435 """Test that we can provide external config parameters in hierarchy""" 

436 c = ConfigTest({"comp": {"item1": 6, "item2": "a", "a": "b", 

437 "item3": 7}, "item4": 8}) 

438 self.assertIn("a", c) 

439 self.assertEqual(c["a"], "b") 

440 self.assertNotIn("item4", c) 

441 

442 def testNoDefaults(self): 

443 """Ensure that defaults can be turned off.""" 

444 

445 # Mandatory keys but no defaults 

446 c = ConfigTest({"item1": "a", "item2": "b", "item6": 6}) 

447 self.assertEqual(len(c.filesRead), 0) 

448 self.assertIn("item1", c) 

449 self.assertEqual(c["item6"], 6) 

450 

451 c = ConfigTestNoDefaults() 

452 self.assertEqual(len(c.filesRead), 0) 

453 

454 def testAbsPath(self): 

455 """Read default config from an absolute path""" 

456 # Force the path to be absolute in the class 

457 ConfigTestAbsPath.defaultConfigFile = os.path.join(self.configDir, "abspath.yaml") 

458 c = ConfigTestAbsPath() 

459 self.assertEqual(c["item11"], "eleventh") 

460 self.assertEqual(len(c.filesRead), 1) 

461 

462 # Now specify the normal config file with an absolute path 

463 ConfigTestAbsPath.defaultConfigFile = os.path.join(self.configDir, ConfigTest.defaultConfigFile) 

464 c = ConfigTestAbsPath() 

465 self.assertEqual(c["item11"], 11) 

466 self.assertEqual(len(c.filesRead), 1) 

467 

468 # and a search path that will also include the file 

469 c = ConfigTestAbsPath(searchPaths=(self.configDir, self.configDir2,)) 

470 self.assertEqual(c["item11"], 11) 

471 self.assertEqual(len(c.filesRead), 1) 

472 

473 # Same as above but this time with relative path and two search paths 

474 # to ensure the count changes 

475 ConfigTestAbsPath.defaultConfigFile = ConfigTest.defaultConfigFile 

476 c = ConfigTestAbsPath(searchPaths=(self.configDir, self.configDir2,)) 

477 self.assertEqual(len(c.filesRead), 2) 

478 

479 # Reset the class 

480 ConfigTestAbsPath.defaultConfigFile = None 

481 

482 def testClassDerived(self): 

483 """Read config specified in class determined from config""" 

484 c = ConfigTestCls(searchPaths=(self.configDir,)) 

485 self.assertEqual(c["item50"], 50) 

486 self.assertEqual(c["help"], "derived") 

487 

488 # Same thing but additional search path 

489 c = ConfigTestCls(searchPaths=(self.configDir, self.configDir2)) 

490 self.assertEqual(c["item50"], 50) 

491 self.assertEqual(c["help"], "derived") 

492 self.assertEqual(c["help2"], "second") 

493 

494 # Same thing but reverse the two paths 

495 c = ConfigTestCls(searchPaths=(self.configDir2, self.configDir)) 

496 self.assertEqual(c["item50"], 500) 

497 self.assertEqual(c["help"], "class") 

498 self.assertEqual(c["help2"], "second") 

499 self.assertEqual(c["help3"], "third") 

500 

501 def testInclude(self): 

502 """Read a config that has an include directive""" 

503 c = Config(os.path.join(self.configDir, "testinclude.yaml")) 

504 self.assertEqual(c[".comp1.item1"], 58) 

505 self.assertEqual(c[".comp2.comp.item1"], 1) 

506 self.assertEqual(c[".comp3.1.comp.item1"], "posix") 

507 self.assertEqual(c[".comp4.0.comp.item1"], "posix") 

508 self.assertEqual(c[".comp4.1.comp.item1"], 1) 

509 self.assertEqual(c[".comp5.comp6.comp.item1"], "posix") 

510 

511 # Test a specific name and then test that all 

512 # returned names are "in" the config. 

513 names = c.names() 

514 self.assertIn(c._D.join(("", "comp3", "1", "comp", "item1")), names) 

515 for n in names: 

516 self.assertIn(n, c) 

517 

518 # Test that override delimiter works 

519 delimiter = "-" 

520 names = c.names(delimiter=delimiter) 

521 self.assertIn(delimiter.join(("", "comp3", "1", "comp", "item1")), names) 

522 

523 def testStringInclude(self): 

524 """Using include directives in strings""" 

525 

526 # See if include works for absolute path 

527 c = Config.fromYaml(f"something: !include {os.path.join(self.configDir, 'testconfig.yaml')}") 

528 self.assertEqual(c["something", "comp", "item3"], 3) 

529 

530 with self.assertRaises(FileNotFoundError) as cm: 

531 Config.fromYaml("something: !include /not/here.yaml") 

532 # Test that it really was trying to open the absolute path 

533 self.assertIn("'/not/here.yaml'", str(cm.exception)) 

534 

535 def testIncludeConfigs(self): 

536 """Test the special includeConfigs key for pulling in additional 

537 files.""" 

538 c = Config(os.path.join(self.configDir, "configIncludes.yaml")) 

539 self.assertEqual(c["comp", "item2"], "hello") 

540 self.assertEqual(c["comp", "item50"], 5000) 

541 self.assertEqual(c["comp", "item1"], "first") 

542 self.assertEqual(c["comp", "item10"], "tenth") 

543 self.assertEqual(c["comp", "item11"], "eleventh") 

544 self.assertEqual(c["unrelated"], 1) 

545 self.assertEqual(c["addon", "comp", "item1"], "posix") 

546 self.assertEqual(c["addon", "comp", "item11"], -1) 

547 self.assertEqual(c["addon", "comp", "item50"], 500) 

548 

549 # Now test with an environment variable in includeConfigs 

550 with modified_environment(SPECIAL_BUTLER_DIR=self.configDir3): 

551 c = Config(os.path.join(self.configDir, "configIncludesEnv.yaml")) 

552 self.assertEqual(c["comp", "item2"], "hello") 

553 self.assertEqual(c["comp", "item50"], 5000) 

554 self.assertEqual(c["comp", "item1"], "first") 

555 self.assertEqual(c["comp", "item10"], "tenth") 

556 self.assertEqual(c["comp", "item11"], "eleventh") 

557 self.assertEqual(c["unrelated"], 1) 

558 self.assertEqual(c["addon", "comp", "item1"], "envvar") 

559 self.assertEqual(c["addon", "comp", "item11"], -1) 

560 self.assertEqual(c["addon", "comp", "item50"], 501) 

561 

562 # This will fail 

563 with modified_environment(SPECIAL_BUTLER_DIR=self.configDir2): 

564 with self.assertRaises(FileNotFoundError): 

565 Config(os.path.join(self.configDir, "configIncludesEnv.yaml")) 

566 

567 def testResource(self): 

568 c = Config("resource://lsst.daf.butler/configs/datastore.yaml") 

569 self.assertIn("datastore", c) 

570 

571 # Test that we can include a resource URI 

572 yaml = """ 

573toplevel: true 

574resource: !include resource://lsst.daf.butler/configs/datastore.yaml 

575""" 

576 c = Config.fromYaml(yaml) 

577 self.assertIn(("resource", "datastore", "cls"), c) 

578 

579 # Test that we can include a resource URI with includeConfigs 

580 yaml = """ 

581toplevel: true 

582resource: 

583 includeConfigs: resource://lsst.daf.butler/configs/datastore.yaml 

584""" 

585 c = Config.fromYaml(yaml) 

586 self.assertIn(("resource", "datastore", "cls"), c) 

587 

588 

589class FileWriteConfigTestCase(unittest.TestCase): 

590 

591 def setUp(self): 

592 self.tmpdir = tempfile.mkdtemp() 

593 

594 def tearDown(self): 

595 if os.path.exists(self.tmpdir): 

596 shutil.rmtree(self.tmpdir, ignore_errors=True) 

597 

598 def testDump(self): 

599 """Test that we can write and read a configuration.""" 

600 

601 c = Config({"1": 2, "3": 4, "key3": 6, "dict": {"a": 1, "b": 2}}) 

602 

603 outpath = os.path.join(self.tmpdir, "test.yaml") 

604 c.dumpToUri(outpath) 

605 

606 c2 = Config(outpath) 

607 self.assertEqual(c2, c) 

608 

609 c.dumpToUri(outpath, overwrite=True) 

610 with self.assertRaises(FileExistsError): 

611 c.dumpToUri(outpath, overwrite=False) 

612 

613 

614if __name__ == "__main__": 614 ↛ 615line 614 didn't jump to line 615, because the condition on line 614 was never true

615 unittest.main()