Unittest : How test upload file function with request.file - python-3.x

I am new at unittest and I want test Function that upload an .ond file but I still don't know how can I use request.file correctly to upload file and send in test : here is my code and test code
def upload_ond_file():
if 'ondFile' not in request.files:
return 'No file'
directory = 'src/BDDs/BDD_Onduleurs'
if not os.path.isdir(path + directory):
os.mkdir(path + directory)
file = request.files['ondFile']
file.save(os.path.join(path + directory, file.filename))
The test was like :
import unittest
from urllib import response
from flask_unittest import ClientTestCase
from requests import request
import src.Controller.InverterController as InverterController
from unittest import mock
import io
class SomefileTestCase(unittest.TestCase):
def test_method_called_from_route(self):
m = mock.MagicMock()
request.method = "POST"
testfile
=open('C:/optisolar/optisolarbe/src/Test/uploads/test_controller_inverter/20170131_SC 2500-EV_MS_V6.ond',"r")
data= testfile.read()
data = {'files[]': testfile}
with mock.patch("src.Controller.InverterController.request", m):
result = InverterController.upload_ond_file()
print(result)
if __name__ == '__main__':
unittest.main()
but when execute code it mentione that there is no file :
request.url = <MagicMock name='mock.url' id='1807586099888'>
request.path = <MagicMock name='mock.path' id='1807586136320'>
request.data = <MagicMock name='mock.data' id='1807586164448'>
request.method = <MagicMock name='mock.method' id='1807586192640'>
request.files = <MagicMock name='mock.files' id='1807586224928'>
request.filesname = <MagicMock name='mock.files.name' id='1807586253120'>
request.filespath = <MagicMock name='mock.files.path' id='1807586289360'>
request.values = <MagicMock name='mock.values' id='1807586317600'>
request.args = <MagicMock name='mock.args' id='1807586345984'>
request.form = <MagicMock name='mock.form' id='1807586374176'>
request.getjson = <MagicMock name='mock.get_json()' id='1807586422544'>
No file
C:\Python39\lib\unittest\case.py:550: ResourceWarning: unclosed file <_io.TextIOWrapper name='C:/optisolar/optisolarbe/src/Test/uploads/test_controller_inverter/20170131_SC 2500-EV_MS_V6.ond' mode='r' encoding='cp1252'>
method()
ResourceWarning: Enable tracemalloc to get the object allocation traceback
.
----------------------------------------------------------------------
Ran 1 test in 0.025s
OK
my question is how can I upload file (request.file) correctly and send it in test ! thank you in advance

Related

Uploading images to a Flask API using FlaskClient

I have the following endpoint in my Flask API:
#api.route("/files", methods=Method.POST)
def upload_file() -> str:
enquirer_id = request.get_json()['id']
try:
logging.info(request.files)
uploaded_file = request.files['image']
except KeyError:
abort(HttpStatusCode.BAD_REQUEST, "Please provide image to upload")
if uploaded_file.content_type != ContentType.JPEG:
abort(HttpStatusCode.BAD_REQUEST, "Only JPEG images are allowed")
filename = str(enquirer_id)
destination_file = os.path.join(current_app.config[AppCfg.DIRECTORY], filename)
uploaded_file.save(destination_file)
return f'Successfully upload file {filename}.'
But I am unable to test this endpoint using the FlaskClient:
class APITestUploadDownload(APITestBase):
url = '/api/v1/files'
my_file = 'some_picture.jpg'
def setUp(self):
self.client = self.app.test_client()
def test_upload(self):
with open(file=self.my_file, mode='rb') as file:
data = {
'image': io.BytesIO(file.read()),
'id': 1
}
response = self.client.post(self.url, data=json.dumps(data), headers={}, content_type='multipart/form-data',)
self.assertEqual(response.status_code, HttpStatusCode.OK)
But this fails with the error message TypeError: Object of type BytesIO is not JSON serializable.
If i make the request without using json.dump(data) using response = self.client.post(self.url, data=data, headers={}, content_type='multipart/form-data', ) I get another error which tells me thatresponse.data is None.
So how can I fix my file upload endpoint and the correspondig test?

Using pytest fixtures in class

I have begun writing unit tests for my Flask API. I have gotten them to work when declared outside of a class. However, for simplicity and OOP constraints, I am trying to have everything run from a class. The issue is I cannot seem to pass any fixture methods to my test class. The code I have here is as follow:
#conftest.py
import os, json, pytest
from ..app import create_app
from flask import Flask
#pytest.fixture
def env_setup():
env_name = os.getenv('FLASK_ENV')
app = create_app(env_name)
return app
I am trying to import env_setup into the following file.
# test_BaseURL.py
import pytest
#pytest.mark.usefixtures("env_setup")
class TestStaticPages:
def setUp(self, env_setup):
"""
Setup Test
"""
self.client = env_setup.test_client()
def test_base_route(self, env_setup):
#client = env_setup.test_client()
url = '/'
html1 = b'Welcome to the API. Please visit '
html2 = b'https://example.com to learn more about this app.'
response = self.client.get(url)
assert response.get_data() == html1 + html2
assert response.status_code == 200
I keep geeting the following error when I run this test:
> response = self.client.get(url)
E AttributeError: 'TestStaticPages' object has no attribute 'client'
src/tests/test_BaseURL.py:18: AttributeError
However if I should uncomment the line with client = env_setup.test_client() it works. For whatever reason it cannot seem to grab the setup from the setUP method and keeps erroring out.
Here is how I fixed my issue:
#conftest.py
import os, json, pytest
from ..app import create_app
from flask import Flask
#pytest.fixture
def client():
env_name = os.getenv('FLASK_ENV')
app = create_app(env_name)
client = app.test_client()
return client
I was then able to import the client into my other test class like so.
#test_StaticView.py
import pytest
#pytest.mark.usefixtures("client")
class TestStaticPages:
def test_base_route(self, client):
url = '/'
html1 = b'Welcome to the API. Please visit '
html2 = b'https://example.com to learn more about this app.'
response = client.get(url)
assert response.get_data() == html1 + html2
assert response.status_code == 200

File upload using Flask

I am trying to implement a python API in order to upload a file on my server but for an unknown reason, it doesn't run.
From my understanding, the app.py is not recognised
Here is my API.py
from flask_cors import CORS
from flask_restful import Api, Resource, reqparse
import sqlite3
import uuid
import os
import csv
import urllib.request
import threading
import queue as Queue
import subprocess
import json
import re
import datetime
app = Flask(__name__)
api = Api(app)
CORS(app)
class upload(Resource):
def post(self):
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def upload_file():
# check if the post request has the file part
if 'file' not in request.files:
resp = jsonify({'message' : 'No file part in the request'})
resp.status_code = 400
return resp
file = request.files['file']
int = str(request.form['int']) #true or false
if file.filename == '':
resp = jsonify({'message' : 'No file selected for uploading'})
resp.status_code = 400
return resp
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return json.dumps(data), 200
else:
resp = jsonify({'message' : 'Allowed file types are doc, etc.'})
resp.status_code = 400
return resp
api.add_resource(upload, "/api/v1/upload")
app.run(host='0.0.0.0', debug=True)
Here is my app.py
UPLOAD_FOLDER = '/home/xxxx/xxx/upload'
app = Flask(__name__)
#app.secret_key = "secret key"
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
Can you please help? Any suggestions?
As per the comment, it looks like you have two sepearate applications here.
I would just stick with the first one API.py, but you'll need to move the lines where you set the config variables into API.py:
So after this line in API.py:
app = Flask(__name__)
Immediately set the config values:
UPLOAD_FOLDER = '/home/xxxx/xxx/upload'
#app.secret_key = "something super secret"
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
Then execute with:
python API.py

How to set the path while working on images in Flask on localhost as well as ibm cloud

This application was running fine till yesterday but today it isn't working. No changes have been made in backend or front end. The site Output is like this. I am trying to use open cv to extract 4 sub images from a image which has been uploaded in ../static/uploads folder. While the extracted 4 images are stored in ../static/extracted folder. Now my concern is, yesterday the code was running but today it isn't working. And while deploying to the ibm cloud, I could not find open cv. "cv2 module not found". Can someone please help me improve this code.
app.py code:
from cloudant import Cloudant
from flask import Flask, render_template, request, jsonify
import atexit
import os
import json
from ocr_core import ocr_core
UPLOAD_FOLDER = '/static/uploads/'
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])
app = Flask(__name__,static_url_path='')
db_name = 'mydb'
client = None
db = None
if 'VCAP_SERVICES' in os.environ:
vcap = json.loads(os.getenv('VCAP_SERVICES'))
print('Found VCAP_SERVICES')
if 'cloudantNoSQLDB' in vcap:
creds = vcap['cloudantNoSQLDB'][0]['credentials']
user = creds['username']
password = creds['password']
url = 'https://' + creds['host']
client = Cloudant(user, password, url=url, connect=True)
db = client.create_database(db_name, throw_on_exists=False)
elif "CLOUDANT_URL" in os.environ:
client = Cloudant(os.environ['CLOUDANT_USERNAME'], os.environ['CLOUDANT_PASSWORD'], url=os.environ['CLOUDANT_URL'], connect=True)
db = client.create_database(db_name, throw_on_exists=False)
elif os.path.isfile('vcap-local.json'):
with open('vcap-local.json') as f:
vcap = json.load(f)
print('Found local VCAP_SERVICES')
creds = vcap['services']['cloudantNoSQLDB'][0]['credentials']
user = creds['username']
password = creds['password']
url = 'https://' + creds['host']
client = Cloudant(user, password, url=url, connect=True)
db = client.create_database(db_name, throw_on_exists=False)
# On IBM Cloud Cloud Foundry, get the port number from the environment variable PORT
# When running this app on the local machine, default the port to 8000
port = int(os.getenv('PORT', 5000))
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
#app.route('/')
def home_page():
return render_template('index.html')
#app.route('/upload', methods=['GET', 'POST'])
def upload_page():
if request.method == 'POST':
# check if the post request has the file part
if 'file' not in request.files:
return render_template('upload.html', msg='No file selected')
file = request.files['file']
if file.filename == '':
return render_template('upload.html', msg='No file selected')
if file and allowed_file(file.filename):
file.save(os.path.join(os.getcwd() + UPLOAD_FOLDER, file.filename))
extracted_text = ocr_core(file)
return render_template('upload.html',
msg='Successfully processed',
extracted_text=extracted_text,
img_src=UPLOAD_FOLDER + file.filename)
elif request.method == 'GET':
return render_template('upload.html')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=port, debug=True)
ocr_py.py:
try:
from PIL import Image
except ImportError:
import Image
import pytesseract
import cv2
from random import randint
import pandas as pd
#UPLOAD_FOLDER = '/static'
def ocr_core(filename):
#text = pytesseract.image_to_string(Image.open(filename))
val=str(filename)
val=list(val)
naam=""
tt=0
for i in range(15,len(val)):
if(val[i]!="'"):
naam+=val[i]
if(val[i]=='g'):
tt=1
if(tt==1):
break
image = cv2.imread("/home/sahil/CheckCheque-deploy/static/uploads/" +str(naam))
k=randint(0, 999999)
cropped1 = image[290:500, 320:1540]
cv2.imwrite("/home/sahil/CheckCheque-deploy/static/extracted/name"+str(k)+".png", cropped1)
name=pytesseract.image_to_string(Image.open("/home/sahil/CheckCheque-deploy/static/extracted/name"+str(k)+".png"))
cropped2 = image[470:700, 670:2640]
cv2.imwrite("/home/sahil/CheckCheque-deploy/static/extracted/amount"+str(k)+".png", cropped2)
amount=pytesseract.image_to_string(Image.open("/home/sahil/CheckCheque-deploy/static/extracted/amount"+str(k)+".png"))
cropped3 = image[850:1000, 480:1040]
cv2.imwrite("/home/sahil/CheckCheque-deploy/static/extracted/acc_no"+str(k)+".png", cropped3)
acc_no=pytesseract.image_to_string(Image.open("/home/sahil/CheckCheque-deploy/static/extracted/acc_no"+str(k)+".png"))
cropped5 = image[500:850, 2940:4500]
cv2.imwrite("/home/sahil/CheckCheque-deploy/static/extracted/amt_num"+str(k)+".png", cropped5)
amt_num=pytesseract.image_to_string(Image.open("/home/sahil/CheckCheque-deploy/static/extracted/amt_num"+str(k)+".png"))
acc_no1=""
lnum=["1","0","2","3","4","5","6","7","8","9"]
for i in range(0,len(acc_no)):
if(str(acc_no[i]) in lnum):
acc_no1+=acc_no[i]
l=[name,acc_no,amt_num]
df = pd.read_csv("/home/sahil/CheckCheque-deploy/jobchahiye.csv")
df.loc[df.Account== int(l[1]), 'Amount'] -=int(l[2])
df.loc[df.Name== str(l[0]), 'Amount'] +=int(l[2])
df.to_csv("/home/sahil/CheckCheque-deploy/jobchahiye.csv", index=False)
return l
Looks like you have a dependency on either opencv-python or opencv-contrib-python. As such it needs to be installed as a pre-requisite on any machine that you want to run your application.
On localhost you do this with pip, but if you are not running something like virtualenv then all installs will be to the global space, and conflicts can easily arise, resulting in you not being able to run anything.
For cloud deployments you specify pip prerequisites in your requirements.txt file

pytest mocking with boto3

just learning python mocking in general and struggling with using Magicmock and pytest with boto3.
Here is my code block
def upload_to_s3(self, local_file, bucket, dest_file):
self.local_file = local_file
self.bucket = bucket
self.dest_file = dest_file
s3_client = self.prime_s3_client() # this method returns the boto3 client
try:
s3_client.upload_file(local_file, bucket, dest_file)
LOG_IT.info('File uploaded to S3 from: %s to %s.', local_file, dest_file)
except Exception:
LOG_IT.critical('The %s failed to upload to S3.', local_file)
This is the test that's not working:
def test_upload_to_s3(self, monkeypatch, aws):
mock_s3_client = MagicMock()
monkeypatch.setattr(boto3, 'client', mock_s3_client)
mock_upload_file = MagicMock()
monkeypatch.setattr(mock_s3_client, 'upload_file', mock_upload_file)
push_to_s3 = aws.upload_to_s3('localfile', 'chumbucket', 'destfile')
mock_upload_file.assert_called()
The error returned:
E AssertionError: Expected 'upload_file' to have been called.
Thank you!

Resources