Search code examples
pythonpygal

Configure a pygal chart with an external conf file?


I'm using pygal to chart some data in a web app I'm working on and thought it would be a good idea to externalize the configuration of the chart.

So I wrote a section in my conf file that replicated my in code conf:

[ChartOptions]
x_label_rotation: -75
x_labels_major_every: 5
show_minor_x_labels: False
range: (30,100)
stroke_style: {'width':8}
title: Chart Title

and found that passing the ChartOptions section to (for instance) pygal.Config() resulted in

  File "/usr/local/lib/python2.7/dist-packages/pygal/util.py", line 370, in mergextend
    if list1 is None or _ellipsis not in list1:

How can I do this?


Solution

  • I'm fairly new at Python, so maybe this is all unnecessary, is well known or there exists a better way. I had a bunch of trouble and couldn't find anything, so here we are.

    The first thing I worked out is that pygal.util.mergextend() does not like to find strings where it's expecting other datatypes. The values in the OrderedDict returned from ConfigParser.read()._sections[your_section_here] are all strings, so they need to be converted to their proper type.

    Enter: ast.literal_eval().

    This seemed like it would work, but kept raising a ValueError('malformed string ') on the __name__ value, which was a str, per type(options['__name__']). Well, now what?

    I didn't really need the __name__ value, so I used pop() to remove it from the dictionary, which left dealing with the title value. I wanted to use title, knew could be a string per pygal and had control over the value of it, so what can be done?

    The docs for ast.literal_eval() insist it allows strings, so adding quotes to the title value, in the conf file seemed 'reasonable', and worked.

    Putting all that together, and adding flask to the mix, we get:

    conf file:

    ...
    [ChartOptions]
    x_label_rotation: -75
    x_labels_major_every: 5
    show_minor_x_labels: False
    range: (30,100)
    stroke_style: {'width':8}
    # note added quotes below
    title: "Chart Title"
    ...
    

    app.py:

    import ConfigParser
    import ast
    import pygal
    from pygal import Config
    from flask import Flask, render_template
    ...
    config = ConfigParser.ConfigParser()
    config.read('my.conf')
    chart_options = config._sections[CHART_SECTION]
    chart_options.pop('__name__')
    for key in chart_options.keys():
        chart_options[key] = ast.literal_eval(chart_options[key])
    
    @app.route('/route')
    def a_route():
        global chart_options # haven't made this into a class yet...
        chart_config = Config(**chart_options)
    
        chart = pygal.Line(chart_config)
        ...
        # add data and finalize chart setup
        ...
        return render_template('route.html', chart=chart.render())
    ...