def test_inifile_nonexistent(self): logging.getLogger().setLevel(logging.CRITICAL) cfg = LayeredConfig(INIFile("nonexistent.ini")) self.assertEqual([], list(cfg)) # make sure a nonexistent inifile doesn't interfere with the # rest of the LayeredConfig object defobj = Defaults({'datadir': 'something'}) iniobj = INIFile("nonexistent.ini") cfg = LayeredConfig(defobj, iniobj) self.assertEqual("something", cfg.datadir) # and make sure it's settable (should set up the INIFile # object and affect it, and leave the defaults dict untouched # as it's the lowest priority) cfg.datadir = "else" self.assertEqual("else", cfg.datadir) self.assertEqual("else", iniobj.get("datadir")) self.assertEqual("something", defobj.get("datadir")) # same as above, but with a "empty" INIFile object iniobj = INIFile() cfg = LayeredConfig(defobj, iniobj) self.assertEqual("something", cfg.datadir) cfg.datadir = "else" self.assertEqual("else", cfg.datadir)
def test_inifile_default_as_root(self): # using a rootsection named DEFAULT triggers different # cascading-like behaviour in configparser. # load a modified version of complex.ini with open("complex.ini") as fp: ini = fp.read() with open("complex-otherroot.ini", "w") as fp: fp.write(ini.replace("[__root__]", "[DEFAULT]")) cfg = LayeredConfig( INIFile("complex-otherroot.ini", rootsection="DEFAULT")) # this is a modified/simplified version of ._test_subsections self.assertEqual(cfg.home, 'mydata') self.assertEqual(cfg.processes, '4') self.assertEqual(cfg.force, 'True') self.assertEqual(cfg.mymodule.force, 'False') self.assertEqual(cfg.extra, "foo, bar") self.assertEqual(cfg.mymodule.extra, "foo, baz") with self.assertRaises(AttributeError): cfg.expires self.assertEqual(cfg.mymodule.expires, "2014-10-15") # this is really unwanted cascading behaviour self.assertEqual(cfg.mymodule.home, 'mydata') self.assertEqual(cfg.mymodule.processes, '4') os.unlink("complex-otherroot.ini")
def __init__(self, repos, inifile=None, **kwargs): self.repos = repos self.log = logging.getLogger("wsgi") # FIXME: Cut-n-paste of the method in Resources.__init__ loadpaths = [ResourceLoader.make_loadpath(repo) for repo in repos] loadpath = ["."] # cwd always has priority -- makes sense? for subpath in loadpaths: for p in subpath: if p not in loadpath: loadpath.append(p) self.resourceloader = ResourceLoader(*loadpath) # FIXME: need to specify documentroot? defaults = DocumentRepository.get_default_options() if inifile: assert os.path.exists( inifile), "INI file %s doesn't exist (relative to %s)" % ( inifile, os.getcwd()) # NB: If both inifile and kwargs are specified, the latter # will take precedence. I think this is the expected # behaviour. self.config = LayeredConfig(Defaults(defaults), INIFile(inifile), Defaults(kwargs), cascade=True)
def _createrepo(self): clsdefaults = self.repoclass.get_default_options() configfile = self.datadir + os.sep + "ferenda.ini" config = LayeredConfig(Defaults(clsdefaults), INIFile(configfile), cascade=True) return self.repoclass(config)
def test_modified_subsections(self): defaults = {'force': False, 'home': 'thisdata', 'loglevel': 'INFO'} cmdline = ['--mymodule-home=thatdata', '--mymodule-force'] cfg = LayeredConfig(Defaults(defaults), INIFile("complex.ini"), Commandline(cmdline), cascade=True) cfg.mymodule.expires = date(2014, 10, 24)
def test_layered_config_with_untyped_source(): typed_source1 = {'x': 5, 'b': {'y': 6}} typed_source2 = {'a': 1, 'b': {'c': 2}} untyped_source1 = io.StringIO( pytest.helpers.unindent(u""" [__root__] a=11 """)) untyped_source2 = io.StringIO( pytest.helpers.unindent(u""" [__root__] a=10 x=50 [b] c=20 y=60 [b.d] e=30 """)) typed1 = DictSource(typed_source1) typed2 = DictSource(typed_source2) untyped1 = INIFile(untyped_source1) untyped2 = INIFile(untyped_source2, subsection_token='.') config = LayeredConfig(typed1, typed2, untyped1, untyped2) assert typed1.x == 5 assert typed1.b.y == 6 assert typed2.a == 1 assert typed2.b.c == 2 with pytest.raises(KeyError): typed2.b.d.e assert untyped1.a == '11' assert untyped2.a == '10' assert untyped2.b.c == '20' assert untyped2.b.d.e == '30' assert config.a == 10 # found in first typed source assert config.x == 50 # found in second typed source assert config.b.c == 20 assert config.b.y == 60 assert config.b.d.e == '30'
def test_layered(self): defaults = {'home': 'someplace'} cmdline = ['--home=anotherplace'] env = {'MYAPP_HOME': 'yourdata'} cfg = LayeredConfig(Defaults(defaults)) self.assertEqual(cfg.home, 'someplace') cfg = LayeredConfig(Defaults(defaults), INIFile("simple.ini")) self.assertEqual(cfg.home, 'mydata') cfg = LayeredConfig(Defaults(defaults), INIFile("simple.ini"), Environment(env, prefix="MYAPP_")) self.assertEqual(cfg.home, 'yourdata') cfg = LayeredConfig(Defaults(defaults), INIFile("simple.ini"), Environment(env, prefix="MYAPP_"), Commandline(cmdline)) self.assertEqual(cfg.home, 'anotherplace') self.assertEqual( ['home', 'processes', 'force', 'extra', 'expires', 'lastrun'], list(cfg))
def test_set(self): # a value is set in a particular underlying source, and the # dirty flag isn't set. cfg = LayeredConfig(INIFile("simple.ini")) LayeredConfig.set(cfg, 'expires', date(2013, 9, 18), "inifile") # NOTE: For this config, where no type information is # available for key 'expires', INIFile.set will convert the # date object to a string, at which point typing is lost. # Therefore this commmented-out test will fail # self.assertEqual(date(2013, 9, 18), cfg.expires) self.assertEqual("2013-09-18", cfg.expires) self.assertFalse(cfg._sources[0].dirty)
def test_get(self): cfg = LayeredConfig( Defaults({ 'codedefaults': 'yes', 'force': False, 'home': '/usr/home' }), INIFile('simple.ini')) # and then do a bunch of get() calls with optional fallbacks self.assertEqual("yes", LayeredConfig.get(cfg, "codedefaults")) self.assertEqual("mydata", LayeredConfig.get(cfg, "home")) self.assertEqual(None, LayeredConfig.get(cfg, "nonexistent")) self.assertEqual("NO!", LayeredConfig.get(cfg, "nonexistent", "NO!"))
def load_config(): """ Searches a standard set of locations for .uh_tools.ini, and parses the first match. """ pwd = os.getcwd() home = os.path.expanduser("~") paths = [ os.path.join(home, CONFIG_FILE_NAME), os.path.join(pwd, CONFIG_FILE_NAME), ] config_file = None for path in paths: if os.path.exists(path): config_file = path break return LayeredConfig(INIFile(config_file), Environment(prefix="TLOG_"))
def test_ini_source(): inifile = io.StringIO( pytest.helpers.unindent(u""" [__root__] a=1 [b] c=2 [b.d] e=%(interpolated)s interpolated=3 [b/d/f] g=4 """)) config = INIFile(inifile) assert config.a == '1' assert config.b.c == '2' assert config['b.d'].e == '3' assert config['b/d/f'].g == '4'
def test_source_items_with_strategies_and_untyped_source(monkeypatch): monkeypatch.setenv('MVP_A', 100) untyped_source = io.StringIO( pytest.helpers.unindent(u""" [__root__] a=1000 """)) config = LayeredConfig( Environment('MVP_'), # last source still needs a typed source DictSource({ 'a': 1, 'x': [5, 6], 'b': { 'c': 2, 'd': [3, 4] } }), DictSource({ 'a': 10, 'x': [50, 60], 'b': { 'c': 20, 'd': [30, 40] } }), INIFile(untyped_source), strategies={ 'a': strategy.add, 'x': strategy.collect, # keep lists intact 'c': strategy.collect, # collect values into list 'd': strategy.merge, # merge lists }) items = list(config.items()) assert items == [('a', 1111), ('b', config.b), ('x', [[50, 60], [5, 6]])] items = list(config.b.items()) assert items == [('c', [20, 2]), ('d', [30, 40, 3, 4])]
def test_read_layered_sources_with_strategies_and_untyped_sources(monkeypatch): monkeypatch.setenv('MVP_A', 100) untyped_source = io.StringIO( pytest.helpers.unindent(u""" [__root__] a=1000 """)) config = LayeredConfig( Environment('MVP_'), # last source still needs a typed source DictSource({ 'a': 1, 'x': [5, 6], 'b': { 'c': 2, 'd': [3, 4] } }), DictSource({ 'a': 10, 'x': [50, 60], 'b': { 'c': 20, 'd': [30, 40] } }), INIFile(untyped_source), strategies={ 'a': strategy.add, 'x': strategy.collect, # keep lists intact 'c': strategy.collect, # collect values into list 'd': strategy.merge, # merge lists }) assert config.a == 1111 assert config.x == [[50, 60], [5, 6]] assert config.b.c == [20, 2] assert config.b.d == [30, 40, 3, 4]
def test_write(self): cfg = LayeredConfig(INIFile("complex.ini")) cfg.mymodule.expires = date(2014, 10, 24) # calling write for any submodule will force a write of the # entire config file LayeredConfig.write(cfg.mymodule) want = """[__root__] home = mydata processes = 4 force = True extra = foo, bar [mymodule] force = False extra = foo, baz expires = 2014-10-24 [extramodule] unique = True """ with open("complex.ini") as fp: got = fp.read().replace("\r\n", "\n") self.assertEqual(want, got)
# 1. hard-coded defaults defaults = {"hello": "is it me you're looking for?"} # 2. INI configuration file with open("myapp.ini", "w") as fp: fp.write(""" [__root__] hello = kitty """) # 3. enironment variables import os os.environ['MYAPP_HELLO'] = 'goodbye' # 4.command-line arguments import sys sys.argv = ['./myapp.py', '--hello=world'] # Create a config object that gets settings from these four # sources. config = LayeredConfig(Defaults(defaults), INIFile("myapp.ini"), Environment(prefix="MYAPP_"), Commandline()) # Prints "Hello world!", i.e the value provided by command-line # arguments. Latter sources take precedence over earlier sources. print("Hello %s!" % config.hello) # end firststep return_value = True
# end defaults # begin inifile with open("myapp.ini", "w") as fp: fp.write("""[__root__] home = /tmp/myapp dostuff = False times = 4 duedate = 2014-10-30 things = Huey, Dewey, Louie [submodule] retry = False lastrun = 2014-10-30 16:40:22 """) inifile = INIFile("myapp.ini") # end inifile # begin argparse parser = argparse.ArgumentParser("This is a simple program") parser.add_argument("--home", help="The home directory of the app") parser.add_argument('--dostuff', action="store_true", help="Do some work") parser.add_argument("-t", "--times", type=int, help="Number of times to do it") parser.add_argument('--things', action="append", help="Extra things to crunch") parser.add_argument('--retry', action="store_true", help="Try again") parser.add_argument("file", metavar="FILE", help="The filename to process") # end argparse # begin layeredconfig sys.argv = ['./myapp.py', '--home=/opt/myapp', '-t=2', '--dostuff', 'file.txt'] cfg = LayeredConfig(defaults, inifile, Commandline(parser=parser))
class TestINIFile(TestINIFileHelper, unittest.TestCase, TestConfigSourceHelper): supported_types = (str,) supports_nesting = False def setUp(self): super(TestINIFile, self).setUp() self.simple = INIFile("simple.ini") self.complex = INIFile("complex.ini") # Overrides of TestHelper.test_get, .test_typed and # .test_subsection_nested due to limitations of INIFile # INIFile carries no typing information def test_get(self): self.assertEqual(self.simple.get("home"), "mydata") self.assertEqual(self.simple.get("processes"), "4") self.assertEqual(self.simple.get("force"), "True") self.assertEqual(self.simple.get("extra"), "foo, bar") self.assertEqual(self.simple.get("expires"), "2014-10-15") self.assertEqual(self.simple.get("lastrun"), "2014-10-15 14:32:07") def test_typed(self): for key in self.simple.keys(): self.assertFalse(self.simple.typed(key)) # Override: INIFile doesn't support nested subsections def test_subsection_nested(self): subsec = self.complex.subsection('mymodule') self.assertEqual(set(subsec.subsections()), set(())) def test_inifile_default_as_root(self): # using a rootsection named DEFAULT triggers different # cascading-like behaviour in configparser. # load a modified version of complex.ini with open("complex.ini") as fp: ini = fp.read() with open("complex-otherroot.ini", "w") as fp: fp.write(ini.replace("[__root__]", "[DEFAULT]")) cfg = LayeredConfig(INIFile("complex-otherroot.ini", rootsection="DEFAULT")) # this is a modified/simplified version of ._test_subsections self.assertEqual(cfg.home, 'mydata') self.assertEqual(cfg.processes, '4') self.assertEqual(cfg.force, 'True') self.assertEqual(cfg.mymodule.force, 'False') self.assertEqual(cfg.extra, "foo, bar") self.assertEqual(cfg.mymodule.extra, "foo, baz") with self.assertRaises(AttributeError): cfg.expires self.assertEqual(cfg.mymodule.expires, "2014-10-15") # this is really unwanted cascading behaviour self.assertEqual(cfg.mymodule.home, 'mydata') self.assertEqual(cfg.mymodule.processes, '4') os.unlink("complex-otherroot.ini") def test_inifile_nonexistent(self): logging.getLogger().setLevel(logging.CRITICAL) cfg = LayeredConfig(INIFile("nonexistent.ini")) self.assertEqual([], list(cfg)) # make sure a nonexistent inifile doesn't interfere with the # rest of the LayeredConfig object defobj = Defaults({'datadir': 'something'}) iniobj = INIFile("nonexistent.ini") cfg = LayeredConfig(defobj, iniobj) self.assertEqual("something", cfg.datadir) # and make sure it's settable (should set up the INIFile # object and affect it, and leave the defaults dict untouched # as it's the lowest priority) cfg.datadir = "else" self.assertEqual("else", cfg.datadir) self.assertEqual("else", iniobj.get("datadir")) self.assertEqual("something", defobj.get("datadir")) # same as above, but with a "empty" INIFile object iniobj = INIFile() cfg = LayeredConfig(defobj, iniobj) self.assertEqual("something", cfg.datadir) cfg.datadir = "else" self.assertEqual("else", cfg.datadir) def test_write(self): cfg = LayeredConfig(INIFile("complex.ini")) cfg.mymodule.expires = date(2014, 10, 24) # calling write for any submodule will force a write of the # entire config file LayeredConfig.write(cfg.mymodule) want = """[__root__] home = mydata processes = 4 force = True extra = foo, bar [mymodule] force = False extra = foo, baz expires = 2014-10-24 [extramodule] unique = True """ with open("complex.ini") as fp: got = fp.read().replace("\r\n", "\n") self.assertEqual(want, got)
def setUp(self): super(TestINIFile, self).setUp() self.simple = INIFile("simple.ini") self.complex = INIFile("complex.ini")
def test_typed_inifile(self): cfg = LayeredConfig(Defaults(self.types), INIFile("complex.ini")) self.supported_types = (str, bool, int, list, date, datetime) self.supports_nesting = False self._test_config_subsections(cfg)
class TestINIFile(TestINIFileHelper, unittest.TestCase, TestConfigSourceHelper): supported_types = (str, ) supports_nesting = False def setUp(self): super(TestINIFile, self).setUp() self.simple = INIFile("simple.ini") self.complex = INIFile("complex.ini") # Overrides of TestHelper.test_get, .test_typed and # .test_subsection_nested due to limitations of INIFile # INIFile carries no typing information def test_get(self): self.assertEqual(self.simple.get("home"), "mydata") self.assertEqual(self.simple.get("processes"), "4") self.assertEqual(self.simple.get("force"), "True") self.assertEqual(self.simple.get("extra"), "foo, bar") self.assertEqual(self.simple.get("expires"), "2014-10-15") self.assertEqual(self.simple.get("lastrun"), "2014-10-15 14:32:07") def test_typed(self): for key in self.simple.keys(): self.assertFalse(self.simple.typed(key)) # Override: INIFile doesn't support nested subsections def test_subsection_nested(self): subsec = self.complex.subsection('mymodule') self.assertEqual(set(subsec.subsections()), set(())) def test_inifile_default_as_root(self): # using a rootsection named DEFAULT triggers different # cascading-like behaviour in configparser. # load a modified version of complex.ini with open("complex.ini") as fp: ini = fp.read() with open("complex-otherroot.ini", "w") as fp: fp.write(ini.replace("[__root__]", "[DEFAULT]")) cfg = LayeredConfig( INIFile("complex-otherroot.ini", rootsection="DEFAULT")) # this is a modified/simplified version of ._test_subsections self.assertEqual(cfg.home, 'mydata') self.assertEqual(cfg.processes, '4') self.assertEqual(cfg.force, 'True') self.assertEqual(cfg.mymodule.force, 'False') self.assertEqual(cfg.extra, "foo, bar") self.assertEqual(cfg.mymodule.extra, "foo, baz") with self.assertRaises(AttributeError): cfg.expires self.assertEqual(cfg.mymodule.expires, "2014-10-15") # this is really unwanted cascading behaviour self.assertEqual(cfg.mymodule.home, 'mydata') self.assertEqual(cfg.mymodule.processes, '4') os.unlink("complex-otherroot.ini") def test_inifile_nonexistent(self): logging.getLogger().setLevel(logging.CRITICAL) cfg = LayeredConfig(INIFile("nonexistent.ini")) self.assertEqual([], list(cfg)) # make sure a nonexistent inifile doesn't interfere with the # rest of the LayeredConfig object defobj = Defaults({'datadir': 'something'}) iniobj = INIFile("nonexistent.ini") cfg = LayeredConfig(defobj, iniobj) self.assertEqual("something", cfg.datadir) # and make sure it's settable (should set up the INIFile # object and affect it, and leave the defaults dict untouched # as it's the lowest priority) cfg.datadir = "else" self.assertEqual("else", cfg.datadir) self.assertEqual("else", iniobj.get("datadir")) self.assertEqual("something", defobj.get("datadir")) # same as above, but with a "empty" INIFile object iniobj = INIFile() cfg = LayeredConfig(defobj, iniobj) self.assertEqual("something", cfg.datadir) cfg.datadir = "else" self.assertEqual("else", cfg.datadir) def test_write(self): cfg = LayeredConfig(INIFile("complex.ini")) cfg.mymodule.expires = date(2014, 10, 24) # calling write for any submodule will force a write of the # entire config file LayeredConfig.write(cfg.mymodule) want = """[__root__] home = mydata processes = 4 force = True extra = foo, bar [mymodule] force = False extra = foo, baz expires = 2014-10-24 [extramodule] unique = True """ with open("complex.ini") as fp: got = fp.read().replace("\r\n", "\n") self.assertEqual(want, got)