Is it safe to reuse exception instances in Python? - 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]

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 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.

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)

Handle Facebook Deauthorize Callback in Python

I am building a Django Application that makes use of the Facebook Login using django-allauth. I would like to know when an user removes my corresponding Facebook App, and Facebook provides such functionality in the form of a deauthorize callback. There are also some instructions on how to parse the request using PHP in the documentation.
However, translating this into Python doesn't seem to be as easy as I thought, as I am getting 'Padding Errors' when decoding the posted base64-encoded string, which seems very odd to me.
The problem seems to be that a certain padding has to be manually added to the posted data. Here is a working example:
class DeauthorizeView(View):
def post(self, request, *args, **kwargs):
try:
signed_request = request.POST['signed_request']
encoded_sig, payload = signed_request.split('.')
except (ValueError, KeyError):
return HttpResponse(status=400, content='Invalid request')
try:
# Reference for request decoding: https://developers.facebook.com/docs/games/gamesonfacebook/login#parsingsr
# For some reason, the request needs to be padded in order to be decoded. See https://stackoverflow.com/a/6102526/2628463
decoded_payload = base64.urlsafe_b64decode(payload + "==").decode('utf-8')
decoded_payload = json.loads(decoded_payload)
if type(decoded_payload) is not dict or 'user_id' not in decoded_payload.keys():
return HttpResponse(status=400, content='Invalid payload data')
except (ValueError, json.JSONDecodeError):
return HttpResponse(status=400, content='Could not decode payload')
try:
secret = SocialApp.objects.get(id=1).secret
sig = base64.urlsafe_b64decode(encoded_sig + "==")
expected_sig = hmac.new(bytes(secret, 'utf-8'), bytes(payload, 'utf-8'), hashlib.sha256)
except:
return HttpResponse(status=400, content='Could not decode signature')
if not hmac.compare_digest(expected_sig.digest(), sig):
return HttpResponse(status=400, content='Invalid request')
user_id = decoded_payload['user_id']
try:
social_account = SocialAccount.objects.get(uid=user_id)
except SocialAccount.DoesNotExist:
return HttpResponse(status=200)
# Own custom logic here
return HttpResponse(status=200)

How to handle all exceptions in one place for several endpoints?

I'm using pynic framework to handle my APIs endpoints, but I guess this would be the same logic with Flask or Django.
I've got a few endpoints, and I was wondering if there were anyway to handle all the exceptions at the same place.
For instance:
class Pnorm(Handler):
def post(self):
logger = logging.getLogger(constants.loggerName)
template_exception = "Exception {0} in class {1} ({2})."
try:
myJson = DoThings()
return myJson
except HTTP_400 as e:
logger.critical(message)
raise e
except Exception as e:
# unknown exception raise 500
logger.critical(message)
raise HTTP_500(message)
Is there anyway I can make all my endpoints handle the exceptions the same way or do I hacve to repeat my "exception block" at the end of each point ?
(I don't mean in the same class only but through the project.)
Cheers,
Julien
Edited:
My main class:
class app(WSGI):
DataStructureHelper.set_dsh()
setup_logging.setup_logging(logger_name=constants.loggerName, console_level=logging.INFO)
routes = [
('/allocator', Allocator()),
('/data/([0-9]+)/([0-9]+)/([0-9]+)/([0-9]+)', InstrumentData()),
('/pnorm', Pnorm()),
('/portfolios')
]
I think the right approach would be decorators, since it fits the needs perfectly. Following is working piece of code w.r.t flask.
A word of caution is you need return the control back to handlers from decorator.
from functools import wraps
from flask import Flask, request
app = Flask(__name__)
def http_error_codes(method_name):
#wraps(method_name)
def handle_exceptions(*args):
try:
print("Inside the exceptions")
return method_name(*args)
except Exception as e:
print("HAHAHAHA")
raise e
return handle_exceptions
def do_the_login():
return "Testing is fun"
def show_the_login_form():
raise ValueError('The day is too frabjous.')
#app.route('/login', methods=['GET', 'POST'])
#http_error_codes
def login():
if request.method == 'POST':
return do_the_login()
else:
return show_the_login_form()
if __name__ == '__main__':
app.run()
Hope this helps
If you want to handle all the exceptions in a single place, you can keep an general exceptional block like as follows..
try:
#Code part may give error
except Exception:
#If error what to do..
Here Exception is the General class, which will handle all exceptions irrespective of the error.

Categories

Resources