X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;f=t%2Fmoe%2Fconfig.py;h=ec057e1cc3bb9edeb347a0ca9fe348fe684e3cfe;hb=ef64f1097b0b3d210ee24f08e91c352865cac59c;hp=bcef93be2cb841e394bc496abeb687078275b1aa;hpb=750990d7dbbaf2740f290a26dab527ce4444cbfb;p=moe.git diff --git a/t/moe/config.py b/t/moe/config.py index bcef93b..ec057e1 100644 --- a/t/moe/config.py +++ b/t/moe/config.py @@ -1,15 +1,6 @@ """ -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. @@ -41,26 +32,41 @@ def check_depth(depth): 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): @@ -94,24 +100,57 @@ class ConfigTree(object): self.variables[k].dump(prefix) for k in sorted(self.variables.keys()) ]) - def parse(self, s, source=None, level=0): - """Parse `s` (stream/string) into the tree, see `moe.confparser.ConfigParser` for details.""" - import moe.confparser - p = moe.confparser.ConfigParser(text, self, source=source, level=level) - p.parse() - - def parse_file(self, filename, desc=None, level=0): - """Parse an utf-8 file into the tree, see `moe.confparser.ConfigParser` for details. + def fix(self, keys): + "Fix value of variable or list of variables. Fixing undefined variable raises `UndefinedError`." + if isinstance(keys, types.StringTypes): + keys = [keys] + for key in keys: + 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 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) + l = p.parse() + if not proxy: + return l + return ParseProxy(self, l) + + 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`>". """ - f = open(filename, 'rt') - if desc: - filename += " <" + desc + ">" - self.parse(f, source=filename, level=level) + with open(filename, 'rt') as f: + if desc: + filename += " <" + desc + ">" + 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): @@ -152,11 +191,11 @@ class ConfigElem(object): 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): @@ -234,10 +273,15 @@ class ConfigCondition(ConfigElem): 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 @@ -250,6 +294,7 @@ class Operation(object): class ConfigVar(ConfigElem): + "Class representing a single configuration variable" def __init__(self, name): super(ConfigVar, self).__init__(name) @@ -260,8 +305,10 @@ class ConfigVar(ConfigElem): self.fixed_val = None def variables(self): - "Return a set of variables used in the expressions" - return set(sum([ list(op.expression.variables()) for op in self.operations ], [])) + "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 ]) def fix(self): """ @@ -274,14 +321,14 @@ class ConfigVar(ConfigElem): self.fixed = True def unfix(self): - "Set the variable to be modifiable again." + "Make the variable modifiable again." self.fixed = False def value(self, depth=0): "Handle the case when fixed, raise exc. on different evaluation" val = super(ConfigVar,self).value(depth) if self.fixed and self.fixed_val != val: - raise VariableFixedError("value of var %s was fixed to %r but evaluated to %r", self.name, self.fixed_val, val) + raise VariableFixedError("value of var %r was fixed to %r but evaluated to %r", self.name, self.fixed_val, val) return val def add_operation(self, operation): @@ -311,7 +358,7 @@ class ConfigVar(ConfigElem): self.invalidate() # Remove the operation self.operations.remove(operation) - # Remove dependencies on variables unused in other operations + # Remove dependencies on variables unused in other defining operations vs = self.variables() for v in operation.expression.variables(): if v not in vs: @@ -376,8 +423,8 @@ class ConfigExpression(object): self.exprlist[i] = unicode(e, 'ascii') def variables(self): - "Return an iterator of variables user in the expression" - return itertools.ifilter(lambda e: isinstance(e, ConfigVar), self.exprlist) + "Return a set of variables used in the expression" + return set([e for e in self.exprlist if isinstance(e, ConfigVar)]) def __str__(self): return self.original