7

I have this issue where I am trying to send/receive to a flask API some file and JSON in a single function.

On my client ( sender ) I have :

#my json to be sent 
datas = {'var1' : 'var1','var2'  : 'var2',}
#my file to be sent 
local_file_to_send = 'user_picture.jpg'
url = "http://10.100.2.6:80/customerupdate"
headers = {'Content-type': 'multipart/form-data'}
files = {'document': open(local_file_to_send, 'rb')}
r = requests.post(url, files=files, data=datas, headers=headers)

On my Flask server I have :

class OPERATIONS(Resource):
        @app.route('/',methods=['GET'])
        def hello_world():
            return 'Hello World!'

        @app.route('/customerupdate',methods=['GET','POST'])
        def customerupdate():
            event_data_2 = json.loads(request.get_data().decode('utf-8'))
            print event_data_2

I have this error message telling me that the data is actually not a json format nor a utf8 format. If I print the content of the "get_data" without trying to decode it shows some binary characters..

What would be the syntax on my client to read the json and write the file locally ?

MouIdri
  • 1,300
  • 1
  • 18
  • 37
  • maybe using `request.get_json()` will work – Omar Dec 06 '17 at 16:42
  • @Omar : yes, it gave me the "none" message meaning this is not a json format. – MouIdri Dec 06 '17 at 16:44
  • does your request include the header `Content-Type: application/json`? – Omar Dec 06 '17 at 16:46
  • 1
    regarding this SO thread, it says, you should not : https://stackoverflow.com/questions/19439961/python-requests-post-json-and-file-in-single-request . And Actually I tryed to specifiy "Content-Type: application/json" iinstead of multipart/form-data and it did not worked. The Json was send but not the files. – MouIdri Dec 06 '17 at 16:49

4 Answers4

12

I would recommend sending both the JSON and the file as parts of the multipart form. In that case you would read them from request.files on the server. (One caveat: I tested all my examples with Python 3, requests 2.18.4, and Flask 0.12.2 -- you might need to change the code around to match your environment).

From https://stackoverflow.com/a/35940980/2415176 (and the Flask docs at http://docs.python-requests.org/en/latest/user/advanced/#post-multiple-multipart-encoded-files), you don't need to specify headers or anything. You can just let requests handle it for you:

import json
import requests

# Ton to be sent
datas = {'var1' : 'var1','var2'  : 'var2',}

#my file to be sent
local_file_to_send = 'tmpfile.txt'
with open(local_file_to_send, 'w') as f:
    f.write('I am a file\n')

url = "http://127.0.0.1:3000/customerupdate"

files = [
    ('document', (local_file_to_send, open(local_file_to_send, 'rb'), 'application/octet')),
    ('datas', ('datas', json.dumps(datas), 'application/json')),
]

r = requests.post(url, files=files)
print(str(r.content, 'utf-8'))

Then on the server you can read from request.files (see http://flask.pocoo.org/docs/0.12/api/#flask.Request.files but note that request.files used to work a little differently, see https://stackoverflow.com/a/11817318/2415176):

import json                                                     

from flask import Flask, request                                

app = Flask(__name__)                                           

@app.route('/',methods=['GET'])                                 
def hello_world():                                              
    return 'Hello World!'                                       

@app.route('/customerupdate',methods=['GET','POST'])            
def customerupdate():                                           
    posted_file = str(request.files['document'].read(), 'utf-8')
    posted_data = json.load(request.files['datas'])             
    print(posted_file)                                          
    print(posted_data)                                          
    return '{}\n{}\n'.format(posted_file, posted_data)          
Craig Kelly
  • 3,776
  • 2
  • 18
  • 17
7

Thanks to Craig answer, I found the solution. I will post both code ( client and server ) to help in case of future use. The CLient server is uploading file and Json in the "form" feature of flask. Then some ast and some dict to make the Payload clearer ( I know this is ugly way, but this is the best scholar approach )

On Client side :

datas = {'CurrentMail': "AA", 'STRUserUUID1': "BB", 'FirstName': "ZZ",     'LastName': "ZZ",  'EE': "RR", 'JobRole': "TT"  }

#sending user infos to app server using python "requests"
url = "http://10.100.2.6:80/customerupdate"
def send_request():
    payload = datas
    local_file_to_send = 'user_picture.jpg' 
    files = {
     'json': (None, json.dumps(payload), 'application/json'),
     'file': (os.path.basename(local_file_to_send), open(local_file_to_send, 'rb'), 'application/octet-stream')
    }
    r = requests.post(url, files=files)

send_request()

On Flask Server side :

import sys, os, logging, time, datetime, json, uuid, requests, ast
from flask import Flask, request , render_template
from werkzeug import secure_filename
from werkzeug.datastructures import ImmutableMultiDict
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)
app.debug = True

class OPERATIONS(Resource):
        @app.route('/',methods=['GET'])
        def hello_world():
            return 'Hello World!'

        @app.route('/customerupdate',methods=['GET','POST'])
        def customerupdate():
            print "************DEBUG 1 ***********"
            RequestValues = request.values
            print RequestValues
            print "************DEBUG 2 ***********"
            RequestForm = request.form
            print RequestForm
            print "************DEBUG 2-1 ***********"
            so = RequestForm
            json_of_metadatas = so.to_dict(flat=False)
            print json_of_metadatas
            print "************DEBUG 2-2 ***********"
            MetdatasFromJSON = json_of_metadatas['json']
            print MetdatasFromJSON          
            print "************DEBUG 2-3 ***********"
            MetdatasFromJSON0 = MetdatasFromJSON[0]
            print MetdatasFromJSON0
            print "************DEBUG 3-5 ***********"
            strMetdatasFromJSON0 = str(MetdatasFromJSON0)
            MetdatasDICT = ast.literal_eval(strMetdatasFromJSON0)
            print MetdatasDICT
            print "************DEBUG 3-5 ***********"
            for key in MetdatasDICT :
                print "key: %s , value: %s" % (key, MetdatasDICT[key])
            print "************DEBUG 4 ***********"
            f = request.files['file']
            f.save(secure_filename(f.filename))
            print "FILE SAVED LOCALY"
            return 'JSON of customer posted'
MouIdri
  • 1,300
  • 1
  • 18
  • 37
3

If this is not in production, there is an easier way than binding the json in files, send in the json data as param value instead of binding it in json.

datas = {data: {'var1' : 'var1','var2'  : 'var2}}
url = "http://10.100.2.6:80/customerupdate"
files = {'document': open(local_file_to_send, 'rb')}
headers = {'Content-type': 'application/json'}
r = requests.post(url, files=files, params=datas, headers=headers)

and in flask server accept data and file as:

image = request.files.get('image')
data = request.args.get('data')
neeraj mdas
  • 163
  • 3
  • 15
0

I was able to send json and file via TestClient post in data attribute like so:

with self.app.test_client() as c:
data = {'here': 'is', 'some': 'data' }
data={
        'file': filename, open(filename, 'rb'), "image/jpeg"),
        'json': json.dumps(data)}
rv = c.post(url, data=data)

On Server-Side json can be loaded from request form:

json_data = json.loads(request.form["json"])

File can be loaded from files

file = request.files["file"]

This way a clean distinction between files and other data is given.

I found how the data must look like here: https://werkzeug.palletsprojects.com/en/1.0.x/test/#werkzeug.test.EnvironBuilder

Prroet
  • 1
  • 2