SQLModel and sqlalchemy exception handling - python

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?

Related

Is it safe to reuse exception instances in Python?

In some cases, my code looks a lot cleaner if I create my exceptions up front, then raise them later:
AUTHENTICATION_ERROR = HTTPException(
status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
detail="Authentication failed",
headers={"WWW-Authenticate": "Bearer"},
)
# ...
async def current_user(token: str = Depends(oauth2_scheme)) -> User:
"""FastAPI dependency that returns the current user
If the current user is not authenticated, raises an authentication error.
"""
try:
payload = jwt.decode(token, SECRET, algorithms=["HS256"])
except JWTError:
raise AUTHENTICATION_ERROR
username = payload["sub"]
if username not in stub_users:
raise AUTHENTICATION_ERROR
return stub_users[username]
However, I've never actually seen this done and it feels quite wrong. It appears to work though.
In Python, is it safe to create Exception instances and raise them several times?
(Safe meaning that it works as expected with no surprises)
I would create a custom exception and use it.
Makes the intention clear, is very "pythonic", and you could explicitly catch (the more specific) AuthenticationError in an outer function if needed.
import fastapi
from fastapi.exceptions import HTTPException
class AuthenticationError(HTTPException):
def __init__(self):
super().__init__(
status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
detail="Authentication failed",
headers={"WWW-Authenticate": "Bearer"},
)
# ...
async def current_user(token: str = Depends(oauth2_scheme)) -> User:
"""FastAPI dependency that returns the current user
If the current user is not authenticated, raises AuthenticationError.
"""
try:
payload = jwt.decode(token, SECRET, algorithms=["HS256"])
except JWTError:
raise AuthenticationError
username = payload["sub"]
if username not in stub_users:
raise AuthenticationError
return stub_users[username]

How to avoid messy error handling in Tornado RequestHandler

Say I have a simple RequestHandler like this.
class RequestHandler(web.RequestHandler):
def get(self, id, status):
obj = self.retrieve_object(id)
obj.update({"status": status})
self.write(json.dumps(obj))
Problem is, whenever there's an error in the handler, it's gonna return an error 500 (Internal server error). Obviously I want to return an error 400 instead when the user has inputted something invalid.
So I have to add a bunch of error checking, like this:
class RequestHandler(web.RequestHandler):
def get(self, id, status):
try:
id = int(id)
except ValueError:
raise web.HTTPError(400, "Invalid id")
if status not in ("open", "closed"):
raise web.HTTPError(400, "Invalid status")
try:
obj = self.retrieve_object(id)
except ObjDoesntExistError:
raise web.HTTPError(400, "Object doesn't exist")
obj.update({"status": status})
self.write(json.dumps(obj))
The issue is that this adds a lot of bloat to the function. Is there a cleaner way to do this? Or is it unavoidable?
If you want to perform the same checks in multiple handlers, you can just create a base class:
class BaseHandler(web.RequestHandler):
def prepare(self):
id = self.path_args[0]
status = self.path_args[1]
try:
id = int(id)
except ValueError:
raise web.HTTPError(400, "Invalid id")
# ... and so on ...
Now, just inherit from this base class and the code will be reused.

CX_oracle update query is not working in AWS Lambda

try:
cursor = connection.cursor()
cursor.execute('update m_password set password = :new_password , updater_id = :updater_id_dic , update_date = :update_date_dic where user_cd = :user_cd_dic and del_flg = :del_flag_dic', queryDictionary)
# Commit
connection.commit()
# Close the cursor
cursor.close()
# Close the database connection
connection.close()
return {
'status': "success",
'errorCode': 0,
'errorMessage': json.dumps("")
}
except: cx_Oracle.IntegrityError as e:
errorObj, = e.args
return {
'status': "error",
'errorCode': errorObj.code,
'errorMessage': json.dumps(errorObj.message)
I am trying to update the value of oracle database . Database connection is alright . I can get the value from database . But update the value is not working . It shows success but value is not actually updated . Also there is no error .
also lambda log showing successfully executed . Please check this image
[1]: https://i.stack.imgur.com/PtLdy.png
I am stuck here .It will be really helpful if i get some help .
Thank you.
It's likely that you're getting an error other than an IntegrityError and don't have an explicit error handler for it. This would cause you to continue towards your this is fine return method.
In fact, updating a password shouldn't throw an integrity error at all since you're not operating on something that effects your schema or is effected by a schema constraint. According to the docs:
This exception is raised when the relational integrity of the data is affected. For example, a duplicate key was inserted or a foreign key constraint would fail.
To troubleshoot, I'd start by accepting any error here. Once you've determined what error type you've caught, you can add per exception error handling as needed.
except Exception as e:
errorObj, = e.args
return {
...

Raise custom exception for incorrect header Flask

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.

Django manually fail transaction after done with for loop

I'm trying to run over a for loop that validates objects and saves them, and I want to fail it all if at least one have failed, but only after going over all the objects. I've tried different approaches, but on all of them - even if there was an exception, at least one object was saved to DB. In the latest version, see below, I'm trying to set
transaction.set_rollback(True)
if at least on exception was raised.
try:
is_failed = False
with transaction.atomic():
for identifier, spec in spec_dict.items():
try:
spec_data = {'title':my_title,
'identifier': identifier,
'updated_by': user_id,
'created_by': user_id
}
serializer = SpecSerializer(data=spec_data)
serializer.is_valid(raise_exception=True)
serializer.save()
except DataError as DE:
print("** in DataError")
is_failed = True
pass
except ValidationError as VE:
print("** in ValidationError")
print(str(VE))
is_failed = True
pass
except Exception as Exc:
print("** inside Exception: " + str(Exc))
is_failed = True
pass
if is_failed:
transaction.set_rollback(True)
except IntegrityError:
print("** inside integrity error")
pass
Seems like the 'set_rollback' doesn't affect the transaction. Worth to mention that all our http requests are wrapped in transaction.
EDIT:
Should transaction.atomic() work for non view functions? Couldn't find answer for that
So, apparently -
transaction.atomic():
is managing the transaction for the 'default' DB by default, unless other DB is mentioned:
transaction.atomic(using='otherDB'):
Since we use more than one DB and the one that I worked on was not set as the default, I was missing the 'using'.

Categories

Resources