9

It seems I have another JSON problem, this time when posting to the REST service. I am using Flask-Restful.

api.add_resource(Records, '/rest/records/<string:email>/<string:password>/<string:last_sync_date>')

parser = reqparse.RequestParser()
parser.add_argument('record_date', type=str)
parser.add_argument('records', type=str)
parser.add_argument('rating', type=str)
parser.add_argument('notes', type=str)

class Records(Resource):
    def post(self, email, password, last_sync_date):
        args = parser.parse_args()
        records = args['records'] # 'records' = None, but why? 
        return records, 201

Unit test:

resource_fields = {
            'record_date': fields.String,
            'rating': fields.Integer,
            'notes': fields.String,
            'last_updated': fields.DateTime,
        }
records = {"records":[]}
records["records"].append(marshal(record1, resource_fields))
    rv = self.app.post('/rest/records/{0}/{1}/{2}'.format(email, password, sync_date), data=json.dumps(records))

json.dumps(records) is:

str: {"records": [{"rating": 1, "notes": null, "last_updated": "Tue, 15 Oct 2013 15:52:44 -0000", "record_date": "2013-10-15 15:52:44.746815"}]}

Why is args['records'] None, where I am clearly sending it over the wire?

UPDATE:

Strange part is when I send a single object, its all dandy. So strange:

record = dict(record_date=record1.record_date, rating=record1.rating, notes=record1.notes, last_updated=record1.last_updated)

rv = self.app.post('/rest/records/{0}/{1}/{2}'.format(email, password, sync_date), data=record)

args:

{'records': None, 'notes': None, 'task': None, 'record_date': '2013-10-15 16:48:40.662744', 'rating': '1'}
Houman
  • 64,245
  • 87
  • 278
  • 460
  • How is `parser` defined? Also inside `def post` what is `args`, i.e. on the line after `records = args['records']` either `log.debug(args)` or `print(args)` – Wayne Werner Oct 15 '13 at 15:13
  • `parser = reqparse.RequestParser()` and `print(args) = Namespace: {'records': None, 'notes': None, 'task': None, 'record_date': None, 'rating': None}` – Houman Oct 15 '13 at 15:19
  • does `from flask import request` and then `print(request.form)` contain different data? Or, from [the docs](http://flask-restful.readthedocs.org/en/latest/reqparse.html) maybe it's `request.values` that you need to look at. – Wayne Werner Oct 15 '13 at 15:23
  • `request.form` says `ImmutableMultiDict: ImmutableMultiDict([])`, `request.values` says `CombinedMultiDict: CombinedMultiDict([ImmutableMultiDict([]), ImmutableMultiDict([])])` – Houman Oct 15 '13 at 15:26
  • Looks like the data is, for some reason, not actually getting posted. At all. You might look at using wireshark or fiddle to see if the data is actually making it off the machine - or try using http://httpbin.org/ – Wayne Werner Oct 15 '13 at 15:29
  • Wayne, please see updated question. – Houman Oct 15 '13 at 15:56
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/39287/discussion-between-wayne-werner-and-kave) – Wayne Werner Oct 15 '13 at 16:33
  • What is the response code from your test. With flask-restless you need to set the content_type = 'application/json' i.e. rv = self.app.post('/rest/records/{0}/{1}/{2}'.format(email, password, sync_date), data=record, content_type = 'application/json') – ejrowley Oct 27 '13 at 20:49

2 Answers2

9

I ended up raising this as an issue on flask-resful github and got this solution, which works for me. Credit goes to Doug Black.

reqparse doesn't really know how to handle JSON. To deal with JSON posts, you'll want to use the flask.request.json dict.

Here's an updated example for what you probably want:

from flask import Flask, request
from flask.ext import restful

class Records(restful.Resource):
    def post(self, email, password, last_sync_date):
        records = request.json['records']
        return records, 201

app = Flask(__name__)
api = restful.Api(app)
api.add_resource(
    Records, 
    '/rest/records/<string:email>/<string:password>/<string:last_sync_date>'
)

The docs on request.json are here.

You'll need to make sure you post with the content type header set to application/json so flask knows to populate the json dictionary.

self.app.post(
    '/rest/records/{0}/{1}/{2}'.format(email, password, sync_date), 
    data=json.dumps(records), 
    headers={'Content-Type': 'application/json'
)
Houman
  • 64,245
  • 87
  • 278
  • 460
  • Yes, this works, the unpleasant thing is that you need to check that the json dict is not `None`. – kralyk Jan 12 '16 at 15:03
-2
class Records(Resource):
    parser = reqparse.RequestParser()
    parser.add_argument('record_date', type=str)
    parser.add_argument('records', type=str)
    parser.add_argument('rating', type=str)
    parser.add_argument('notes', type=str)

    def post(self):
        args = parser.parse_args()
        records = args['records']
        return records, 201

I hope it works.

Liam
  • 18
  • 3