I have this configuration file where I have statistics: number of iterations and a time delta object. I am trying to find a pythonic and safe way of evaluating these values. So the config file looks like this (test_config.cfg):
[Section]
option1 = (6, datetime.timedelta(0, 9, 520000))
option2 = (4, datetime.timedelta(0, 8, 510000))
That entry time.timedelta()
gets there when I use RawConfigParser.set('Section', 'option', (i, t_delta))
where t_delta
would simply be the time span between two operations and i
is the number of iterations. When reading this values back from the config they are returned as strings. I want to evaluate them for what they were initially. What I have tried and works but I feel there has to be a safer, respectivelly pythonic way of doing it:
import ConfigParser
import datetime
config = ConfigParser.RawConfigParser()
config.read('test_config.cfg')
stats = config.get('Section', 'option1')
# The obvious way is with eval() but makes me very uncomfortable using it
iterations = eval(stats)[0] # 6
duration = eval(stats)[1] # 0:00:09.520000
# The ugly way is with stripping and splitting
duration_tuple = tuple(int(i) for i in stats.split('(')[-1].strip(')').split(','))
duration = datetime.timedelta(*duration_tuple)
iterations = int(stats.split(',')[0].lstrip('('))
print iterations # 6
print duration # 0:00:09.520000
So is there a better way? Eventually is there a way of having only the 'tuple' used by the timedelta object when setting to the config? Like (0, 9, 520000)
instead of datetime.timedelta(0, 9, 520000)
. In this way I could easily use ast.literal_eval()
. Thanks!
You can convert timedelta to seconds (expressed as a float so that partial seconds are not lost).
RawConfigParser.set('Section', 'option1', '%s,%s' % (i, t_delta.total_seconds()))
Now your section looks like:
[Section]
option1 = (6, 9.520000)
option2 = (4, 8.510000)
And you can skip using eval:
stats = RawConfigParser.get('Section', 'option1').split(',')
iterations = int(stats[0]) # 6
duration = datetime.timedelta(seconds=float(stats[1])) # 0:00:09.520000
(edit)
Here's a worked example that uses ast.literal_eval for one option and split/cast for another option. literal_eval is a bit more risky because a bad actor can run more code than you want, but is still reasonable. The split/cast method is much more strict about what input it will accept:
>>> from ConfigParser import RawConfigParser
>>> import datetime
>>> import ast
>>>
>>> i = 6
>>> t_delta = datetime.timedelta(0, 9, 520000)
>>>
>>> config = RawConfigParser()
>>> config.add_section('Section')
>>> config.set('Section', 'option_for_eval', (i, t_delta.total_seconds()))
>>> config.set('Section', 'option_for_cast', '%s,%f' % (i, t_delta.total_seconds()))
>>> config.write(open('/tmp/config.ini', 'w'))
>>>
>>> config = RawConfigParser()
>>> config.read('/tmp/config.ini')
['/tmp/config.ini']
>>> option_for_eval = config.get('Section', 'option_for_eval')
>>> option_for_eval
'(6, 9.52)'
>>> i, t_delta = ast.literal_eval(option_for_eval)
>>> i, t_delta
(6, 9.52)
>>>
>>> option_for_cast = config.get('Section', 'option_for_cast')
>>> stat = option_for_cast.split(',')
>>> i = int(stat[0])
>>> t_delta = datetime.timedelta(seconds=float(stat[1]))
>>> i, t_delta
(6, datetime.timedelta(0, 9, 520000))
>>>