Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
138 lines (106 sloc) 3.59 KB
layout title
default
Printing Objects in Flask

Recently I needed to painlessly enumerate a (simple) Python object's properties in HTML to make a web page. I was creating different types of objects and I just wanted an easy way to print them. This post is how to do that in Flask.

What is a "(simple) Python object"

What I'm calling a simple Python object is an instance of a class or namedtuple that only has primitive types (I only needed to test for ints, strs), or primitive collections of primitive types (lists or dicts of ints, strs) as properties. Take the following class for example.

class SimplePythonObject:
    def __init__(self, a: int, b_list=None):
        self.a = a
        self.b_list = b_list or ['b1', 'b2']

my_spo = SimplePythonObject(1)

For the purposes of this post, it's a simple Python object because it's properties are a of type int and b_list of type List[str]. Of course, because of Python's dynamic typing, actually guaranteeing this might be more trouble than it's worth, but I can guarantee it for my code at least.

Getting the properties of a class instance

I'm going to use the vars function to get all the properties as a dict. Unfortunately, this function won't work for namedtuples (they use <name>._asdict()) so I'm going to use a small wrapper function that will work for both simple classes and namedtuples.

def todict(obj):
    """Convert both objects and namedtuples to dicts"""
    if hasattr(obj, '_asdict'):
        return obj._asdict()
    return vars(obj)

How to use this in Jinja? I could turn my simple classes into dicts before passing them to render_template like so:

    ...
    my_spo = SimplePythonObject(1)
    return render_template(todict(my_spo), ...)

But that's annoying and I'd rather do it on the template side, so I told Jinja2 about it:

# Give Jinja the ability to turn things into dicts
# This will let us print objects easily
# https://stackoverflow.com/a/7226047/2958070
app.jinja_env.globals.update(todict=todict)

Now I don't have to babysit my render_template function:

    ...
    my_spo = SimplePythonObject(1)
    return render_template(my_spo, ...)

Rendering the object

Now it's fairly easy to define a macro in Jinja2 to print the object:

{% raw %}
<!-- This needs todict(obj) to turn both classes and namedtuples into dicts -->
<!-- not recursive- I don't think I need recursion for my simple objects -->
{% macro render_object(obj) %}
  {% for key, value in todict(obj).items() %}
    <h3> {{ key }} </h3>
    <ul>
    {% if value is mapping %}
      {% for child_key, child_value in value.items() %}
        <li> {{ child_key }} : {{ child_value }}</li>
      {% endfor %}
    {% elif value is iterable and value is not string %}
      {% for child_value in value %}
        <li>{{ child_value }}</li>
      {% endfor %}
    {% else %}
      <li>{{ value }}</li>
    {% endif %}
    </ul>
  {% endfor %}
{% endmacro %}
{% endraw %}

I put this macro in a helper file: templates/_macros.html, and then reference it everywhere else:

{% raw %}
{% from "_macros.html" import render_object %}
<h2>My Simple Object</h2>
{{ render_object(my_spo) }}
{% endraw %}

Which produces the following HTML:

    <h3> a </h3>
    <ul>

      <li>1</li>

    </ul>

    <h3> b_list </h3>
    <ul>


        <li>b1</li>

        <li>b2</li>


    </ul>

This is a sorta-ugly and brittle way to print objects, but it'll work until I get some good output and it's saved me some time.