Coverage for tests/test_config.py : 14%

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/>.
22import unittest
23import os
24import contextlib
25import collections
26import itertools
28from lsst.daf.butler import ConfigSubset, Config
29from lsst.daf.butler.tests.utils import makeTestTempDir, removeTestTempDir
31TESTDIR = os.path.abspath(os.path.dirname(__file__))
34@contextlib.contextmanager
35def modified_environment(**environ):
36 """
37 Temporarily set environment variables.
39 >>> with modified_environment(DAF_BUTLER_CONFIG_PATHS="/somewhere"):
40 ... os.environ["DAF_BUTLER_CONFIG_PATHS"] == "/somewhere"
41 True
43 >>> "DAF_BUTLER_CONFIG_PATHS" != "/somewhere"
44 True
46 Parameters
47 ----------
48 environ : `dict`
49 Key value pairs of environment variables to temporarily set.
50 """
51 old_environ = dict(os.environ)
52 os.environ.update(environ)
53 try:
54 yield
55 finally:
56 os.environ.clear()
57 os.environ.update(old_environ)
60class ExampleWithConfigFileReference:
61 defaultConfigFile = "viacls.yaml"
64class ExampleWithConfigFileReference2:
65 defaultConfigFile = "viacls2.yaml"
68class ConfigTest(ConfigSubset):
69 component = "comp"
70 requiredKeys = ("item1", "item2")
71 defaultConfigFile = "testconfig.yaml"
74class ConfigTestEmpty(ConfigTest):
75 defaultConfigFile = "testconfig_empty.yaml"
76 requiredKeys = ()
79class ConfigTestButlerDir(ConfigTest):
80 defaultConfigFile = "testConfigs/testconfig.yaml"
83class ConfigTestNoDefaults(ConfigTest):
84 defaultConfigFile = None
85 requiredKeys = ()
88class ConfigTestAbsPath(ConfigTest):
89 defaultConfigFile = None
90 requiredKeys = ()
93class ConfigTestCls(ConfigTest):
94 defaultConfigFile = "withcls.yaml"
97class ConfigTestCase(unittest.TestCase):
98 """Tests of simple Config"""
100 def testBadConfig(self):
101 for badArg in ([], # Bad argument
102 __file__, # Bad file extension for existing file
103 ):
104 with self.assertRaises(RuntimeError):
105 Config(badArg)
106 for badArg in ("file.fits", # File that does not exist with bad extension
107 "b/c/d/", # Directory that does not exist
108 "file.yaml", # Good extension for missing file
109 ):
110 with self.assertRaises(FileNotFoundError):
111 Config(badArg)
113 def testBasics(self):
114 c = Config({"1": 2, "3": 4, "key3": 6, "dict": {"a": 1, "b": 2}})
115 pretty = c.ppprint()
116 self.assertIn("key3", pretty)
117 r = repr(c)
118 self.assertIn("key3", r)
119 regex = r"^Config\(\{.*\}\)$"
120 self.assertRegex(r, regex)
121 c2 = eval(r)
122 self.assertIn("1", c)
123 for n in c.names():
124 self.assertEqual(c2[n], c[n])
125 self.assertEqual(c, c2)
126 s = str(c)
127 self.assertIn("\n", s)
128 self.assertNotRegex(s, regex)
130 self.assertCountEqual(c.keys(), ["1", "3", "key3", "dict"])
131 self.assertEqual(list(c), list(c.keys()))
132 self.assertEqual(list(c.values()), [c[k] for k in c.keys()])
133 self.assertEqual(list(c.items()), [(k, c[k]) for k in c.keys()])
135 newKeys = ("key4", ".dict.q", ("dict", "r"), "5")
136 oldKeys = ("key3", ".dict.a", ("dict", "b"), "3")
137 remainingKey = "1"
139 # Check get with existing key
140 for k in oldKeys:
141 self.assertEqual(c.get(k, "missing"), c[k])
143 # Check get, pop with nonexistent key
144 for k in newKeys:
145 self.assertEqual(c.get(k, "missing"), "missing")
146 self.assertEqual(c.pop(k, "missing"), "missing")
148 # Check setdefault with existing key
149 for k in oldKeys:
150 c.setdefault(k, 8)
151 self.assertNotEqual(c[k], 8)
153 # Check setdefault with nonexistent key (mutates c, adding newKeys)
154 for k in newKeys:
155 c.setdefault(k, 8)
156 self.assertEqual(c[k], 8)
158 # Check pop with existing key (mutates c, removing newKeys)
159 for k in newKeys:
160 v = c[k]
161 self.assertEqual(c.pop(k, "missing"), v)
163 # Check deletion (mutates c, removing oldKeys)
164 for k in ("key3", ".dict.a", ("dict", "b"), "3"):
165 self.assertIn(k, c)
166 del c[k]
167 self.assertNotIn(k, c)
169 # Check that `dict` still exists, but is now empty (then remove
170 # it, mutatic c)
171 self.assertIn("dict", c)
172 del c["dict"]
174 # Check popitem (mutates c, removing remainingKey)
175 v = c[remainingKey]
176 self.assertEqual(c.popitem(), (remainingKey, v))
178 # Check that c is now empty
179 self.assertFalse(c)
181 def testDict(self):
182 """Test toDict()"""
183 c1 = Config({"a": {"b": 1}, "c": 2})
184 self.assertIsInstance(c1["a"], Config)
185 d1 = c1.toDict()
186 self.assertIsInstance(d1["a"], dict)
187 self.assertEqual(d1["a"], c1["a"])
189 # Modifying one does not change the other
190 d1["a"]["c"] = 2
191 self.assertNotEqual(d1["a"], c1["a"])
193 def assertSplit(self, answer, *args):
194 """Helper function to compare string splitting"""
195 for s in (answer, *args):
196 split = Config._splitIntoKeys(s)
197 self.assertEqual(split, answer)
199 def testSplitting(self):
200 """Test of the internal splitting API."""
201 # Try lots of keys that will return the same answer
202 answer = ["a", "b", "c", "d"]
203 self.assertSplit(answer, ".a.b.c.d", ":a:b:c:d", "\ta\tb\tc\td", "\ra\rb\rc\rd")
205 answer = ["a", "calexp.wcs", "b"]
206 self.assertSplit(answer, r".a.calexp\.wcs.b", ":a:calexp.wcs:b")
208 self.assertSplit(["a.b.c"])
209 self.assertSplit(["a", r"b\.c"], r"_a_b\.c")
211 # Escaping a backslash before a delimiter currently fails
212 with self.assertRaises(ValueError):
213 Config._splitIntoKeys(r".a.calexp\\.wcs.b")
215 # The next two fail because internally \r is magic when escaping
216 # a delimiter.
217 with self.assertRaises(ValueError):
218 Config._splitIntoKeys("\ra\rcalexp\\\rwcs\rb")
220 with self.assertRaises(ValueError):
221 Config._splitIntoKeys(".a.cal\rexp\\.wcs.b")
223 def testEscape(self):
224 c = Config({"a": {"foo.bar": 1}, "b😂c": {"bar_baz": 2}})
225 self.assertEqual(c[r".a.foo\.bar"], 1)
226 self.assertEqual(c[":a:foo.bar"], 1)
227 self.assertEqual(c[".b😂c.bar_baz"], 2)
228 self.assertEqual(c[r"😂b\😂c😂bar_baz"], 2)
229 self.assertEqual(c[r"\a\foo.bar"], 1)
230 self.assertEqual(c["\ra\rfoo.bar"], 1)
231 with self.assertRaises(ValueError):
232 c[".a.foo\\.bar\r"]
234 def testOperators(self):
235 c1 = Config({"a": {"b": 1}, "c": 2})
236 c2 = c1.copy()
237 self.assertEqual(c1, c2)
238 self.assertIsInstance(c2, Config)
239 c2[".a.b"] = 5
240 self.assertNotEqual(c1, c2)
242 def testUpdate(self):
243 c = Config({"a": {"b": 1}})
244 c.update({"a": {"c": 2}})
245 self.assertEqual(c[".a.b"], 1)
246 self.assertEqual(c[".a.c"], 2)
247 c.update({"a": {"d": [3, 4]}})
248 self.assertEqual(c[".a.d.0"], 3)
249 c.update({"z": [5, 6, {"g": 2, "h": 3}]})
250 self.assertEqual(c[".z.1"], 6)
252 # This is detached from parent
253 c2 = c[".z.2"]
254 self.assertEqual(c2["g"], 2)
255 c2.update({"h": 4, "j": 5})
256 self.assertEqual(c2["h"], 4)
257 self.assertNotIn(".z.2.j", c)
258 self.assertNotEqual(c[".z.2.h"], 4)
260 with self.assertRaises(RuntimeError):
261 c.update([1, 2, 3])
263 def testHierarchy(self):
264 c = Config()
266 # Simple dict
267 c["a"] = {"z": 52, "x": "string"}
268 self.assertIn(".a.z", c)
269 self.assertEqual(c[".a.x"], "string")
271 # Try different delimiters
272 self.assertEqual(c["⇛a⇛z"], 52)
273 self.assertEqual(c[("a", "z")], 52)
274 self.assertEqual(c["a", "z"], 52)
276 c[".b.new.thing1"] = "thing1"
277 c[".b.new.thing2"] = "thing2"
278 c[".b.new.thing3.supp"] = "supplemental"
279 self.assertEqual(c[".b.new.thing1"], "thing1")
280 tmp = c[".b.new"]
281 self.assertEqual(tmp["thing2"], "thing2")
282 self.assertEqual(c[".b.new.thing3.supp"], "supplemental")
284 # Test that we can index into lists
285 c[".a.b.c"] = [1, "7", 3, {"1": 4, "5": "Five"}, "hello"]
286 self.assertIn(".a.b.c.3.5", c)
287 self.assertNotIn(".a.b.c.10", c)
288 self.assertNotIn(".a.b.c.10.d", c)
289 self.assertEqual(c[".a.b.c.3.5"], "Five")
290 # Is the value in the list?
291 self.assertIn(".a.b.c.hello", c)
292 self.assertNotIn(".a.b.c.hello.not", c)
294 # And assign to an element in the list
295 self.assertEqual(c[".a.b.c.1"], "7")
296 c[".a.b.c.1"] = 8
297 self.assertEqual(c[".a.b.c.1"], 8)
298 self.assertIsInstance(c[".a.b.c"], collections.abc.Sequence)
300 # Test we do get lists back from asArray
301 a = c.asArray(".a.b.c")
302 self.assertIsInstance(a, list)
304 # Is it the *same* list as in the config
305 a.append("Sentinel")
306 self.assertIn("Sentinel", c[".a.b.c"])
307 self.assertIn(".a.b.c.Sentinel", c)
309 # Test we always get a list
310 for k in c.names():
311 a = c.asArray(k)
312 self.assertIsInstance(a, list)
314 # Check we get the same top level keys
315 self.assertEqual(set(c.names(topLevelOnly=True)), set(c._data.keys()))
317 # Check that we can iterate through items
318 for k, v in c.items():
319 self.assertEqual(c[k], v)
321 # Check that lists still work even if assigned a dict
322 c = Config({"cls": "lsst.daf.butler",
323 "formatters": {"calexp.wcs": "{component}",
324 "calexp": "{datasetType}"},
325 "datastores": [{"datastore": {"cls": "datastore1"}},
326 {"datastore": {"cls": "datastore2"}}]})
327 c[".datastores.1.datastore"] = {"cls": "datastore2modified"}
328 self.assertEqual(c[".datastores.0.datastore.cls"], "datastore1")
329 self.assertEqual(c[".datastores.1.datastore.cls"], "datastore2modified")
330 self.assertIsInstance(c["datastores"], collections.abc.Sequence)
332 # Test that we can get all the listed names.
333 # and also that they are marked as "in" the Config
334 # Try delimited names and tuples
335 for n in itertools.chain(c.names(), c.nameTuples()):
336 val = c[n]
337 self.assertIsNotNone(val)
338 self.assertIn(n, c)
340 names = c.names()
341 nameTuples = c.nameTuples()
342 self.assertEqual(len(names), len(nameTuples))
343 self.assertEqual(len(names), 11)
344 self.assertEqual(len(nameTuples), 11)
346 # Test that delimiter escaping works
347 names = c.names(delimiter=".")
348 for n in names:
349 self.assertIn(n, c)
350 self.assertIn(".formatters.calexp\\.wcs", names)
352 # Use a name that includes the internal default delimiter
353 # to test automatic adjustment of delimiter
354 strangeKey = f"calexp{c._D}wcs"
355 c["formatters", strangeKey] = "dynamic"
356 names = c.names()
357 self.assertIn(strangeKey, "-".join(names))
358 self.assertFalse(names[0].startswith(c._D))
359 for n in names:
360 self.assertIn(n, c)
362 top = c.nameTuples(topLevelOnly=True)
363 self.assertIsInstance(top[0], tuple)
365 # Investigate a possible delimeter in a key
366 c = Config({"formatters": {"calexp.wcs": 2, "calexp": 3}})
367 self.assertEqual(c[":formatters:calexp.wcs"], 2)
368 self.assertEqual(c[":formatters:calexp"], 3)
369 for k, v in c["formatters"].items():
370 self.assertEqual(c["formatters", k], v)
372 # Check internal delimiter inheritance
373 c._D = "."
374 c2 = c["formatters"]
375 self.assertEqual(c._D, c2._D) # Check that the child inherits
376 self.assertNotEqual(c2._D, Config._D)
378 def testSerializedString(self):
379 """Test that we can create configs from strings"""
381 serialized = {
382 "yaml": """
383testing: hello
384formatters:
385 calexp: 3""",
386 "json": '{"testing": "hello", "formatters": {"calexp": 3}}'
387 }
389 for format, string in serialized.items():
390 c = Config.fromString(string, format=format)
391 self.assertEqual(c["formatters", "calexp"], 3)
392 self.assertEqual(c["testing"], "hello")
394 with self.assertRaises(ValueError):
395 Config.fromString("", format="unknown")
397 with self.assertRaises(ValueError):
398 Config.fromString(serialized["yaml"], format="json")
400 # This JSON can be parsed by YAML parser
401 j = Config.fromString(serialized["json"])
402 y = Config.fromString(serialized["yaml"])
403 self.assertEqual(j["formatters", "calexp"], 3)
404 self.assertEqual(j.toDict(), y.toDict())
406 # Round trip JSON -> Config -> YAML -> Config -> JSON -> Config
407 c1 = Config.fromString(serialized["json"], format="json")
408 yaml = c1.dump(format="yaml")
409 c2 = Config.fromString(yaml, format="yaml")
410 json = c2.dump(format="json")
411 c3 = Config.fromString(json, format="json")
412 self.assertEqual(c3.toDict(), c1.toDict())
415class ConfigSubsetTestCase(unittest.TestCase):
416 """Tests for ConfigSubset
417 """
419 def setUp(self):
420 self.testDir = os.path.abspath(os.path.dirname(__file__))
421 self.configDir = os.path.join(self.testDir, "config", "testConfigs")
422 self.configDir2 = os.path.join(self.testDir, "config", "testConfigs", "test2")
423 self.configDir3 = os.path.join(self.testDir, "config", "testConfigs", "test3")
425 def testEmpty(self):
426 """Ensure that we can read an empty file."""
427 c = ConfigTestEmpty(searchPaths=(self.configDir,))
428 self.assertIsInstance(c, ConfigSubset)
430 def testDefaults(self):
431 """Read of defaults"""
433 # Supply the search path explicitly
434 c = ConfigTest(searchPaths=(self.configDir,))
435 self.assertIsInstance(c, ConfigSubset)
436 self.assertIn("item3", c)
437 self.assertEqual(c["item3"], 3)
439 # Use environment
440 with modified_environment(DAF_BUTLER_CONFIG_PATH=self.configDir):
441 c = ConfigTest()
442 self.assertIsInstance(c, ConfigSubset)
443 self.assertEqual(c["item3"], 3)
445 # No default so this should fail
446 with self.assertRaises(KeyError):
447 c = ConfigTest()
449 def testExternalOverride(self):
450 """Ensure that external values win"""
451 c = ConfigTest({"item3": "newval"}, searchPaths=(self.configDir,))
452 self.assertIn("item3", c)
453 self.assertEqual(c["item3"], "newval")
455 def testSearchPaths(self):
456 """Two search paths"""
457 c = ConfigTest(searchPaths=(self.configDir2, self.configDir))
458 self.assertIsInstance(c, ConfigSubset)
459 self.assertIn("item3", c)
460 self.assertEqual(c["item3"], "override")
461 self.assertEqual(c["item4"], "new")
463 c = ConfigTest(searchPaths=(self.configDir, self.configDir2))
464 self.assertIsInstance(c, ConfigSubset)
465 self.assertIn("item3", c)
466 self.assertEqual(c["item3"], 3)
467 self.assertEqual(c["item4"], "new")
469 def testExternalHierarchy(self):
470 """Test that we can provide external config parameters in hierarchy"""
471 c = ConfigTest({"comp": {"item1": 6, "item2": "a", "a": "b",
472 "item3": 7}, "item4": 8})
473 self.assertIn("a", c)
474 self.assertEqual(c["a"], "b")
475 self.assertNotIn("item4", c)
477 def testNoDefaults(self):
478 """Ensure that defaults can be turned off."""
480 # Mandatory keys but no defaults
481 c = ConfigTest({"item1": "a", "item2": "b", "item6": 6})
482 self.assertEqual(len(c.filesRead), 0)
483 self.assertIn("item1", c)
484 self.assertEqual(c["item6"], 6)
486 c = ConfigTestNoDefaults()
487 self.assertEqual(len(c.filesRead), 0)
489 def testAbsPath(self):
490 """Read default config from an absolute path"""
491 # Force the path to be absolute in the class
492 ConfigTestAbsPath.defaultConfigFile = os.path.join(self.configDir, "abspath.yaml")
493 c = ConfigTestAbsPath()
494 self.assertEqual(c["item11"], "eleventh")
495 self.assertEqual(len(c.filesRead), 1)
497 # Now specify the normal config file with an absolute path
498 ConfigTestAbsPath.defaultConfigFile = os.path.join(self.configDir, ConfigTest.defaultConfigFile)
499 c = ConfigTestAbsPath()
500 self.assertEqual(c["item11"], 11)
501 self.assertEqual(len(c.filesRead), 1)
503 # and a search path that will also include the file
504 c = ConfigTestAbsPath(searchPaths=(self.configDir, self.configDir2,))
505 self.assertEqual(c["item11"], 11)
506 self.assertEqual(len(c.filesRead), 1)
508 # Same as above but this time with relative path and two search paths
509 # to ensure the count changes
510 ConfigTestAbsPath.defaultConfigFile = ConfigTest.defaultConfigFile
511 c = ConfigTestAbsPath(searchPaths=(self.configDir, self.configDir2,))
512 self.assertEqual(len(c.filesRead), 2)
514 # Reset the class
515 ConfigTestAbsPath.defaultConfigFile = None
517 def testClassDerived(self):
518 """Read config specified in class determined from config"""
519 c = ConfigTestCls(searchPaths=(self.configDir,))
520 self.assertEqual(c["item50"], 50)
521 self.assertEqual(c["help"], "derived")
523 # Same thing but additional search path
524 c = ConfigTestCls(searchPaths=(self.configDir, self.configDir2))
525 self.assertEqual(c["item50"], 50)
526 self.assertEqual(c["help"], "derived")
527 self.assertEqual(c["help2"], "second")
529 # Same thing but reverse the two paths
530 c = ConfigTestCls(searchPaths=(self.configDir2, self.configDir))
531 self.assertEqual(c["item50"], 500)
532 self.assertEqual(c["help"], "class")
533 self.assertEqual(c["help2"], "second")
534 self.assertEqual(c["help3"], "third")
536 def testInclude(self):
537 """Read a config that has an include directive"""
538 c = Config(os.path.join(self.configDir, "testinclude.yaml"))
539 self.assertEqual(c[".comp1.item1"], 58)
540 self.assertEqual(c[".comp2.comp.item1"], 1)
541 self.assertEqual(c[".comp3.1.comp.item1"], "posix")
542 self.assertEqual(c[".comp4.0.comp.item1"], "posix")
543 self.assertEqual(c[".comp4.1.comp.item1"], 1)
544 self.assertEqual(c[".comp5.comp6.comp.item1"], "posix")
546 # Test a specific name and then test that all
547 # returned names are "in" the config.
548 names = c.names()
549 self.assertIn(c._D.join(("", "comp3", "1", "comp", "item1")), names)
550 for n in names:
551 self.assertIn(n, c)
553 # Test that override delimiter works
554 delimiter = "-"
555 names = c.names(delimiter=delimiter)
556 self.assertIn(delimiter.join(("", "comp3", "1", "comp", "item1")), names)
558 def testStringInclude(self):
559 """Using include directives in strings"""
561 # See if include works for absolute path
562 c = Config.fromYaml(f"something: !include {os.path.join(self.configDir, 'testconfig.yaml')}")
563 self.assertEqual(c["something", "comp", "item3"], 3)
565 with self.assertRaises(FileNotFoundError) as cm:
566 Config.fromYaml("something: !include /not/here.yaml")
567 # Test that it really was trying to open the absolute path
568 self.assertIn("'/not/here.yaml'", str(cm.exception))
570 def testIncludeConfigs(self):
571 """Test the special includeConfigs key for pulling in additional
572 files."""
573 c = Config(os.path.join(self.configDir, "configIncludes.yaml"))
574 self.assertEqual(c["comp", "item2"], "hello")
575 self.assertEqual(c["comp", "item50"], 5000)
576 self.assertEqual(c["comp", "item1"], "first")
577 self.assertEqual(c["comp", "item10"], "tenth")
578 self.assertEqual(c["comp", "item11"], "eleventh")
579 self.assertEqual(c["unrelated"], 1)
580 self.assertEqual(c["addon", "comp", "item1"], "posix")
581 self.assertEqual(c["addon", "comp", "item11"], -1)
582 self.assertEqual(c["addon", "comp", "item50"], 500)
584 c = Config(os.path.join(self.configDir, "configIncludes.json"))
585 self.assertEqual(c["comp", "item2"], "hello")
586 self.assertEqual(c["comp", "item50"], 5000)
587 self.assertEqual(c["comp", "item1"], "first")
588 self.assertEqual(c["comp", "item10"], "tenth")
589 self.assertEqual(c["comp", "item11"], "eleventh")
590 self.assertEqual(c["unrelated"], 1)
591 self.assertEqual(c["addon", "comp", "item1"], "posix")
592 self.assertEqual(c["addon", "comp", "item11"], -1)
593 self.assertEqual(c["addon", "comp", "item50"], 500)
595 # Now test with an environment variable in includeConfigs
596 with modified_environment(SPECIAL_BUTLER_DIR=self.configDir3):
597 c = Config(os.path.join(self.configDir, "configIncludesEnv.yaml"))
598 self.assertEqual(c["comp", "item2"], "hello")
599 self.assertEqual(c["comp", "item50"], 5000)
600 self.assertEqual(c["comp", "item1"], "first")
601 self.assertEqual(c["comp", "item10"], "tenth")
602 self.assertEqual(c["comp", "item11"], "eleventh")
603 self.assertEqual(c["unrelated"], 1)
604 self.assertEqual(c["addon", "comp", "item1"], "envvar")
605 self.assertEqual(c["addon", "comp", "item11"], -1)
606 self.assertEqual(c["addon", "comp", "item50"], 501)
608 # This will fail
609 with modified_environment(SPECIAL_BUTLER_DIR=self.configDir2):
610 with self.assertRaises(FileNotFoundError):
611 Config(os.path.join(self.configDir, "configIncludesEnv.yaml"))
613 def testResource(self):
614 c = Config("resource://lsst.daf.butler/configs/datastore.yaml")
615 self.assertIn("datastore", c)
617 # Test that we can include a resource URI
618 yaml = """
619toplevel: true
620resource: !include resource://lsst.daf.butler/configs/datastore.yaml
621"""
622 c = Config.fromYaml(yaml)
623 self.assertIn(("resource", "datastore", "cls"), c)
625 # Test that we can include a resource URI with includeConfigs
626 yaml = """
627toplevel: true
628resource:
629 includeConfigs: resource://lsst.daf.butler/configs/datastore.yaml
630"""
631 c = Config.fromYaml(yaml)
632 self.assertIn(("resource", "datastore", "cls"), c)
635class FileWriteConfigTestCase(unittest.TestCase):
637 def setUp(self):
638 self.tmpdir = makeTestTempDir(TESTDIR)
640 def tearDown(self):
641 removeTestTempDir(self.tmpdir)
643 def testDump(self):
644 """Test that we can write and read a configuration."""
646 c = Config({"1": 2, "3": 4, "key3": 6, "dict": {"a": 1, "b": 2}})
648 for format in ("yaml", "json"):
649 outpath = os.path.join(self.tmpdir, f"test.{format}")
650 c.dumpToUri(outpath)
652 c2 = Config(outpath)
653 self.assertEqual(c2, c)
655 c.dumpToUri(outpath, overwrite=True)
656 with self.assertRaises(FileExistsError):
657 c.dumpToUri(outpath, overwrite=False)
660if __name__ == "__main__": 660 ↛ 661line 660 didn't jump to line 661, because the condition on line 660 was never true
661 unittest.main()