I have a small flask application which takes some images for upload and converts them into a multipage tiff. Nothing special.
But how do I test the upload of multiple files and the file download?
My Testclient:
class RestTestCase(unittest.TestCase):
def setUp(self):
self.dir = os.path.dirname(__file__)
rest = imp.load_source('rest', self.dir + '/../rest.py')
rest.app.config['TESTING'] = True
self.app = rest.app.test_client()
def runTest(self):
with open(self.dir + '/img/img1.jpg', 'rb') as img1:
img1StringIO = StringIO(img1.read())
response = self.app.post('/convert',
content_type='multipart/form-data',
data={'photo': (img1StringIO, 'img1.jpg')},
follow_redirects=True)
assert True
if __name__ == "__main__":
unittest.main()
The application sends back the file with
return send_file(result, mimetype='image/tiff', \
as_attachment=True)
I want to read the file sent in the response and compare it with another file. How do I get the file from the response object?
I think maybe the confusion here is that response is a Response object and not the data downloaded by the post request. This is because an HTTP response has other attributes that are often useful to know, for example http status code returned, the mime-type of the response, etc... The attribute names to access these are listed in the link above.
The response object has an attribute called 'data', so response.data will contain the data downloaded from the server. The docs I linked to indicate that data is soon to be deprecated, and the get_data() method should be used instead, but the testing tutorial still uses data. Test on your own system to see what works.Assuming you want to test a round trip of the data,
def runTest(self):
with open(self.dir + '/img/img1.jpg', 'rb') as img1:
img1StringIO = StringIO(img1.read())
response = self.app.post('/convert',
content_type='multipart/form-data',
data={'photo': (img1StringIO, 'img1.jpg')},
follow_redirects=True)
img1StringIO.seek(0)
assert response.data == imgStringIO.read()
Related
I have a script called output.py
This script takes in 2 inputs, fileA and file B.
I can run it on my terminal by using the command output.py -fileA -fileB. The script will create a new JSON file and save it to the directory.
I want to run this script using Flask. I've defined a bare bones App here but I'm not sure how I'd run this using Flask
from flask import Flask
import output
import scripting
app = Flask(__name__)
#app.route('/')
def script():
return output
if __name__ == '__main__':
app.run()
Can someone help me out here, thanks!
It appears you are new to Flask. Get some basic tutorials (there are many on the web).
There are a couple of options:
Send contents of file A and File b as json payload. Pull the A and B content off the json body and do the processing you need to and return the body.
Send the contents of the file as multipart/form-data (you can send multiple files).
Note: This is not working code - just for illustration.
from flask import Flask, request, make_response
app = Flask(__name__)
def build_response(status=False, error="", data={}, total=0, headers=[], contentType="application/json", expose_headers=["X-Total-Count"], retcode=400, additional_data=None):
resp = {"success": status, "error": error, "data": data}
resp = make_response(json.dumps(resp))
for item in headers:
resp.headers[item] = headers[item]
resp.headers['Content-Type'] = contentType
resp.headers.add('Access-Control-Expose-Headers', ','.join(expose_headers))
resp.status_code = retcode
return resp
#app.route('/run-script', methods=['POST'])
def run_script():
# check if the post request has the file part
try:
# Note: THis code is just to illustrate the concept.
# Option-1 (content type must be application/json)
json_dict = request.get_json()
fileA = json_dict["fileA"]
fileB = json_dict["fileB"]
# Option-2 (Note: fileA/fileB are objects, put a pdb and check it out)
fileA = request.files['fileA']
fileB = request.files['fileA']
resp = process(fileA, fileB)
return build_response(status=True, data=resp, retcode=200)
except Exception as e:
msg = f"Error - {str(ec)}"
return build_response(status=False, error=msg, retcode=400)
Hi I am new to writing web APIs in python. And my understanding of REST is limited
I have a simple Flask API that takes in a python dict {'pdf':pdf_as_bytes, 'filename':string}
The below is my server script:
#app.route("/predict", methods=["POST"])
def predict():
data = {"success": False}
if flask.request.method == "POST":
pdf = flask.request.files["pdf"].read()
filename = flask.request.files["filename"].read().decode("utf-8")
assert isinstance(filename, str), "Got {}".format(type(filename))
assert isinstance(pdf, bytes), "Got {}".format(type(pdf))
# further processing happens and returns a json
This works as intended when I write a python client as follows:
import requests
import os
ip = "localhost"
port = 8605
url = "http://{}:{}/predict".format(ip,port)
path_to_pdf = "./617339931.pdf"
with open(path_to_pdf, "rb") as f:
pdf = f.read() # pdf is a bytes
# the payload must have the following fields: "pdf": bytes, "filename": string object
payload = {"pdf":pdf,"filename":os.path.basename(path_to_pdf).split(".")[0]}
# post request
result = requests.post(url=url, files=payload).json()
# the resulting json always has a field called as success, which returns True or False accordingly
if result["success"] == True:
print(result["data"].keys())
But, When I send a request using Postman I get a 400 Error! Below is the screen shot of the error
I don't understand. How can I change my server code so that it works with Postman and also Python client programs
I just did the same thing, and I think it's because of the double quotes you are putting in key and value, try to take them out.
I have a problem with getting my test running using Robot Framework and robotframework-requests. I need to send a POST request and a binary data in the body. I looked at this question already, but it's not really answered. Here's how my test case looks like:
Upload ${filename} file
Create Session mysession http://${ADDRESS}
${data} = Get Binary File ${filename}
&{headers} = Create Dictionary Content-Type=application/octet-stream Accept=application/octet-stream
${resp} = Post Request mysession ${CGIPath} data=${data} headers=&{headers}
[Return] ${resp.status_code} ${resp.text}
The problem is that my binary data is about 250MB. When the data is read with Get Binary File I see that memory consumption goes up to 2.x GB. A few seconds later when the Post Request is triggered my test is killed by OOM. I already looked at files parameter, but it seems it uses multipart encoding upload, which is not what I need.
My other thought was about passing open file handler directly to underlying requests library, but I guess that would require robotframework-request modification. Another idea is to fall back to curl for this test only.
Am I missing something in my test? What is the better way to address this?
I proceeded with the idea of robotframework-request modification and added this method
def post_request_binary(
self,
alias,
uri,
path=None,
params=None,
headers=None,
allow_redirects=None,
timeout=None):
session = self._cache.switch(alias)
redir = True if allow_redirects is None else allow_redirects
self._capture_output()
method_name = "post"
method = getattr(session, method_name)
with open(path, 'rb') as f:
resp = method(self._get_url(session, uri),
data=f,
params=self._utf8_urlencode(params),
headers=headers,
allow_redirects=allow_redirects,
timeout=self._get_timeout(timeout),
cookies=self.cookies,
verify=self.verify)
self._print_debug()
# Store the last session object
session.last_resp = resp
self.builtin.log(method_name + ' response: ' + resp.text, 'DEBUG')
return resp
I guess I can improve it a bit and create a pull request.
I have a flask app that was created using a csv file. Using html forms, I give the user the option to input new data, and then I write changes to the csv (using a custom function) and download it. This downloads just fine and saves to my desktop. Is there any way to tweak the code to save it in the same project directory and overwrite the csv that serves the flask app? This way the app might update upon refresh. Thanks!
#app.route('/csv/')
def download_csv():
model_id=request.args['textid']
client_id = session['client_id']
# return response
df=recommender.update_history(client_id, model_id)
df= recommender.get_csv()
resp = make_response(df.to_csv(encoding='iso-8859-1',index=False))
resp.headers["Content-Disposition"] = "attachment; filename=export.csv"
resp.headers["Content-Type"] = "text/csv"
return resp
The comment above is correct. I didn't need the make_response function. Here's what worked:
#app.route('/csv/')
def download_csv():
model_id=request.args['textid']
client_id = session['client_id']
# return response
df=recommender.update_history(client_id, model_id)
df= recommender.get_csv()
path=r'data/file_name.csv'
resp = df.to_csv(path, encoding='iso-8859-1',index=False)
return render_template('index.html')
I am using Python's requests library in one method of my application. The body of the method looks like this:
def handle_remote_file(url, **kwargs):
response = requests.get(url, ...)
buff = StringIO.StringIO()
buff.write(response.content)
...
return True
I'd like to write some unit tests for that method, however, what I want to do is to pass a fake local url such as:
class RemoteTest(TestCase):
def setUp(self):
self.url = 'file:///tmp/dummy.txt'
def test_handle_remote_file(self):
self.assertTrue(handle_remote_file(self.url))
When I call requests.get with a local url, I got the KeyError exception below:
requests.get('file:///tmp/dummy.txt')
/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/requests/packages/urllib3/poolmanager.pyc in connection_from_host(self, host, port, scheme)
76
77 # Make a fresh ConnectionPool of the desired type
78 pool_cls = pool_classes_by_scheme[scheme]
79 pool = pool_cls(host, port, **self.connection_pool_kw)
80
KeyError: 'file'
The question is how can I pass a local url to requests.get?
PS: I made up the above example. It possibly contains many errors.
As #WooParadog explained requests library doesn't know how to handle local files. Although, current version allows to define transport adapters.
Therefore you can simply define you own adapter which will be able to handle local files, e.g.:
from requests_testadapter import Resp
import os
class LocalFileAdapter(requests.adapters.HTTPAdapter):
def build_response_from_file(self, request):
file_path = request.url[7:]
with open(file_path, 'rb') as file:
buff = bytearray(os.path.getsize(file_path))
file.readinto(buff)
resp = Resp(buff)
r = self.build_response(request, resp)
return r
def send(self, request, stream=False, timeout=None,
verify=True, cert=None, proxies=None):
return self.build_response_from_file(request)
requests_session = requests.session()
requests_session.mount('file://', LocalFileAdapter())
requests_session.get('file://<some_local_path>')
I'm using requests-testadapter module in the above example.
Here's a transport adapter I wrote which is more featureful than b1r3k's and has no additional dependencies beyond Requests itself. I haven't tested it exhaustively yet, but what I have tried seems to be bug-free.
import requests
import os, sys
if sys.version_info.major < 3:
from urllib import url2pathname
else:
from urllib.request import url2pathname
class LocalFileAdapter(requests.adapters.BaseAdapter):
"""Protocol Adapter to allow Requests to GET file:// URLs
#todo: Properly handle non-empty hostname portions.
"""
#staticmethod
def _chkpath(method, path):
"""Return an HTTP status for the given filesystem path."""
if method.lower() in ('put', 'delete'):
return 501, "Not Implemented" # TODO
elif method.lower() not in ('get', 'head'):
return 405, "Method Not Allowed"
elif os.path.isdir(path):
return 400, "Path Not A File"
elif not os.path.isfile(path):
return 404, "File Not Found"
elif not os.access(path, os.R_OK):
return 403, "Access Denied"
else:
return 200, "OK"
def send(self, req, **kwargs): # pylint: disable=unused-argument
"""Return the file specified by the given request
#type req: C{PreparedRequest}
#todo: Should I bother filling `response.headers` and processing
If-Modified-Since and friends using `os.stat`?
"""
path = os.path.normcase(os.path.normpath(url2pathname(req.path_url)))
response = requests.Response()
response.status_code, response.reason = self._chkpath(req.method, path)
if response.status_code == 200 and req.method.lower() != 'head':
try:
response.raw = open(path, 'rb')
except (OSError, IOError) as err:
response.status_code = 500
response.reason = str(err)
if isinstance(req.url, bytes):
response.url = req.url.decode('utf-8')
else:
response.url = req.url
response.request = req
response.connection = self
return response
def close(self):
pass
(Despite the name, it was completely written before I thought to check Google, so it has nothing to do with b1r3k's.) As with the other answer, follow this with:
requests_session = requests.session()
requests_session.mount('file://', LocalFileAdapter())
r = requests_session.get('file:///path/to/your/file')
The easiest way seems using requests-file.
https://github.com/dashea/requests-file (available through PyPI too)
"Requests-File is a transport adapter for use with the Requests Python library to allow local filesystem access via file:// URLs."
This in combination with requests-html is pure magic :)
packages/urllib3/poolmanager.py pretty much explains it. Requests doesn't support local url.
pool_classes_by_scheme = {
'http': HTTPConnectionPool,
'https': HTTPSConnectionPool,
}
In a recent project, I've had the same issue. Since requests doesn't support the "file" scheme, I'll patch our code to load the content locally. First, I define a function to replace requests.get:
def local_get(self, url):
"Fetch a stream from local files."
p_url = six.moves.urllib.parse.urlparse(url)
if p_url.scheme != 'file':
raise ValueError("Expected file scheme")
filename = six.moves.urllib.request.url2pathname(p_url.path)
return open(filename, 'rb')
Then, somewhere in test setup or decorating the test function, I use mock.patch to patch the get function on requests:
#mock.patch('requests.get', local_get)
def test_handle_remote_file(self):
...
This technique is somewhat brittle -- it doesn't help if the underlying code calls requests.request or constructs a Session and calls that. There may be a way to patch requests at a lower level to support file: URLs, but in my initial investigation, there didn't seem to be an obvious hook point, so I went with this simpler approach.
To load a file from a local URL, e.g. an image file you can do this:
import urllib
from PIL import Image
Image.open(urllib.request.urlopen('file:///path/to/your/file.png'))
I think simple solution for this will be creating temporary http server using python and using it.
Put all your files in temporary folder eg. tempFolder
Go to that directory and create a temporary http server in terminal/cmd as per your OS using command python -m http.server 8000 (Note 8000 is port no.)
This will you give you a link to http server. You can access it from http://127.0.0.1:8000/
Open your desired file in browser and copy the link to your url.