Flask returning namedtuples as dicts, but not all the time

Viewed 389

I have a very small Flask app that looks very much like this:

Point = namedtuple('Point', ['lat', 'lng', 'alt'])

p1 = Point(38.897741, -77.036450, 20)


def create_app():
    app = flask.Flask(__name__)

    @app.route('/position')
    def position():
        return flask.jsonify({
            'vehicle': p1,
        })

    return app

It exists only to feed position data to a web ui. I was expecting that the Point namedtuple would be rendered as a JSON array, but to my surprise I was getting:

{
  "vehicle": {
    "alt": 20, 
    "lat": 38.897741, 
    "lng": -77.03645
  }
}

...which, you know, that's fine. I can work with that. But then I was writing some unit tests, which look something like this:

from unittest import TestCase
import json
import tupletest

class TupleTestTest(TestCase):
    def setUp(self):
        _app = tupletest.create_app()
        _app.config['TESTING'] = True
        self.app = _app.test_client()

    def test_position(self):
        rv = self.app.get('/position')
        assert rv.status_code == 200
        assert rv.mimetype == 'application/json'
        data = json.loads(rv.get_data())
        assert data['vehicle']['lat'] = 38.897741

...and they failed, because suddenly I wasn't get dictionaries:

>       assert data['vehicle']['lat'] == 38.897741
E       TypeError: list indices must be integers, not str

And indeed, if in the test I wrote the return value out to a file I had:

{
  "vehicle": [
    38.897741,
    -77.03645,
    20
  ]
}

What.

What is going on here? I can't even reproduce this for the purposes of this question; the unit test above renders dictionaries. As does my actual webapp, when it is running, but not when it's being tested. But on another system I appear to be getting arrays from the actual app.

1 Answers

Looking at the sourcecode, this is in the flask's jsonify.py:

# Use the same json implementation as itsdangerous on which we
# depend anyways.
from itsdangerous import json as _json

and in the itstangerous.py there is:

try:
    import simplejson as json
except ImportError:
    import json

The simplejson library has an option namedtuple_as_object which is enabled by default.


So when the 3rd party simplejson is installed, the app uses it and serializes a namedtuple to a JSON object (a dict in Python).

On systems where that library is not installed, the app falls back to standard json and serializes a namedtuple to an array (list).

But if the simplejson is installed and imported by flask, the test program imports directly the standard json overwriting it and thus changing the behaviour between running and testing.

Related