How to avoid messy error handling in Tornado RequestHandler - python

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.

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]

Pytest raises not working for custom exception

I have the following defined in an exceptions.py file:
class Error(Exception):
"""Base exception raised by api wrapper"""
def __init__(self, message: str):
self.message = message
super().__init__(self.message)
# HTTP response exceptions
class ApiBadRequestError(Error):
"""Bad Request –- Incorrect parameters."""
def __init__(self, message: str):
self.message = message
super().__init__(self.message)
I then have a function that correctly raises an ApiBadRequestError exception.
In pytest I'm doing the following:
def test_handle_request_response_raises_correct_exception_for_response_code(
self, status_code, exception_type, client, create_response
):
response = create_response(status_code=status_code)
with pytest.raises(ApiBadRequestError) as e:
a = client._check_response_codes(response)
which is failing the test because inside pytest.raises it's doing isintance(e, ApiBadRequestError) which is returning False. If I however change the test to the following:
def test_handle_request_response_raises_correct_exception_for_response_code(
self, status_code, exception_type, client, create_response
):
response = create_response(status_code=status_code)
with pytest.raises(Exception) as e:
a = client._check_response_codes(response)
it passes as the raised exception is seen as an instance of Exception even though it's an ApiBadRequestError
Any help would be really appreciated as I'm completely stumped here.

inner function raise in Django rest API

How can I catch a 4XX series raise in inner function of an API in Django rest framework?
from rest_framework.exceptions import ValidationError
class DummyView(APIView):
def get(self, request, id):
if id==something:
dummy_function_1(id)
else:
dummy_function_2(id)
return Response()
def dummy_function_1():
try:
validate_1(id)
except ValidationError:
raise ValidationError()
#do something with id
return id
When I send a HTTP GET request, I receive a 5XX series error if exception occurs. I want to get 400 Bad Request error in response.
After lots of effort and I don't know why,
If I specify the exception type of inner function, I will get 5XX series error.
so in dummy function I just wrote :
def dummy_function_1():
try:
validate_1(id)
except Exception: # just exception, Not ValidationError or other exception
raise ValidationError()
I could get 4XX series error
Try this:
def dummy_function_1():
try:
validate_1(id)
except Exception: # just exception, Not ValidationError or other exceptions
raise ValidationError()

How to create a custom Exception with error message and status code in python3

I am trying to create the following exception and call it in another function:
### The exception
class GoogleAuthError(Exception):
def __init__(self, message, code=403):
self.code = code
self.message = message
### Generating the exception
raise GoogleAuthError(message="There was an error authenticating")
### printing the exception
try:
do_something()
except GoogleAuthError as e:
print(e.message)
Basically, I want it to print "There was an error authenticating". How would I do this properly, or is the above the correct way to do it?
Remove the code argument from your __init__. You aren't using it.
You can also delegate the handling of the error message to the parent Exception class, which already knows about messages
class GoogleAuthError(Exception):
def __init__(self, message):
super().__init__(message)
self.code = 403
try:
raise GoogleAuthError('There was an error authenticating')
except GoogleAuthError as e:
print(e)
# There was an error authenticating

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