On Wed, 9 Mar 2016 03:18:49 -0600 "Karl O. Pinc" <k...@meme.com> wrote:
> On Tue, 8 Mar 2016 20:26:36 -0800 > Mike Orr <sluggos...@gmail.com> wrote: > > > On Tue, Mar 8, 2016 at 4:22 PM, Michael Merickel > > <mmeri...@gmail.com> wrote: > > > > I think with the advent of montague by Joe Rosenbaugh as well as > > > 12-factor app design I know, personally, that I'm ready to start > > > having a conversation about what to do in this space. > > Still, it would be nice to have something cleaner than INI and > > PasteDeploy in Pyramid's default, but I also don't know what would > > be a good candidate for that. > > Raw python literal expressions parsed with ast.literal_eval(), > read from a file. > Sounds fun, but my python-fu is not strong. Here's > a horrible example: Appended is a better proof-of-concept. It extends the original by allowing tuple values. I thought maybe montague would do this, but I don't see it. -----------------------<snip>------------------ '''Support the writing of configuration files in Python syntax. Allow a configuration file to be written in Python syntax, as a Python dict containing literal values. Each key/value pair gives a name, the key, to a configuration value. Values may themselves be dicts, allowing for arbitrary levels of nested sections of configuration. Using the configuration file consists of calling `parse_config()` on it's content to produce a dict. Then, typically, referencing the dict's values by keyword. ''' import ast class PyConfError(Exception): def __init__(self, msg, node, conf_str): self.value = ( 'Error parsing config: line {}: column {}: {}\n{}\n{}' .format(node.lineno, node.col_offset, msg, _get_line(conf_str, node.lineno), _col_pointer(node.col_offset))) def __str__(self): return self.value def _get_line(conf_str, lineno): '''Return the (1-based) line number from the conf string.''' return conf_str.split('\n')[lineno - 1] def _col_pointer(col): '''Return a string pointing to the error location.''' return '{{:>{col}}}'.format(col=col + 1).format('^') def _complex_value(conf_str, node): '''Node must be a tuple or a singleton literal.''' if isinstance(node, ast.Tuple): for fieldname, values in ast.iter_fields(node): if fieldname == 'elts': for child in values: _complex_value(conf_str, child) else: _singleton(conf_str, node) def _key_value(node): '''Is the node suitable as a dict key?''' return (isinstance(node, ast.Num) or isinstance(node, ast.Str)) def _singleton(conf_str, node): '''Node must be a literal that's not a container.''' if (not _key_value(node) and not isinstance(node, ast.NameConstant)): raise PyConfError('Not a literal', node, conf_str) def _validate_config(conf_str, st): '''Allow only nested dicts, strings, numbers, and booleans in configs.''' if isinstance(st, ast.Dict): for fieldname, values in ast.iter_fields(st): if fieldname == 'keys': for node in values: if not _key_value(node): raise PyConfError( 'Not a valid key value', node, conf_str) else: for node in values: _validate_config(conf_str, node) else: _complex_value(conf_str, st) def parse_config(conf_str): '''Parse a config string, returning a python dict. The `conf_str` is a python expression, subject to the following restrictions: * It must evaluate to a single object, a dict, and be written as a literal. * Dict values must be written as literals. They may be dicts, tuples, numbers, strings, or literal constants such a True, False, None, etc. * Dict keys must be written as literals. They may be numbers or strings. Comments, indentation, etc., are allowed. So is arbitrary levels of nesting. What is not allowed is any sort of evaluation. Example:: # Sample configuration file { # A configuration dict may contain: 'alpha': 1, # numeric literals 'beta' : 'b', # string literals 'gamma': (1, 2, 3 , # tuple literals (4, 5) # tuples may be nested ), 'omega': { # nested dict literals 1 : 'first' # a numeric dict key 'a' : 'second' # a string dict key } } ''' st = ast.parse(conf_str) if not isinstance(st, ast.Module): raise PyConfError('not a Module', st, conf_str) mod_children = [node for node in ast.iter_child_nodes(st)] if len(mod_children) != 1: raise PyConfError('unknown Module content', st, conf_str) mod_child = mod_children[0] if not isinstance(mod_child, ast.Expr): raise PyConfError( 'Module does not contain Expr', mod_child, conf_str) expr_children = [node for node in ast.iter_child_nodes(mod_child)] if len(expr_children) != 1: raise PyConfError('unknown Expr content', mod_child, conf_str) expr_child = expr_children[0] if not isinstance(expr_child, ast.Dict): raise PyConfError('Config is not a dict', expr_child, conf_str) _validate_config(conf_str, expr_child) return ast.literal_eval(expr_child) -----------------------<snip>------------------ Karl <k...@meme.com> Free Software: "You don't pay back, you pay forward." -- Robert A. Heinlein -- You received this message because you are subscribed to the Google Groups "pylons-devel" group. To unsubscribe from this group and stop receiving emails from it, send an email to pylons-devel+unsubscr...@googlegroups.com. To post to this group, send email to pylons-devel@googlegroups.com. Visit this group at https://groups.google.com/group/pylons-devel. For more options, visit https://groups.google.com/d/optout.
#!/usr/bin/env python3 '''Support the writing of configuration files in Python syntax. Allow a configuration file to be written in Python syntax, as a Python dict containing literal values. Each key/value pair gives a name, the key, to a configuration value. Values may themselves be dicts, allowing for arbitrary levels of nested sections of configuration. Using the configuration file consists of calling `parse_config()` on it's content to produce a dict. Then, typically, referencing the dict's values by keyword. ''' import ast class PyConfError(Exception): def __init__(self, msg, node, conf_str): self.value = ( 'Error parsing config: line {}: column {}: {}\n{}\n{}' .format(node.lineno, node.col_offset, msg, _get_line(conf_str, node.lineno), _col_pointer(node.col_offset))) def __str__(self): return self.value def _get_line(conf_str, lineno): '''Return the (1-based) line number from the conf string.''' return conf_str.split('\n')[lineno - 1] def _col_pointer(col): '''Return a string pointing to the error location.''' return '{{:>{col}}}'.format(col=col + 1).format('^') def _complex_value(conf_str, node): '''Node must be a tuple or a singleton literal.''' if isinstance(node, ast.Tuple): for fieldname, values in ast.iter_fields(node): if fieldname == 'elts': for child in values: _complex_value(conf_str, child) else: _singleton(conf_str, node) def _key_value(node): '''Is the node suitable as a dict key?''' return (isinstance(node, ast.Num) or isinstance(node, ast.Str)) def _singleton(conf_str, node): '''Node must be a literal that's not a container.''' if (not _key_value(node) and not isinstance(node, ast.NameConstant)): raise PyConfError('Not a literal', node, conf_str) def _validate_config(conf_str, st): '''Allow only nested dicts, strings, numbers, and booleans in configs.''' if isinstance(st, ast.Dict): for fieldname, values in ast.iter_fields(st): if fieldname == 'keys': for node in values: if not _key_value(node): raise PyConfError( 'Not a valid key value', node, conf_str) else: for node in values: _validate_config(conf_str, node) else: _complex_value(conf_str, st) def parse_config(conf_str): '''Parse a config string, returning a python dict. The `conf_str` is a python expression, subject to the following restrictions: * It must evaluate to a single object, a dict, and be written as a literal. * Dict values must be written as literals. They may be dicts, tuples, numbers, strings, or literal constants such a True, False, None, etc. * Dict keys must be written as literals. They may be numbers or strings. Comments, indentation, etc., are allowed. So is arbitrary levels of nesting. What is not allowed is any sort of evaluation. Example:: # Sample configuration file { # A configuration dict may contain: 'alpha': 1, # numeric literals 'beta' : 'b', # string literals 'gamma': (1, 2, 3 , # tuple literals (4, 5) # tuples may be nested ), 'omega': { # nested dict literals 1 : 'first' # a numeric dict key 'a' : 'second' # a string dict key } } ''' st = ast.parse(conf_str) if not isinstance(st, ast.Module): raise PyConfError('not a Module', st, conf_str) mod_children = [node for node in ast.iter_child_nodes(st)] if len(mod_children) != 1: raise PyConfError('unknown Module content', st, conf_str) mod_child = mod_children[0] if not isinstance(mod_child, ast.Expr): raise PyConfError( 'Module does not contain Expr', mod_child, conf_str) expr_children = [node for node in ast.iter_child_nodes(mod_child)] if len(expr_children) != 1: raise PyConfError('unknown Expr content', mod_child, conf_str) expr_child = expr_children[0] if not isinstance(expr_child, ast.Dict): raise PyConfError('Config is not a dict', expr_child, conf_str) _validate_config(conf_str, expr_child) return ast.literal_eval(expr_child) print(parse_config('{"a": 1, "b": 2}')) print(parse_config('{"a": {1: 1}, "b": 2}')) #print(parse_config('{{1: 1}: "a", "b": 2}')) print(parse_config('{"tuple": (1, 2, ("a",))}')) #print(parse_config('{"tuple": (1, 2,\n{"a":1})}')) print(parse_config('{"a": 1, "b": sqrt}'))