Python response with excel file - python

I'm using api_hour for API web server and trying to response to request with excel file. But it is much more difficult than I thought.
If I use nodejs or django, it is fine and there are many guide for it. But api_hour is not. The following code is mine.
headers = {
'Content-Disposition': 'attachment; filename="excel_file.xlsx"',
'Access-Control-Allow-Headers': 'status,Origin, X-Requested-With, Content-Type, Cookie, Accept, X-PINGOTHER',
'Access-Control-Allow-Methods': '*',
'Accept-Ranges': 'bytes'
}
self.responseHeaders = multidict.MultiDict(headers, )
return Response(content_type='application/vnd.openxmlformatsofficedocument.spreadsheetml.sheet',
status=self.status,
headers=self.responseHeaders,
body=response['excel'])
What I found is that the other frameworks, for example nodejs, django, and ASP.NET Core, respond with file using there own encapsulated functions so do not assign binary data, which is response['excel'] in this code, directly to body.
Is there any way to respond with file ? especially excel ? thanks.

It looks like Response is aiohttp.web.Response.
You may thus want to look into the StreamResponse class instead, which you can stream data into, if that's what you're asking.
Or, if you need to (try to) force the client to download your data as a file, add a Content-Disposition header:
Response(
...,
headers={"Content-Disposition": "attachment; filename=my-excel.xlsx"},
)

Related

Chalice Framework: Request did not specify an Accept header with image/jpeg

I want to return an image from a Chalice/python application. My entire application code is pasted below:
from chalice import Chalice, Response
import base64
app = Chalice(app_name='hello')
#app.route('/makeImage', methods=['GET'])
def makeImage():
return Response(
base64.b64decode(
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
),
headers={
'Content-Type': 'image/jpeg'
},
status_code=200)
The result...
{"Code":"BadRequest","Message":"Request did not specify an Accept
header with image/jpeg, The response has a Content-Type of image/jpeg.
If a response has a binary Content-Type then the request must specify
an Accept header that matches."}
Why does this happen?
I have poured through a ton of documentation already and most of it is outdated as binary support was added to Chalice very recently:
https://github.com/aws/chalice/pull/352
https://github.com/aws/chalice/issues/592
https://github.com/aws/chalice/issues/348
AWS Chalice Return an Image File from S3 (Warning: the sole answer to this question is COMPLETELY WRONG)
https://chalice.readthedocs.io/en/latest/api.html
https://github.com/aws/chalice/issues/391 (issue WRONGLY CLOSED in 2017 without a resolution)
https://github.com/aws/chalice/issues/1095 is a re-open of 391 above
Just for troubleshooting purposes I'm able to obtain a response by using curl -H "accept: image/jpeg", but this is useless since browsers to not work this way, and I need to use the response in a browser (HTML IMG TAG).
UPDATE
I also tried #app.route('/makeImage', methods=['GET'], content_types=['image/jpeg'])
And result became {"Code":"UnsupportedMediaType","Message":"Unsupported media type: application/json"}
I had the same issue.
If no header accept is present, AWS set it to default application/json and I receive a base64 response. If I set accept to images/jpeg or any binary content type in header, then I got the images. Great but web browser to not set the accept header.
But if I add
app.api.binary_types =['*/*']
then ok my images apis now works. Great but now the json ones fail.
Currently I do not see any solution except having two API gateway : one for json and one for images. If you really want only one API Gateway, I think you have to use gzip conpression on all you json response to convert them to binaries.
It is more how AWS API Gateway works with lambda proxy than a Chalice issue. But I agree, it is a big limitation
There was a bug in Chalice that was fixed on 14-May-2019 and documented here:
https://github.com/aws/chalice/issues/1095
In addition to installing the latest Chalice directly from GitHub, I also had to add:
app.api.binary_types =['*/*']
in app.py.
The final working code looks like this:
from chalice import Chalice, Response
import base64
app = Chalice(app_name='hello')
app.api.binary_types =['*/*']
#app.route('/makeImage', methods=['GET'])
def makeImage():
return Response(
base64.b64decode(
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
),
headers={
'Content-Type': 'image/jpeg'
},
status_code=200)

How to upload a binary/video file using Python http.client PUT method?

I am communicating with an API using HTTP.client in Python 3.6.2.
In order to upload a file it requires a three stage process.
I have managed to talk successfully using POST methods and the server returns data as I expect.
However, the stage that requires the actual file to be uploaded is a PUT method - and I cannot figure out how to syntax the code to include a pointer to the actual file on my storage - the file is an mp4 video file.
Here is a snippet of the code with my noob annotations :)
#define connection as HTTPS and define URL
uploadstep2 = http.client.HTTPSConnection("grabyo-prod.s3-accelerate.amazonaws.com")
#define headers
headers = {
'accept': "application/json",
'content-type': "application/x-www-form-urlencoded"
}
#define the structure of the request and send it.
#Here it is a PUT request to the unique URL as defined above with the correct file and headers.
uploadstep2.request("PUT", myUniqueUploadUrl, body="C:\Test.mp4", headers=headers)
#get the response from the server
uploadstep2response = uploadstep2.getresponse()
#read the data from the response and put to a usable variable
step2responsedata = uploadstep2response.read()
The response I am getting back at this stage is an
"Error 400 Bad Request - Could not obtain the file information."
I am certain this relates to the body="C:\Test.mp4" section of the code.
Can you please advise how I can correctly reference a file within the PUT method?
Thanks in advance
uploadstep2.request("PUT", myUniqueUploadUrl, body="C:\Test.mp4", headers=headers)
will put the actual string "C:\Test.mp4" in the body of your request, not the content of the file named "C:\Test.mp4" as you expect.
You need to open the file, read it's content then pass it as body. Or to stream it, but AFAIK http.client does not support that, and since your file seems to be a video, it is potentially huge and will use plenty of RAM for no good reason.
My suggestion would be to use requests, which is a way better lib to do this kind of things:
import requests
with open(r'C:\Test.mp4'), 'rb') as finput:
response = requests.put('https://grabyo-prod.s3-accelerate.amazonaws.com/youruploadpath', data=finput)
print(response.json())
I do not know if it is useful for you, but you can try to send a POST request with requests module :
import requests
url = ""
data = {'title':'metadata','timeDuration':120}
mp3_f = open('/path/your_file.mp3', 'rb')
files = {'messageFile': mp3_f}
req = requests.post(url, files=files, json=data)
print (req.status_code)
print (req.content)
Hope it helps .

Download doesn't start in web browser by flask.Response

In Flask (micro web framework), we have a view as:
#app.route('/download/<id>/<resolution>/<extension>/')
def download_by_id(id, resolution=None, extension=None):
stream = youtube.stream_url(id, resolution, extension)
binary = requests.get(stream['url'], stream=True)
return flask.Response(
binary,
headers={'Content-Disposition': 'attachment; '
'filename=' + stream['filename']})
In template we have a link as Download 240p Video and when it's clicked, it should start downloading that video.
Issue is:
It is working fine in some browsers where no Download Manager like IDM etc. is installed. But IDM fails to download it. IDM just hangs at http://example.com/download/adkdsk457jds/240p/mp4/
Same is the case with Firefox's own download manager. Firefox just downloads a plain .html page and not the actual video.
But, videos gets downloaded successfully in Chrome when no IDM or other Download Manager is installed.
Please help and advice why it's not working. Do i need to change something in code?
You haven't included any response information, including the content type; you need to copy over a little more information about the original response to communicate what type of response you are returning. Otherwise defaults are used (dictated either by the HTTP standard or by Flask).
Specifically, at the very least you want to copy across the content type, length, and the transfer encoding:
headers={
'Content-Disposition': 'attachment; filename=' + stream['filename']
}
for header in ('content-type', 'content-length', 'transfer-encoding'):
if header in binary.headers:
headers[header] = binary.headers[header]
return flask.Response(binary.raw, headers=headers)
I'm using the response.raw underlying raw file object; this should work too but has the added advantage that any compression applied by YouTube is retained.
Some download managers may try to use a HTTP range request to grab a download in parallel, even when the server is not advertising that it supports such requests. You should probably respond with a 406 Not Acceptable response (requesting byte ranges when not supported is a Accept-* violation). You'll need to log what headers the download manager sends to be sure if this is the case.
Add 'Content-Type': 'application/octet-stream' to headers

Python file upload from url using requests library

I want to upload a file to an url. The file I want to upload is not on my computer, but I have the url of the file. I want to upload it using requests library. So, I want to do something like this:
url = 'http://httpbin.org/post'
files = {'file': open('report.xls', 'rb')}
r = requests.post(url, files=files)
But, only difference is, the file report.xls comes from some url which is not in my computer.
The only way to do this is to download the body of the URL so you can upload it.
The problem is that a form that takes a file is expecting the body of the file in the HTTP POST. Someone could write a form that takes a URL instead, and does the fetching on its own… but that would be a different form and request than the one that takes a file (or, maybe, the same form, with an optional file and an optional URL).
You don't have to download it and save it to a file, of course. You can just download it into memory:
urlsrc = 'http://example.com/source'
rsrc = requests.get(urlsrc)
urldst = 'http://example.com/dest'
rdst = requests.post(urldst, files={'file': rsrc.content})
Of course in some cases, you might always want to forward along the filename, or some other headers, like the Content-Type. Or, for huge files, you might want to stream from one server to the other without downloading and then uploading the whole file at once. You'll have to do any such things manually, but almost everything is easy with requests, and explained well in the docs.*
* Well, that last example isn't quite easy… you have to get the raw socket-wrappers off the requests and read and write, and make sure you don't deadlock, and so on…
There is an example in the documentation that may suit you. A file-like object can be used as a stream input for a POST request. Combine this with a stream response for your GET (passing stream=True), or one of the other options documented here.
This allows you to do a POST from another GET without buffering the entire payload locally. In the worst case, you may have to write a file-like class as "glue code", allowing you to pass your glue object to the POST that in turn reads from the GET response.
(This is similar to a documented technique using the Node.js request module.)
import requests
img_url = "http://...."
res_src = requests.get(img_url)
payload={}
files=[
('files',('image_name.jpg', res_src.content,'image/jpeg'))
]
headers = {"token":"******-*****-****-***-******"}
response = requests.request("POST", url, headers=headers, data=payload, files=files)
print(response.text)
above code is working for me.

sending data to xml api of webservice

Im trying to write a python script that basically interacts with a webservice that uses an xml api. The request method is POST.
Usually I would write a request of the form request(url, data, headers) - however, in the case of an xml api it would not work. Also something like data.encode('utf-8') or urllib.urlencode(data) would not work as the data is not a dict.
In this case, data is xml so how am i supposed to sent it over?
[EDIT]
When I send a string of XML I get a urllib2.HTTPError: HTTP Error 415: Unsupported Media Type Exception. Is there any other way I'm supposed to send the data?
Also, the API I am using the Google Contacts API. I'm trying to write a script that adds a contact to my gmail account.
You probably need to set proper Content-Type header, for XML it would probably be:
application/xml
So something like this should get you going:
request = urllib2.Request( 'xml_api.example.com' )
request.add_header('Content-Type', 'application/xml')
response = urllib2.urlopen(request, xml_data_string)
Hope that helps :)

Categories