Python: post JSON data containing a datetime object with requests to flask



Spoiler: My main point in this post is not given away by the title. But first things first: What are all those words? * *Flask* is a Microframework for web applications often used for REST(-like) APIs. * *requests* is simply the best library to make HTTP requests there is. * *JSON* (JavaScript Object Notation) is a file format I prefer to XML in almost all of the case. * And finally *POST* is a HTTP verb often used in the context of REST to create new entry in a database. In Flask one can simply call request.get_json() if POSTed content is encoded in JSON. On the client side of things, one can query such a POST endpoint for example with the following shell command:
curl -X POST https://example.com/ -d @some-file.json --header 'Content-Type: application/json'
The requests library has the post function which accepts data through the json kwarg. The library then takes care of a few things (like setting the content type for example):
some_data = {'foo': 123}
requests.post('https://example.com/api', json=some_data)
All dandy so far. But things break when the some_data contains a datetime.datetime object. You will get the well-googled exception TypeError: datetime.datetime(1985, 1, 26, 0, 0, 0) is not JSON serializable. The behavior is exactly the same as if you would call json.dumps on such data. Of course there is a way to tell dumps how to encode this in a sane way (copying & pasting from StackOverflow here):
from datetime import datetime
import json

class DateTimeEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, datetime):
            return o.isoformat()
        return json.JSONEncoder.default(self, o)

json.dumps({'foo': datetime(1985, 1, 26, 0, 0, 0)}, cls=DateTimeEncoder)
outputs {"foo": "1985-01-26T00:00:00"} which looks perfect. The naive "enterprise software engineering" way would now of course be to make requests.post accept a similar argument as cls and pass that to the json.dumps call that probably is buried somewhere deep in requests. There also is an issue on github requesting such a feature[1]. But the developer of the library politely asked the OP to dumps himself. And I really like that! It saves the requests library from bloating away and I strongly think more developers should reason like this more often. All right. You got it. That was the point. You can go now. Oh you landed here because of a google search for the title? Here you go:
import json
import requests

class DateTimeEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, datetime):
            return o.isoformat()

        return json.JSONEncoder.default(self, o)


r = requests.post(
    'https://example.com/api',
    data=json.dumps(out, cls=DateTimeEncoder),
    headers={'Content-type': 'application/json'}
)
  1. https://github.com/requests/requests/issues/3947 [back]

Tags: - - -

Leave a Reply

Your email address will not be published. Required fields are marked *