pyramid FileResponse encoding - pyramid

I'm trying to serve base64 encoded image files and failing. Either I get UTF-8 encoded responses or the line return response errors in an interesting way. Mostly everything I've tried can be seen as commented out code in the excerpt below. Details of the traceback follow that.
My question is: How can I return base64 encoded files?
#import base64
#with open(sPath, "rb") as image_file:
#encoded_string = base64.b64encode(image_file.read())
dContentTypes = {
'bmp' : 'image/bmp',
'cod' : 'image/cis-cod',
'git' : 'image/gif',
'ief' : 'image/ief',
'jpe' : 'image/jpeg',
.....
}
sContentType = dContentTypes[sExt]
response = FileResponse(
sPath,
request=request,
content_type= sContentType#+';base64',
#content_encoding = 'base_64'
#content_encoding = encoded_string
)
return response
Uncommenting the line #content_encoding = encoded_string gives me the error:
AssertionError: Header value b'/9j/4AAQSkZJRgABAQAA' is not a string in ('Content-Encoding', b'/9j/4AAQSkZJRgABAQAA....')

FileResponse is used specifically for uploading a file as a response (hence the path argument). In you're case you want to base64-encode the file before uploading it. This means no FileResponse.
Since you've read the file into memory you can just upload the content in a Response.
response = Response(encoded_string,
request=request,
content_type=sContentType+';base64')
I'm not actually sure how content_encoding compares to the ;base64 on the type, but I think the encoding is used more commonly for gzipped content. YMMV.

The error you are seeing is telling you that Content-Type is not a string. Content-Type is an HTTP header. And as far as I know, HTTP headers must be strings.
I believe the base64 encoded file you want to pass as the body of the response. FileResponse is not appropriate here since you presumably want to pass encoded string as the body and FileResponse expects a path that it then reads in and sets the body.

Related

Urllib3 POST request with attachment in python

I wish to make a post request to add an attachment utilising urllib3 in python without success. I have confirmed the API itself is working in postman but cannot work out how to convert this request to python. Appreciating I'm mixing object types I just don't know how to avoid it.
Python code:
import urllib3
import json
api_key = "secret_key"
header = {"X-API-KEY": api_key, "ACCEPT": "application/json", "content-type": "multipart/form-data"}
url = "https://secret_url.com/api/"
http = urllib3.PoolManager()
with open("invoice.html", 'rb') as f:
file_data = f.read()
payload = {
"attchment": {
"file": file_data
}
}
payload = json.dumps(payload)
r = http.request('post', url, headers = header, fields = payload)
print(r.status)
print(r.data)
Postman - which works and properly sends file-name through also (I'm guessing it splits the bytes and filename up?)
Edit: I've also tried the requests library as I'm more familiar with this (but can't use it as the script will be running in AWS lambda). Removing the attachment element form the dict allows it to run but the API endpoint gives 401 presumably because it's missing the "attachement" part to the data structure as per postman below... but when I put this in I get runtime errors.
r = requests.post(url, headers = header, files={"file": open("invoice.html", 'rb')})
For anyone who stumbles upon this from Dr google a few points:
I was completely mis-interpreting the structure of the element. It's actually a string "attachment[file]" not a dict like object.
Postman has the ability to output python code in urllib/request syntax albeit not 100% what I was after. Note: the chrome version (depreciated) outputs gibberish code that only half works so the client version should be used. A short bit of work below shows it working as expected:
http = urllib3.PoolManager()
with open("invoice.html", "rb") as f:
file = f.read()
payload={
'attachment[file]':('invoice.html',file,'text/html')
}
r = http.request('post', url, headers = header, fields = payload)

PYTHON FLASK - request.files displays as file not found eventhough it exits

I am trying to trigger an external api from postman by passing the uploadFile in the body as form-data. Below code throws me an error as 'FileNotFoundError: [Errno 2] No such file or directory:'
Note: In postman, uploadFile takes file from my local desktop as input. have also modified the postman settings to allow access for files apart from working directory
Any help would be highly appreciable.
Below is the Code:
#app.route('/upload', methods=['POST'])
#auth.login_required
def post_upload():
payload = {
'table_name': 'incident', 'table_sys_id': request.form['table_sys_id']
}
files = {'file': (request.files['uploadFile'], open(request.files['uploadFile'].filename,
'rb'),'image/jpg', {'Expires': '0'})}
response = requests.post(url, headers=headers, files=files, data=payload)
return jsonify("Success- Attachment uploaded successfully ", 200)
Below code throws me an error as 'FileNotFoundError: [Errno 2] No such file or directory:
Have you defined UPLOAD_FOLDER ? Please see: https://flask.palletsprojects.com/en/latest/patterns/fileuploads/#a-gentle-introduction
i am passing the attribute (upload file) in body as form-data, can this be passed as raw json
You cannot upload files with JSON. But one hacky way to achieve this is to base64 (useful reference) encode the file before sending it. This way you do not upload the file instead you send the file content encoded in base64 format.
Server side:
import base64
file_content = base64.b64decode(request.data['file_buffer_b64'])
Client side:
-> Javascript:
const response = await axios.post(uri, {file_buffer_b64: window.btoa(file)})
-> Python:
import base64
with open(request.data['uploadFile'], "rb") as myfile:
encoded_string = base64.b64encode(myfile.read())
payload = {"file_buffer_b64": encoded_string}
response = requests.post(url, data=payload)

Python Requests Post - Additional field is not recognized for file upload

I have to post a file using Multipart upload to a company-internal REST service. The endpoint needs the file as property "file" and it needs an additional property "DestinationPath". Here is what I do:
url = r"http://<Internal IP>/upload"
files = {
"DestinationPath": "/some/where/foo.txt",
"file": open("test.txt", "rb")
}
response = requests.post(url, files=files)
The server complains that it can't get the "DestinationPath". Full error message I receive is:
{'errors': {'DestinationPath': ['The DestinationPath field is required.']},
'status': 400,
'title': 'One or more validation errors occurred.',
'traceId': '00-1993fbc53ab2ee418b683915dd7a440a-2338bd9cf34d414a-00',
'type': 'https://tools.ietf.org/html/rfc7231#section-6.5.1'}
The file upload works in curl, thus it must be python specific.
You might want to try using the data argument instead of files.
response = requests.post(url, data=files)
Thanks to #etemple1 I found the solution to my question:
url = r"http://<Internal IP>/upload"
data = {
"DestinationPath": "/some/where/foo.txt",
}
with open("test.txt", "rb") as content:
files = {
"file": content.read(),
}
response = requests.post(url, data=data, files=files)
The data for the multipart upload needed to be divided between "data" and "files". They are later combined in the body of the http post by the requests library.

Pyramid FileResponse for dynamic files

I want my client to download (not render) a dynamically generated PDF file via pyramid. Right now I do it like this:
def get_pdf(request):
pdfFile = open('/tmp/example.pdf', "wb")
pdfFile.write(generator.GeneratePDF())
response = FileResponse('/tmp/example.pdf')
response.headers['Content-Disposition'] = ('attachment; filename=example.pdf')
return response
From the client point-of-view it's exactly what I need. However,
It leaves behind an orphaned file
It isn't thread-safe (though I could use random filenames)
The docs say:
class FileResponse
A Response object that can be used to serve a static file from disk simply.
So FileResponse is probably not what I should be using. How would you replace it with something more dynamic but indistinguishable for the client?
Just use a normal response with the same header:
def get_pdf(request):
response = Response(body=generator.GeneratePDF())
response.headers['Content-Disposition'] = ('attachment;filename=example.pdf')
return response

Post multipart form with multiple file uploads using python requests

I have demographics information extracted for some people in the form of list of python dictionaries(each dict for an individual). Also I need to upload the document from where I extracted the data (pdf/word). I tried Multipart form submission using Python requests which because of some reason does not seem to work.
The API expects two keys 'files' and 'data'
'files' is a list of file objects
'data' is a list of dicts which is stringified using json.dumps (API requirements)
pay_part= [{"umr":"","age":"","gender":"","first_name":"","middle_name":"","last_name":"","phone":"","address":"","admission_date":"","lab":"","discharge_date":"","ip_number":"","diagnosis":"","reason":"","treatment":"","medications":"","expired_date":"","instructions":"","review_date":"","procedure":"","notes":"","physician":"","filename":""},{"umr":"","age":"","gender":"","first_name":"","middle_name":"","last_name":"","phone":"","address":"","admission_date":"","lab":"","discharge_date":"","ip_number":"","diagnosis":"","reason":"","treatment":"","medications":"","expired_date":"","instructions":"","review_date":"","procedure":"","notes":"","physician":"","filename":""}]
multipart_data = MultipartEncoder(
fields={
"file":[('file.docx',open('13427.docx', 'rb'),'text/plain'),
('file.docx',open('13427.docx', 'rb'),'text/plain')],
"payload": json.dumps(pay_part)
}
)
response = requests.post(url, data=multipart_data, headers={'Content-Type': 'multipart_data.content_type; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW', 'userid': sUserID,'metaid': metaid,'postman-token':postmanToken})
print(response.text)
While forming the multipart form object I get an error
"AttributeError: 'tuple' object has no attribute 'encode'".
I believe this has to do something with creating file objects as a binary and storing in list.
Thanks in advance!
I got it to work!
Just send your json object using the argument ‘data’ and a list of your file objects using the argument ‘files’ as shown below.
I removed from the header argument “'Content-Type': 'multipart_data.content_type; boundary=---WebKitFormBoundary7MA4YWxkTrZu0gW'”
The post request was made as a multipart post
Code:-
fields={'payload': json.dumps(pay_part)})
response = requests.post(url, data=fields,files =[('file',open('13385.docx', 'rb')),('file',open('13385.docx', 'rb'))], headers={'userid': sUserID,'metaid': metaid,'postman-token':postmanToken})
print(response.text)

Resources