What's the best way to read a condition from a config file in Python using ConfigParser
and json
? I want to read something like:
[mysettings]
x >= 10
y < 5
and then apply it to code where x
and y
are defined variables, and the condition will be applied as to the values of x, y
in the code. Something like:
l = get_lambda(settings["mysettings"][0])
if l(x):
# do something
pass
l2 = get_lambda(settings["mysettings"][1])
if l2(y):
# do something
pass
ideally I'd like to specify conditions like x + y >= 6
too.
there must be a better way, but the idea is to constrain the values of variables using simple boolean expressions from the config file.
I don't think you want—or need—to use both configparser
and json
, as both are sufficient by itself. Here's how to do it with each one:
Say you had a config file from a trusted source that contained something like this:
myconfig.ini
[mysettings]
other=stuff
conds=
x >= 10
y < 5
x + y >= 6
It could parsed and used like this:
from __future__ import print_function
try:
import configparser
except ImportError: # Python 2
import ConfigParser as configparser
get_lambda = lambda expr: lambda **kwargs: bool(eval(expr, kwargs))
cp = configparser.ConfigParser()
cp.read('myconfig.ini')
exprs = cp.get('mysettings', 'conds').strip()
conds = [expr for expr in exprs.split('\n')]
l = get_lambda(conds[0])
l2 = get_lambda(conds[1])
l3 = get_lambda(conds[2])
def formatted(l, c, **d):
return '{:^14} : {:>10} -> {}'.format(
', '.join('{} = {}'.format(var, val) for var, val in sorted(d.items())), c, l(**d))
l = get_lambda(conds[0])
print('l(x=42): {}'.format(l(x=42)))
print()
print(formatted(l, conds[0], x=42))
print(formatted(l2, conds[1], y=6))
print(formatted(l3, conds[2], x=3, y=4))
Which would result in the following output:
l(x=42): True
x = 42 : x >= 10 -> True
y = 6 : y < 5 -> False
x = 3, y = 4 : x + y >= 6 -> True
If the information the was instead kept in a JSON-format file similar to this:
myconfig.json
{
"mysettings": {
"other": "stuff",
"conds": [
"x >= 10",
"y < 5",
"x + y >= 6"
]
}
}
It could be easily parsed with the json
module and used in a similar fashion:
import json
with open('myconfig.json') as file:
settings = json.loads(file.read())
conds = settings['mysettings']['conds']
...the remainder would be the identical and produce the same results. i.e.:
l = get_lambda(conds[0])
print('l(x=42): {}'.format(l(x=42)))
print()
print(formatted(l, conds[0], x=42))
print(formatted(l2, conds[1], y=6))
print(formatted(l3, conds[2], x=3, y=4))
This is an example using Python itself as the language for describing the config file:
config.py
mysettings = [
lambda x: x >= 10,
lambda y: y < 5,
]
main.py
from config import mysettings
a = 42
b = 300
for i, condition in enumerate(mysettings):
for value in (a, b):
result = condition(value)
print "condition %s for value %s is: %s" % (i, value, result)
Output:
condition 0 for value 42 is: True
condition 0 for value 300 is: True
condition 1 for value 42 is: False
condition 1 for value 300 is: False
This of course assumes that the config file is considered trusted input, because by doing condition(value)
you'll execute whatever function is defined in the config file.
But I don't see any way around that, regardless of what language you're using: conditions are expressions and therefore executable code. If you want to end up with a Python expression that you can just use in your code, you'll have to evaluate that expression sooner or later.
Edit:
If for some reason you really can't use Python, this is how you could do it with a config file in JSON:
config.json
{
"mysettings": {
"color": "Blue",
"expressions": [
"x >= 10",
"y < 5"
]
},
"other_settings": {
"color": "red"
}
}
main.py
import json
x = 42
y = 300
def eval_expr(expr, values):
result = eval(expr, values.copy())
print "The expression '%s' evaluates to '%s' for the values %r" % (
expr, result, values)
return result
f = open('config.json')
data = json.loads(f.read())
settings = data["mysettings"]
for expr in settings['expressions']:
values = dict(x=x, y=y)
eval_expr(expr, values)
Result:
The expression 'x >= 10' evaluates to 'True' for the values {'y': 300, 'x': 42}
The expression 'y < 5' evaluates to 'False' for the values {'y': 300, 'x': 42}
Or, closer to your example:
x = 1
y = 2
values = dict(x=x, y=y)
e1 = settings['expressions'][0]
if eval_expr(e1, values):
# do something
pass
e2 = settings['expressions'][1]
if eval_expr(e2, values):
# do something else
pass
Result:
The expression 'x >= 10' evaluates to 'False' for the values {'y': 2, 'x': 1}
The expression 'y < 5' evaluates to 'True' for the values {'y': 2, 'x': 1}