|
|
|
|
|
by kstrauser
2326 days ago
|
|
For example, in a web service I maintain, we maintain an internal exception hierarchy like: class OurExceptions:
http_status_code = 500
class DatabaseRecordNotFoundError:
http_status_code = 404
Database queries are all written like: rows = db.select(...)
if not rows:
raise DatabaseRecordNotFoundError
and the top-level Flask error handler has code like: try:
return call_view()
except OurExceptions as exc:
return response(status_code=exc.http_status_code)
This is grossly oversimplified, but you get the idea. So, this means that we can write views like: def some_view(object_id):
obj = fetch_obj_from_db(object_id)
return {"found": obj.name}
If the object isn't found, the caller gets a 404 response without the person writing the view having to do a single thing. However, they can still handle the problem themselves if they really want to: def another_view(object_id):
try:
obj = fetch_obj_from_db(object_id)
except DatabaseRecordNotFoundError:
return {"error": "Not found. Try again later?"}, 404
return {"found": obj.name}
I absolutely love this coding style because exceptions are still being handled everywhere, but don't have to be explicitly dealt with deep inside a nested call stack. That lets us write very uncluttered, testable view code like: def change_password(userid, oldpass, newpass):
# This raises an exception if the user can't be found
user = get_user_by_id(userid)
# This raises an exception if the old password is wrong
verify_password(user, oldpass)
# This raises an exception if the DB couldn't be updated,
# perhaps because of a race condition with another request
update_password(user, newpass)
# By the time we get to this line, everything above has to have
# succeeded, with zero manual error checking inside this view
return {"result": "Password successfully updated."}
|
|