class CyclicConfigError(ConfigError):
pass
+class ParseProxy(list):
+ """Proxy helper class around values returned by `parse` and `parse_file`,
+ useful in "with" constructs."""
+ def __init__(self, config, parsed_ops):
+ super(ParseProxy, self).__init__(parsed_ops)
+ self.config = config
+ def __enter__(self):
+ pass
+ def __exit__(self, etype, value, traceback):
+ self.config.remove(list(self))
+
class ConfigTree(object):
"""
v = self.lookup(vname, create = True)
v.remove_operation(o)
- def parse(self, s, source=None, level=0):
- """Parse `s` (stream/string) into the tree, see `moe.confparser.ConfigParser` for details.
- Returns list of parset operations: [(varname, `Operation`)]"""
+ def parse(self, s, source=None, level=0, proxy=True):
+ """Parse `s` (stream/string) into the tree, see `moe.config_parser.ConfigParser` for details.
+ Returns list of parset operations: [(varname, `Operation`)].
+ By default returns a proxy list-like object that can be used in "with" constructs:
+
+ with config.parse("TEST='1'"):
+ print config['TEST']
+ raise StupidError
+ """
import moe.config_parser
p = moe.config_parser.ConfigParser(s, self, source=source, level=level)
- return p.parse()
+ l = p.parse()
+ if not proxy:
+ return l
+ return ParseProxy(self, l)
- def parse_file(self, filename, desc=None, level=0):
- """Parse an utf-8 file into the tree, see `moe.confparser.ConfigParser` for details.
+ def parse_file(self, filename, desc=None, level=0, proxy=True):
+ """Parse an utf-8 file into the tree using func:`parse`.
Names the source "`filename` <`desc`>". """
with open(filename, 'rt') as f:
if desc:
filename += " <" + desc + ">"
- return self.parse(f, source=filename, level=level)
+ return self.parse(f, source=filename, level=level, proxy=proxy)
class ConfigElem(object):
s.t = cf.ConfigTree()
def parse(s, string, level=0, fname='test'):
- cp = ConfigParser(string, s.t, fname, level)
- ops = cp.parse()
- cp.p_WS()
- assert cp.eof()
- return ops
+ return s.t.parse(string, source=fname, level=level)
def var(s, varname, create=True):
return s.t.lookup(varname, create=create)
s.assertRaises(ConfigSyntaxError, s.parse, "if 'a'<>'b' {}")
def test_parse_remove(s):
- s.parse('a="000"')
+ def raise_UserWarning():
+ with s.parse("b='F'", level=99):
+ assert s.val('b') == 'F'
+ raise UserWarning
+ d0 = s.parse('a="000"')
d1 = s.parse("a='A'; b='B'; c='C'", level=10)
d2 = s.parse('c="{a}{a}"; b="XX" ', level=20)
d3 = s.parse('b+=c ', level=30)
assert s.val('b') == "XXAA"
s.t.remove(d2)
assert s.val('b') == "BC"
- s.assertRaises(ValueError, s.t.remove, [('d', d1[1][0])])
- s.assertRaises(ValueError, s.t.remove, [('b', d1[1][0])])
+ s.assertRaises(ValueError, s.t.remove, [('d', d1[0][1])])
+ s.assertRaises(ValueError, s.t.remove, [('b', d1[0][1])])
+ # Try exception in "with parse():"
+ s.assertRaises(UserWarning, raise_UserWarning)
+ assert s.val('b') == "BC"
# partially remove d1
s.t.remove([('c', d1[2][1])])
# try to remove rest - 'a' and 'b' should get removed
s.assertRaises(ValueError, s.t.remove, d1)
assert s.val('a') == "000"
+ # cleanup
s.t.remove(d3)
+ s.t.remove(d0)
+ for v in 'abcd':
+ assert len(s.var(v).operations) == 0
class TestConfigEval(TestConfig):