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