Raise custom exception for incorrect header Flask - python

I am building a Flask REST API which does not have a front-end yet. I've successfully developed a GET request for a Flask view that fetches all the subscribers from the SQL database. I'm using the same Flask view to INSERT VALUES through POST requests to the database. To sanitize the input, I've used (%s) as placeholder for the values. Below are excerpts from the code:
#main.py
#app.route('/api/subscribe/', methods=['GET', 'POST'])
def subscribe():
if request.method == 'GET':
try:
data = DB.get_subscription_list()
return Response(json.dumps({'result': data}, cls=DateTimeEncoder, sort_keys=True), mimetype='application/json')
except Exception as e:
return e
elif request.method == 'POST':
try:
email = request.form.get('email')
data = DB.add_email(email)
return Response(json.dumps({'result': 'Email added to database'}, cls=DateTimeEncoder, sort_keys=True), mimetype='application/json')
except Exception as e:
return e
#dbhelper.py
def add_email(self,data):
connection = self.connect()
try:
#The following adds an email entry to the 'users' table.
query = "INSERT INTO users (email) VALUES (%s);"
with connection.cursor() as cursor:
cursor.execute(query,data)
connection.commit()
except pymysql.ProgrammingError as e:
print(e)
raise e
finally:
connection.close()
I have edited this question with more precise information and specific to my problem.
I am currently testing the API on RESTClient and Postman.
When I send a POST request with header {'Content-Type':'application/x-www-form-urlencoded'}, the value inserts successfully. Output: {"result": "Email added to database"}. If this header is not used, I get a Programmingerror exception below:
(1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '%s)' at line 1")
How can I raise a custom exception for the incorrect header entry so that it raises this custom exception rather than show a syntax error?Of course, this is not a syntax error as the value is inserted successfully when using the urlencoded header.

I solved this issue by creating custom error handlers for each error status code. I realized that the mimetype {'Content-Type':'application/x-www-form-urlencoded'} header is required when I use request.form.get(). This is because the information in the form is encoded when it is sent to the server. I have now used request.json.get() as I would like to send the data in json instead, the header now required is {'Content-Type':'application/json'} mimetype. So, now my view looks like this:
#subscribe.py
#app.route('/api/users/subscribe', methods=["GET", "POST"])
def subscribe():
if request.method == "GET":
try:
data = DB.get_subscription_list()
return Response(json.dumps({'result': data}, cls=DateTimeEncoder, sort_keys=True), mimetype='application/json', status=200)
except Exception:
return Response(json.dumps({'error': 'Could not fetch subscribers from database.'}), status=500)
elif request.method == "POST":
try:
email = request.json.get("email")
data = DB.add_email(email)
return Response(json.dumps({'result': 'Email added to database.'}, cls=DateTimeEncoder, sort_keys=True), mimetype='application/json', status=201)
except Exception:
return Response(json.dumps({'error': 'Could not add to database.'}), status=500)
Notice, the custom error exception above for each method. I also created custom error handlers in cases where an error appears with a status code and I haven't explicitly defined an exception for them . For example, if I get an error with status code 500 which renders in html, the error handler below will show a custom error in json. I've added similar error handlers for status codes 405, 401 etc. so that I always get custom errors injson.
#app.errorhandler(500)
def method_not_allowed(error):
return make_response(jsonify({'error': 'An error occurred during a request'}), 500)
Similarly, I added an exception for database queries:
#dbhelper.py
try:
#The following adds an email entry to the 'users' table.
uri = url_for('subscribe', email=email, _external=True)
query = "INSERT INTO users (email) VALUES (%s);"
with connection.cursor() as cursor:
cursor.execute(query,email)
connection.commit()
except Exception:
return Response(json.dumps({'error':'Database connection error.'}), status=500)
Now, I was getting json exceptions when I don't send data in json or invalid data in my POST requests. There are no Programmingerrors anymore as I have corrected my code to respond with desired results.
A more organized way is to define custom error classes in a separate errors.py file and call the exception functions whenever necessary.

Related

SQLModel and sqlalchemy exception handling

I am trying to understand exception handling in SQLModel and SqlAlchemy.
Assume I have this code
def create_blog(blog: BlogCreate, session: Session = Depends(get_session)):
try:
result = Blog(title=blog.title, body=blog.body, author_id=blog.author_id)
session.add(result)
session.commit()
session.refresh(result)
except exc.IntegrityError:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Author with {blog.author_id} not found"
)
except:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Something went wrong"
)
return result
Here, author_id is a foreign key. Now Suppose if I pass it some id for which author doesn't exist (and not doing try except), It is going to throw an error (in my terminal logs).
DETAIL: Key (author_id)=(5) is not present in table "author".
How can I get this message so that I can pass it to user?

How to call a normal python function along with header information without using requests

Details of application:
UI: Angular
Backend: Python Flask (using Swagger)
Database: MongoDB
We have a few backend python methods which will be called from the UI side to do CURD operations on the database.
Each of the methods has a decorator which will check the header information to ensure that only a genuine person can call the methods.
From the UI side when these API's are called, this authorization decorator is not creating any problem and a proper response is returned to the UI (as we are passing the header information also to the request)
But now we are writing unit test cases for the API's. Here each test case will call the backend method and because of the authorization decorator, I am getting errors and not able to proceed. How can I handle this issue?
backend_api.py
--------------
from commonlib.auth import require_auth
#require_auth
def get_records(record_id):
try:
record_details = records_coll.find_one({"_id": ObjectId(str(record_id))})
if record_details is not None:
resp = jsonify({"msg": "Found Record", "data": str(record_details)})
resp.status_code = 200
return resp
else:
resp = jsonify({"msg": "Record not found"})
resp.status_code = 404
return resp
except Exception as ex:
resp = jsonify({"msg": "Exception Occured",'Exception Details': ex}))
resp.status_code = 500
return resp
commonlib/auth.py
-----------------
### some lines of code here
def require_auth(func):
"""
Decorator that can be added to a function to check for authorization
"""
def wrapper(*args, **kwargs):
print(*args,**kwargs)
username = get_username()
security_log = {
'loginId': username,
'securityProtocol': _get_auth_type(),
}
try:
if username is None:
raise SecurityException('Authorization header or cookie not found')
if not is_auth_valid():
raise SecurityException('Authorization header or cookie is invalid')
except SecurityException as ex:
log_security(result='DENIED', message=str(ex))
unauthorized(str(ex))
return func(*args, **kwargs)
return wrapper
test_backend_api.py
-------------------
class TestBackendApi(unittest.TestCase):
### some lines of code here
#mock.patch("pymongo.collection.Collection.find_one", side_effect=[projects_json])
def test_get_records(self, mock_call):
from backend_api import get_records
ret_resp = get_records('61729c18afe7a83268c6c9b8')
final_response = ret_resp.get_json()
message1 = "return response status code is not 200"
self.assertEqual(ret_resp.status_code, 200, message1)
Error snippet :
---------------
E RuntimeError: Working outside of request context.
E
E This typically means that you attempted to use functionality that needed
E an active HTTP request. Consult the documentation on testing for
E information about how to avoid this problem.

how to improve exception handling in python/django

This is an example of my exception handling in a django project:
def boxinfo(request, url: str):
box = get_box(url)
try:
box.connect()
except requests.exceptions.ConnectionError as e:
context = {'error_message': 'Could not connect to your box because the host is unknown.'}
return render(request, 'box/error.html', context)
except requests.exceptions.RequestException as e:
context = {'error_message': 'Could not connect to your box because of an unknown error.'}
return render(request, 'box/error.html', context)
There is only two excepts now, but it should be more for the several request exceptions. But already the view method is bloated up by this. Is there a way to forward the except handling to a separate error method?
There is also the problem, that I need here to call the render message for each except, I would like to avoid that.
And here I also repeat for each except "could not connect to your box because", that should be set once when there appeared any exception.
I can solve it by something like this:
try:
box.connect()
except Exception as e:
return error_handling(request, e)
-
def error_handling(request, e):
if type(e).__name__ == requests.exceptions.ConnectionError.__name__:
context = {'error_message': 'Could not connect to your box because the host is unknown.'}
elif type(e).__name__ == requests.exceptions.RequestException.__name__:
context = {'error_message': 'Could not connect to your box because of an unknown error.'}
else:
context = {'error_message': 'There was an unkown error, sorry.'}
return render(request, 'box/error.html', context)
and I could of course improve the error message thing then. But overall, is it a pythonic way to handle exceptions with if/else? For example I could not catch RequestException here if ConnectionError is thrown, so I would need to catch each requests error, that looks more like an ugly fiddling...
This is a use case for decorators. If it's something more general that applies to all views (say, error logging), you can use the Django exception middleware hook, but that doesn't seem to be the case here.
With respect to the repetitive error string problem, the Pythonic way to solve it is to have a constant base string with {replaceable_parts} inserted, so that later on you can .format() them.
With this, say we have the following file decorators.py:
import functools
from django.shortcuts import render
from requests.exceptions import ConnectionError, RequestException
BASE_ERROR_MESSAGE = 'Could not connect to your box because {error_reason}'
def handle_view_exception(func):
"""Decorator for handling exceptions."""
#functools.wraps(func)
def wrapper(request, *args, **kwargs):
try:
response = func(request, *args, **kwargs)
except RequestException as e:
error_reason = 'of an unknown error.'
if isinstance(e, ConnectionError):
error_reason = 'the host is unknown.'
context = {
'error_message': BASE_ERROR_MESSAGE.format(error_reason=error_reason),
}
response = render(request, 'box/error.html', context)
return response
return wrapper
We're using the fact that ConnectionError is a subclass of RequestException in the requests library. We could also do a dictionary with the exception classes as keys, but the issue here is that this won't handle exception class inheritance, which is the kind of omission that generates subtle bugs later on. The isinstance function is a more reliable way of doing this check.
If your exception tree keeps growing, you can keep adding if statements. In case that starts to get unwieldy, I recommend looking here, but I'd say it's a code smell to have that much branching in error handling.
Then in your views:
from .decorators import handle_view_exception
#handle_view_exception
def boxinfo(request, url: str):
box = get_box(url)
box.connect()
...
That way the error handling logic is completely separate from your views, and best of all, it's reusable.
could you have something like this:
views.py
EXCEPTION_MAP = {
ConnectionError: "Could not connect to your box because the host is unknown.",
RequestException: "Could not connect to your box because of an unknown error.",
}
UNKNOWN_EXCEPTION_MESSAGE = "Failed due to an unknown error."
def boxinfo(request, url: str):
box = get_box(url)
try:
box.connect()
except (ConnectionError, RequestException) as e:
message = EXCEPTION_MAP.get(type(e)) or UNKNOWN_EXCEPTION_MESSAGE
context = {'error_message': message}
return render(request, 'box/error.html', context)
You could then just expand the EXCEPTION_MAP and the except () for any other known exception types you're expecting to catch?
if you want to reduce the duplication of "Could not connect to your box because ...
You could maybe do:
views.py
BASE_ERROR_STRING = "Could not connect to your box because {specific}"
EXCEPTION_MAP = {
ConnectionError: "the host is unknown.",
RequestException: "of an unknown error.",
}
UNKNOWN_EXCEPTION_MESSAGE = "Failed due to an unknown error."
def boxinfo(request, url: str):
box = get_box(url)
try:
box.connect()
except (ConnectionError, RequestException) as e:
specific_message = EXCEPTION_MAP.get(type(e))
if specific_message:
message = BASE_ERROR_STRING.format(specific=specific_message)
else:
message = UNKNOWN_EXCEPTION_MESSAGE
context = {'error_message': message}
return render(request, 'box/error.html', context)

Extracting json dict from POST request

I want to make use of webhook provided by Townscript to update the is_paid field in my model. The format of the data is (a json dict):- link(Under server notification API column). A piece of useful information on the link was: We will send the data in following format using post parameter data.
Here is the python code:
def payment(request):
if request.method == "POST":
posts=request.POST["data"]
result=json.dumps(posts) #converting incoming json dict to string
paymentemail(result) # emailing myself the string (working fine)
data = posts
try:
user = Profile.objects.filter(email=data['userEmailId']).first()
user.is_paid = True
user.save()
except Exception as e: paymentemail(str(e)) # emailing myself the exception
return redirect('/home')
else:
.......
The two emails correspoinding to the paymentemail() function in the above code were:
"{\"customQuestion1\":\"+9175720*****\",\"customAnswer205634\":\"+917572******\",
\"customQuestion2\":\"**** University\",\"customQuestion205636\":\"College Name\",\"ticketPrice\":**00.00,
\"discountAmount\":0.00,\"uniqueOrderId\":\"***********\",\"userName\":\"********\",
\"customQuestion205634\":\"Contact Number\",\"eventCode\":\"**********\",\"registrationTimestamp\":\"12-12-2019 22:22\",
\"userEmailId\":\"***********#gmail.com\",etc....}"
I understand that the backslashes are for escaping the quotation marks.
Second email: (which is the exception)
string indices must be integers
Does that mean that data=request.POST['data'] gives me a string thus leading to an error when I use data['userEmailId']? How do I deal with this error?
def payment(request):
if request.method == "POST":
posts=request.POST
result=json.dumps(posts) #converting incoming json dict to string
paymentemail(result) # emailing myself the string (working fine)
try:
user = Profile.objects.filter(email=posts['userEmailId']).first()
user.is_paid = True
user.save()
except Exception as e: paymentemail(str(e)) # emailing myself the exception
return redirect('/home')
else:
.......
I changed posts=request.POST["data"] since the will get only the value of key "data", instead I use posts=request.POST so that we will get the entire request as key-value pair (dict).
user = Profile.objects.filter(email=posts['userEmailId']).first()

Returning error indication when flask POST Request fails

I'm trying to create a simple flask application that takes a string from an iOS application and stores it in a local data base. I'm a bit confused whats happening in the return portion of the submitPost() function. I'm trying to return a dictionary that contains a BOOL that indicates whether the Post request executed fully. However, i'm not sure how to variate between returning a 0 or a 1.
//Function that handles the Post request
#app.route('/submitPost/', methods=['POST'])
def submitPost():
post = Post.from_json(request.json)
db.session.add(post)
db.session.commit()
return jsonify(post.to_json), {'Success': 1}
Try the below. This way if an exception is thrown when trying to insert data it will be caught and the transaction will be rolled back as well as a response being send back.
you could also replace the zero and one with True & False depending on your preference
Below code hasn't been tested
#app.route('/submitPost/', methods=['POST'])
def submitPost():
try:
post = Post.from_json(request.json)
db.session.add(post)
db.session.commit()
return jsonify({'Success': 1})
except Exception: # replace with a more meaningful exception
db.session.rollback()
return jsonify{'Failure': 0}

Categories

Resources