cpython
cpython copied to clipboard
sqlite3 - preserve error information from a user-defined function exception
In accordance with sqlite3.enable_callback_tracebacks documentation, "By default you will not get any tracebacks in user-defined functions, aggregates, converters, authorizer callbacks etc." While it is possible to retrieve debugging information via sqlite3.enable_callback_tracebacks & sys.unraisablehook, often it would be sufficient to append the exception message to the canned response, avoiding the need for extra efforts. For example, the problem in the following code is clearly indicated by the actual exception message, whereas the canned message is not nearly as helpful:
import sqlite3
con = sqlite3.connect(":memory:")
con.create_function("dummy", 0, lambda __: 1)
sqlite3.enable_callback_tracebacks(True) # Provides error details to stderr
try:
con.execute("SELECT dummy();")
except sqlite3.OperationalError as err:
print(err) # Error details are not available, only a canned message.
As pointed out in this SO answer, it should be possible to appended the actual exception message to the canned response in the code block that processes the sqlite3.enable_callback_tracebacks flag.
The original exception message is unfortunately not directly available for us. We'd have to extract the current in-flight exception using PyErr_Fetch(), and then use some other tricks to extract the exception message as a C string. Is this easily feasable using the C API, @iritkatriel?
The only generic way to get the exception message is to call str() on the exception. You don't know how each exception type implements that or what fields it has.
Yeah, that could work. Thanks.
Proof-of-concept branch: https://github.com/erlend-aasland/cpython/pull/new/sqlite-callback-exceptions
If we choose to implement this, it would make sense to alter some of the exception messages so they play better with the potentially added information. This is technically a change in the API, so we'll have to document it in the What's New document for 3.12. Also, note that we cannot backport enhancements.
I think this is worth implementing; better error messages FTW. I'll create a PR and we'll get it into one of the next 3.13 alphas.
I've resurrected my old branch and created a draft PR; I'll have a closer look at it in a few days.
As the PR currently stands, we now get exceptions like this:
OperationalError: user-defined function raised exception: ZeroDivisionError: division by zero
OperationalError: user-defined function raised exception: Exception:
OperationalError: user-defined aggregate's 'step' method raised error: Exception:
OperationalError: user-defined aggregate's 'step' method raised error: ZeroDivisionError: division by zero
OperationalError: user-defined aggregate's 'step' method raised error: AttributeError: 'Empty' object has no attribute 'step'
Another option could be to add the underlying exception (for example ZeroDivisionError) as a member to the OperationalError, similar to how we deal with SQLite error codes.
With this feature, we now get duplicate information if we sqlite3.enable_callback_tracebacks(True). Also, I believe the information you get by setting enable_callback_tracebacks to True is better than what you get by concat'ing one exception on top of another. With that in mind, I'm inclined to reject this feature.
OTOH, what I would like to do, is to enable callback tracebacks by default; they are already outputted as unraisable exceptions, which makes for a better debug experience:
- https://docs.python.org/3/library/sys.html#sys.unraisablehook
I'm going to reject this feature and instead work on enabling unraisable exceptions by default. That is:
- if an exception happens in a callback, raise an unraisable exception at once
- make
sqlite3.enable_callback_tracebacksa no-op
I'll create a topic on Discourse to get more input on this.
Topic created:
- https://discuss.python.org/t/enable-unraisable-exceptions-by-default-for-sqlite3-callbacks/43808