Problem Statement
Frappe Framework raises errors through the frappe.throw
wrapper and allows the developer to specify many properties regarding the error. This includes the exception to raise, and the message to display to the end user.
This is great, until the developer handles an exceptions raised by frappe.throw
. The error message is still displayed to the end user.
The developer can omit these messages by setting frappe.flags.mute_messages = True
This can be dangerous because if the developer doesn’t reset the flag after they’ve handled the exception, then no unhandled exceptions will be displayed to the user.
So, what is the appropriate way to handle exceptions in frappe?
Examples
Case 1:
@frappe.whitelist()
def test_1():
"""
This case we will attempt to load a non-existing document without handling any exceptions.
Observation and Expected Results:
1. The exception frappe.DoesNotExistError is raised.
2. The api call returns a status code of 404.
3. A dialog is displayed to the user regarding the error that occurred.
"""
frappe.get_doc("DocType", "Foo")
Case 2:
@frappe.whitelist()
def test_1a():
"""
Case Description:
This case we will attempt to load a non-existing document while handing a single expected exception
frappe.DoesNotExistError
Observed Results:
1. The exception frappe.DoesNotExistError is raised.
2. We catch the exception. (For demonstration, we aren't actually doing anything with the caught exception)
3. The api call return a status code of 200
4. A dialog id displayed to the user regarding the error that was caught.
Expected Results:
Don't display an error dialog in regards to an exception that has been caught.
"""
try:
test_1()
except frappe.DoesNotExistError:
pass # Do something useful :p
Case 3:
@frappe.whitelist()
def test_1b():
"""
Case Description:
Repeat the same case as test_1a except we will enable the flag mute_messages to avoid an error dialog of the
already caught exception.
Observed and Expected results:
1. The exception is handled
2. api responds with status 200
3. the end user knows the action they performed is successful.
"""
frappe.flags.mute_messages = True
test_1a()
Case 4:
@frappe.whitelist()
def test_1c():
"""
Case Description:
Execute test_1b knowing that all known exceptions will be handled.
Then attempt to add an already existing document without handling any possible exceptions.
Observed Results:
1. The exception frappe.DuplicateEntryError is raised
2. api responds with status code 409
3. The traceback to the error is posted to the browser console.
4. The end user has no idea their request didn't work. (If they don't have the browser console open)
Expected Results:
1. The exception frappe.DoesNotExistError is raised and handled. (Don't report to user since it's been handled)
2. The exception frappe.DuplicateEntryError is raised
3. The api responds with a client error.
4. The user is notified by the error dialog of their mistake.
"""
test_1b()
frappe.get_doc({
"doctype": "Gender",
"gender": "Male"
}).insert()
Possible Solutions ?
frappe.flags.mute_messages
By setting the frappe.flags.mute_messages
flag before an expected exception is to occur, then resetting it after the exception has been handled kind of works.
Only if frappe.DoesNotExistError
is raised during the try block this method will work, if any other exception is raised it will not be displayed to the user.
@frappe.whitelist()
def test_2():
"""
Case Description:
Catch frappe.DoesNotExistError while muting messages. Then reset mute_messages after handling the error.
Observed and Expected Results:
1. The frappe.DoesNotExistError does not get presented to the user since it's been handled.
2. The frappe.DuplicateEntryError is displayed to the user.
"""
try:
frappe.flags.mute_messages = True
test_1()
except frappe.DoesNotExistError:
pass
finally:
frappe.flags.mute_messages = False
frappe.get_doc({
"doctype": "Gender",
"gender": "Male"
}).insert()
Avoid exceptions?
Developing to avoid exceptions is the best way in my option to avoid this.
@frappe.whitelist()
def test_3():
if frappe.db.exists("DocType", "Foo"):
test_1()
if not frappe.db.exists("Gender", "Male"):
frappe.get_doc({
"doctype": "Gender",
"gender": "Male"
}).insert()
To conclude
Should developers handle exceptions via try - except statements, or avoid exceptions using logical conditions?