"""
-Lazy conditional string evaluation module for Moe configuration variables.
+Module for managing and evaluation of Moe configuration variables.
-* Each variable has ordered list of operations (definitions), each defining operation either
- assigns (SET) or appends (APPEND) value of an expression to the variable. Each operation may be guarded by condition(s).
-
-* Each condition is a formula (tree consisting of 'AND', 'OR', 'NOT' and '==', '!=' between two expressions.
-
-* Expression is a list of strings and variables to be expanded.
-
-.. note:: If no 'SET' applies, a variable is still undefined even if some 'APPEND' applies. This might change.
-.. note:: All expanded data should be (or is converted to) unicode
.. todo:: (OPT) Cleanup of unused undefined variables.
.. todo:: (OPT) Better variable name checking (no name '.'-structural prefix of another)
.. todo:: (OPT) Implemet "subtree" listing.
class ConfigError(MoeError):
+ "Base class for moe.config errors"
pass
class UndefinedError(ConfigError):
+ "Raised when no **SET** operation applies to evaluated variable."
pass
class VariableNameError(ConfigError):
+ "Raised on invalid config variable name."
pass
class VariableFixedError(ConfigError):
+ "Raised when modifying a fixed variable"
pass
class CyclicConfigError(ConfigError):
+ "Raised when evaluation recursion is too deep"
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):
"""
- Configuration tree containing all the variables.
-
- The variables in `self.variables` are referenced directly by the full name.
+ Configuration environment containing the variables.
"""
def __init__(self):
if isinstance(keys, types.StringTypes):
keys = [keys]
for key in keys:
- self.lookup(key, create=True).fix()
+ self.lookup(key, create=False).fix()
+
+ def unfix(self, keys):
+ "Unfix value of variable or list of variables. Unfixing undefined variable raises `UndefinedError`."
+ if isinstance(keys, types.StringTypes):
+ keys = [keys]
+ for key in keys:
+ self.lookup(key, create=False).unfix()
- def parse(self, s, source=None, level=0):
- """Parse `s` (stream/string) into the tree, see `moe.confparser.ConfigParser` for details."""
+ def remove(self, parsed):
+ """Given a list [(varname, `Operation`)] as returned by `parse` or `parse_file`,
+ removes the operations from the respective variables config tree.
+ Variables/operations not present int the tree raise ValueError.
+ """
+ for vname, o in parsed:
+ v = self.lookup(vname, create = True)
+ v.remove_operation(o)
+
+ 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)
- 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 + ">"
- self.parse(f, source=filename, level=level)
+ return self.parse(f, source=filename, level=level, proxy=proxy)
class ConfigElem(object):
"""
- Base class for cahed config elements - variables and conditions
+ Base class for cached config elements - variables and conditions
"""
def __init__(self, name):
class ConfigCondition(ConfigElem):
"""
Condition using equality and logic operators.
- Clause is a tuple-tree in the following recursive form::
+ Formula is a tuple-tree in the following recursive form::
('AND', c1, c1), ('OR', c1, c2), ('NOT', c1), ('==', e1, e2), ('!=', e1, e2)
- where e1, e2 are `ConfigExpression`, c1, c2, `ConfigCondition`.
+ where ``e1``, ``e2`` are :class:`ConfigExpression`, ``c1``, ``c2``, :class:`ConfigCondition`.
"""
def __init__(self, formula, text=None, parent=None):
class Operation(object):
- "Helper class for operation data. Must not be present in more variables or present multiple times."
+ """
+ Helper class for operation data. Must be present at most once in at most one variable.
+
+ ``operation`` is either ``"SET"`` or ``"APPEND"``, ``condition`` is a :class:`ConfigCondition` instance or ``None``,
+ ``expression`` is a :class:`ConfigExpression` instance, ``level`` is the priority of the operation and ``source``
+ is an informative string describing the operation origin.
+ """
def __init__(self, operation, condition, expression, level=0, source='?'):
- # operation is currently 'SET' and 'APPEND'
self.operation = operation
self.condition = condition
self.expression = expression
class ConfigVar(ConfigElem):
+ "Class representing a single configuration variable"
def __init__(self, name):
super(ConfigVar, self).__init__(name)
self.fixed_val = None
def variables(self):
- "Return a set of variables used in the expressions"
+ "Return a set of variables used in the expressions of the operations"
if not self.operations:
return set([])
return set.union(*[ op.expression.variables() for op in self.operations ])