In the Django Rest Framework I would like to post a file, received as an InMemoryUploadedFile, to a different server as soon as it is received.
It sounds simple, but the request.post() function does not seem to properly send over such a file :
def post(self, request, *args, **kwargs):
data = request.data
print(data)
# <QueryDict: {'file': [<InMemoryUploadedFile: myfile.pdf (application/pdf)>]}>
endpoint = OTHER_API_URL + "/endpoint"
r = requests.post(endpoint, files=data)
My other server receives the request (through flask) with the name of the file, but not the content:
#app.route("/endpoint", methods=["POST"])
def endpoint():
if flask.request.method == "POST":
# I removed the many checks to simplify the code
file = flask.request.files['file']
path = os.path.join(UPLOAD_FOLDER, file.filename)
file.save(path)
print(file) #<FileStorage: u'file.pdf' (None)>
print(os.path.getsize(path)) #0
return [{"response":"ok"}]
When posting a file directly to that api in form-data with postman, It works as expected:
print(file) # <FileStorage: u'file.pdf' ('application/pdf')>
print(os.path.getsize(path)) #8541
Any help on how to fix this, i.e. transform the InMemoryUploadedFile type in something a normal REST api can understand? Or maybe just adding the right headers?
I had to figure this issue out passing an uploaded file from a Django front end website to a Django backend API in Python 3. The InMemoryUploadedFile's actual file data can be accessed via the object's .file property's .getvalue() method.
path="path/to/api"
in_memory_uploaded_file = request.FILES['my_file']
io_file = in_memory_uploaded_file.file
file_value = io_file.getvalue()
files = {'my_file': file_value}
make_http_request(path, files=files)
and can be shortened
file = request.FILES['my_file'].file.getvalue()
files = {'my_file': file}
Before this, trying to send InMemoryUploadFile objects, the file property, or the result of the read() method all proved to send a blank/empty file by the time it got to the API.
I had the same problem and the same case.
My working solution
headers = {
"Host": API_HOST,
"cache-control": "no-cache",
}
try:
data = json_request = request.POST['json_request'].strip()
data = json.loads(data) # important!
except:
raise Http404
try:
in_memory_uploaded_file = request.FILES['file'].file.getvalue()
files = {'photo': in_memory_uploaded_file} # important!
except:
files = {}
if USE_HTTPS:
API_HOST = f'https://{API_HOST}'
else:
API_HOST = f'http://{API_HOST}'
if authorization_key and len(authorization_key) > 0:
response = requests.post(f'{API_HOST}/api/json/?authorization_key={authorization_key}', headers=headers, data=data, files=files)
else:
response = requests.post(f'{API_HOST}/api/json/', headers=headers, data=data)
json_response = json.dumps(response.json())
Related
I have been following the advice given on this thread: How to pass image to requests.post in python?
However, I got stuck with the error, "ValueError: read of closed file".
I have been trying to invoke an API that does some image recognition. At the moment, I am using Postman's form-data feature to send it to the Django server, which then uses "requests" to invoke the API. Let me show you the code snippet.
def post(self, request):
img = request.FILES["file"]
with img.open("rb") as f:
files = {"fileToUpload": f}
url = "http://XXXXXXXXXXXXXXXX/getSimilarProducts?APIKEY=XXXXXXX"
response = requests.post(url, files=files)
After sending the request, when I check each variable with the debugger, the 'img' variable contains a temporary uploaded file like so:
<TemporaryUploadedFile: XXXXXXXXXXXXXX.png (image/png)>
What am I missing? And what should I do to overcome this error? Thanks a lot in advance!
the response should be within the with context
def post(self, request):
img = request.FILES['file']
with img.open('rb') as f:
files = {'fileToUpload': f}
url = 'http://XXXXXXXXXXXXXXXX/getSimilarProducts?APIKEY=XXXXXXX'
response = requests.post(url, files=files)
I am writing a small test project with python\django.
I'm sending an image to server (with request post), resizing it and want it back to the client.
So far it seems that everything working properly beside that i dont know how to send the file back to the client.
CLIENT:
testFile = "<location>"
api_url = 'http://localhost:9999/upload'
files = {'file': open(testFile, 'rb')}
r = requests.post(api_url,files=files,data={"m":10,"n":15})
files["file"].close()
print r -> status 200
SERVER:
#csrf_exempt
def post(request):
if request.method == 'POST':
logger.info("New post request")
#logger.info(request.FILES)
file = request.FILES['file']
resized_frame = job_manager.add_job(file,request.POST.get("m"),request.POST.get("n"))
logger.info("returning new resized frame ")
response = HttpResponse(content_type='image/png')
response['Content-Disposition'] = 'attachment; filename=%s' % smart_str(resized_frame)
return response
#return HttpResponse(resized_frame)
else:
return HttpResponse('500')
can I even send response with files while getting a post request?
I have the following view. I test it through the laptop browsers and download takes place with no problem. But if I use browser of a document manager like 'Documents' on iphone, the very same requested file gets loaded into the browser. What am I missing here?
def servefiles(request, segmentID):
segments = []
obj = MainFile.objects.filter(owner=request.user)
file_name = MainFile.objects.get(file_id=segmentID).file_name
if request.method == 'GET':
hosts = settings.HOSTS
for i in hosts:
try:
url = 'http://' + i + ':8000/foo/' + str(segmentID)
r = requests.get(url, timeout=1, stream=True)
if r.status_code == 200:
segments.append(r.content)
except:
continue
instance = SeIDA('test', x=settings.M, y=settings.N)
docfile = instance.decoder(segments)
response = HttpResponse()
response.write(docfile)
response['Content-Disposition'] = 'attachment; filename={0}'.format(file_name)
return response
Note: If you might be wondering, SeIDA module encodes a data onto n segments such that presence of m segments is sufficed to recover the file. servefiles view retrieves the segments from storage backends and recovers the file and finally serves them. I have no trouble making requests on desktop browsers, but with no download manager on iphone have I been able to download the file.
Thanks to Sayse the trick was to specify the mimetypes in the content_type header
import mymetypes
response = HttpResponse(content_type=mimetypes.guess_type(file_name))
response.write(docfile)
response['Content-Disposition'] = 'attachment; filename={0}'.format(file_name)
return response
I have a REST PUT request to upload a file using the Django REST framework. Whenever I am uploading a file using the Postman REST client it works fine:
But when I try to do this with my code:
import requests
API_URL = "http://123.316.118.92:8888/api/"
API_TOKEN = "1682b28041de357d81ea81db6a228c823ad52967"
URL = API_URL + 'configuration/configlet/31'
#files = {
files = {'file': open('configlet.txt','rb')}
print URL
print "Update Url ==-------------------"
headers = {'Content-Type' : 'text/plain','Authorization':API_TOKEN}
resp = requests.put(URL,files=files,headers = headers)
print resp.text
print resp.status_code
I am getting an error on the server side:
MultiValueDictKeyError at /api/configuration/31/
"'file'"
I am passing file as key but still getting above error please do let me know what might I am doing wrong here.
This is how my Django server view looks
def put(self, request,id,format=None):
configlet = self.get_object(id)
configlet.config_path.delete(save=False)
file_obj = request.FILES['file']
configlet.config_path = file_obj
file_content = file_obj.read()
params = parse_file(file_content)
configlet.parameters = json.dumps(params)
logger.debug("File content: "+str(file_content))
configlet.save()
For this to work you need to send a multipart/form-data body. You should not be setting the content-type of the whole request to text/plain here; set only the mime-type of the one part:
files = {'file': ('configlet.txt', open('configlet.txt','rb'), 'text/plain')}
headers = {'Authorization': API_TOKEN}
resp = requests.put(URL, files=files, headers=headers)
This leaves setting the Content-Type header for the request as a whole to the library, and using files sets that to multipart/form-data for you.
I am trying to upload a file via a post request to amazon s3. Trouble is I don't know how to format the request to be a multipart form.
This is what I have right now:
content_type = "image/JPEG"
key = 'uploads/filename.jpg'
acl = "public-read"
bucket = None
params_raw = create_upload_data(content_type,key,acl,bucket)
params = { 'policy': params_raw['policy'],'acl':acl,'signature':params_raw['signature'],'key':params_raw['key'],'Content-Type':params_raw['Content-Type'],'AWSAccessKeyId':params_raw['AWSAccessKeyId'],'success_action_status':params_raw['success_action_status'],'binary': binary_data}
r = requests.post(params_raw['form_action'],data=params)
I think I am getting a bad response because it isn't a multipart form but here is what the response text looks like:
{"request": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Error><Code>InvalidArgument</Code><Message>Conflicting query string parameters: acl, policy</Message><ArgumentName>ResourceType</ArgumentName><ArgumentValue>acl</ArgumentValue><RequestId>D558E016151E448F</RequestId><HostId>WT5aT0OOqJx3ziPgYFzjuTHJSERaCcuJG+y/acs6+l/mWVwO0MiH3lhWyBWIdhKr9BnhdIpkarw=</HostId></Error>"}
How do I structure the request with the file... it is a .jpg in base 64?
It is sufficient to change content-type
content-type = 'multipart/form-data'
Had a fair bit of pain around this but finally got it going. Very simple in the end!
url = "https://yourbucket.s3.amazonaws.com"
#complete_path is the local server path to the file
files = {'file':open(complete_path,'rb')}
r = requests.post(url, data=params, files=files)