diff -r 1b2134a78c17 Doc/includes/sqlite3/complete_statement.py --- a/Doc/includes/sqlite3/complete_statement.py Fri Dec 28 10:09:54 2012 +0200 +++ b/Doc/includes/sqlite3/complete_statement.py Fri Dec 28 21:03:25 2012 +0000 @@ -24,7 +24,10 @@ while True: if buffer.lstrip().upper().startswith("SELECT"): print(cur.fetchall()) except sqlite3.Error as e: - print("An error occurred:", e.args[0]) + code = sqlite3.errorcode.get(e.sqlite_errcode, + str(e.sqlite_errcode)) + msg = e.args[0] + print("Error {0} occurred: {1}".format(code, msg)) buffer = "" con.close() diff -r 1b2134a78c17 Doc/library/sqlite3.rst --- a/Doc/library/sqlite3.rst Fri Dec 28 10:09:54 2012 +0200 +++ b/Doc/library/sqlite3.rst Fri Dec 28 21:03:25 2012 +0000 @@ -213,6 +213,21 @@ Module functions and constants again. +.. data:: errorcode + + Dictionary mapping SQLite error code values to their name in the `SQLite API + `_. For instance, + ``sqlite3.errorcode[sqlite3.SQLITE_OK]`` maps to ``'SQLITE_OK'``. Similar + to :data:`errno.errorcode`. + + The list of available error codes depend on the sqlite3 library Python was + built against. It is available as ``sqlite3.errorcode.keys()``. Available + error codes are also presented as module attributes (e.g., + `sqlite3.SQLITE_OK`). + +.. versionadded:: 3.4 + + .. _sqlite3-connection-objects: Connection Objects @@ -894,6 +909,15 @@ threads. If you still try to do so, you The only exception is calling the :meth:`~Connection.interrupt` method, which only makes sense to call from a different thread. +Error handling +^^^^^^^^^^^^^^ + +Exceptions thrown by this object are subclasses of ``sqlite3.Error`` and have +a ``sqlite_errcode`` attribute whose value is the SQLite API error code as +an integer. Use :data:`errorcode` to convert the integer to a symbolic name. + +.. versionadded:: 3.4 + .. rubric:: Footnotes .. [#f1] The sqlite3 module is not built with loadable extension support by diff -r 1b2134a78c17 Lib/sqlite3/test/dbapi.py --- a/Lib/sqlite3/test/dbapi.py Fri Dec 28 10:09:54 2012 +0200 +++ b/Lib/sqlite3/test/dbapi.py Fri Dec 28 21:03:25 2012 +0000 @@ -83,6 +83,20 @@ class ModuleTests(unittest.TestCase): sqlite.DatabaseError), "NotSupportedError is not a subclass of DatabaseError") + def CheckErrorCodeMapping(self): + for errcode in {'SQLITE_OK', 'SQLITE_ROW', 'SQLITE_DONE', + 'SQLITE_PERM'}: + self.assertIsInstance(getattr(sqlite, errcode), int) + self.assertEqual(sqlite.errorcode[getattr(sqlite, errcode)], + errcode) + + def CheckErrorCodeOnExceptions(self): + with self.assertRaises(sqlite.Error) as cm: + db = sqlite.connect('/no/such/file/exists') + e = cm.exception + self.assertEqual(e.sqlite_errorcode, sqlite.SQLITE_CANTOPEN) + self.assertRegex(str(e), r"open|file") + class ConnectionTests(unittest.TestCase): def setUp(self): diff -r 1b2134a78c17 Modules/_sqlite/module.c --- a/Modules/_sqlite/module.c Fri Dec 28 10:09:54 2012 +0200 +++ b/Modules/_sqlite/module.c Fri Dec 28 21:03:25 2012 +0000 @@ -253,8 +253,9 @@ static PyMethodDef module_methods[] = { }; struct _IntConstantPair { - char* constant_name; + const char* constant_name; int constant_value; + int is_error_code; }; typedef struct _IntConstantPair IntConstantPair; @@ -263,9 +264,52 @@ static IntConstantPair _int_constants[] {"PARSE_DECLTYPES", PARSE_DECLTYPES}, {"PARSE_COLNAMES", PARSE_COLNAMES}, - {"SQLITE_OK", SQLITE_OK}, + /* sqlite API error codes */ + {"SQLITE_OK", SQLITE_OK, 1}, + {"SQLITE_ERROR", SQLITE_ERROR, 1}, + {"SQLITE_INTERNAL", SQLITE_INTERNAL, 1}, + {"SQLITE_PERM", SQLITE_PERM, 1}, + {"SQLITE_ABORT", SQLITE_ABORT, 1}, + {"SQLITE_BUSY", SQLITE_BUSY, 1}, + {"SQLITE_LOCKED", SQLITE_LOCKED, 1}, + {"SQLITE_NOMEM", SQLITE_NOMEM, 1}, + {"SQLITE_READONLY", SQLITE_READONLY, 1}, + {"SQLITE_INTERRUPT", SQLITE_INTERRUPT, 1}, + {"SQLITE_IOERR", SQLITE_IOERR, 1}, + {"SQLITE_CORRUPT", SQLITE_CORRUPT, 1}, + {"SQLITE_NOTFOUND", SQLITE_NOTFOUND, 1}, + {"SQLITE_FULL", SQLITE_FULL, 1}, + {"SQLITE_CANTOPEN", SQLITE_CANTOPEN, 1}, + {"SQLITE_PROTOCOL", SQLITE_PROTOCOL, 1}, + {"SQLITE_EMPTY", SQLITE_EMPTY, 1}, + {"SQLITE_SCHEMA", SQLITE_SCHEMA, 1}, + {"SQLITE_TOOBIG", SQLITE_TOOBIG, 1}, + {"SQLITE_CONSTRAINT", SQLITE_CONSTRAINT, 1}, + {"SQLITE_MISMATCH", SQLITE_MISMATCH, 1}, + {"SQLITE_MISUSE", SQLITE_MISUSE, 1}, +#ifdef SQLITE_NOLFS + {"SQLITE_NOLFS", SQLITE_NOLFS, 1}, +#endif +#ifdef SQLITE_AUTH + {"SQLITE_AUTH", SQLITE_AUTH, 1}, +#endif +#ifdef SQLITE_FORMAT + {"SQLITE_FORMAT", SQLITE_FORMAT, 1}, +#endif +#ifdef SQLITE_RANGE + {"SQLITE_RANGE", SQLITE_RANGE, 1}, +#endif +#ifdef SQLITE_NOTADB + {"SQLITE_NOTADB", SQLITE_NOTADB, 1}, +#endif + {"SQLITE_DONE", SQLITE_DONE, 1}, + {"SQLITE_ROW", SQLITE_ROW, 1}, + + /* enumerated return values for sqlite3_set_authorizer() callback */ {"SQLITE_DENY", SQLITE_DENY}, {"SQLITE_IGNORE", SQLITE_IGNORE}, + + /* enumerated values for sqlite3_set_authorizer() callback */ {"SQLITE_CREATE_INDEX", SQLITE_CREATE_INDEX}, {"SQLITE_CREATE_TABLE", SQLITE_CREATE_TABLE}, {"SQLITE_CREATE_TEMP_INDEX", SQLITE_CREATE_TEMP_INDEX}, @@ -316,7 +360,7 @@ static struct PyModuleDef _sqlite3module PyMODINIT_FUNC PyInit__sqlite3(void) { - PyObject *module, *dict; + PyObject *module, *dict, *errorcode; PyObject *tmp_obj; int i; @@ -415,14 +459,26 @@ PyMODINIT_FUNC PyInit__sqlite3(void) Py_INCREF((PyObject*)&PyUnicode_Type); PyDict_SetItemString(dict, "OptimizedUnicode", (PyObject*)&PyUnicode_Type); + /* Prepare 'errorcode'. */ + errorcode = PyDict_New(); + if (!errorcode || PyDict_SetItemString(dict, "errorcode", errorcode) < 0) + goto error; + /* Set integer constants */ for (i = 0; _int_constants[i].constant_name != 0; i++) { + PyObject *name = PyUnicode_FromString(_int_constants[i].constant_name); tmp_obj = PyLong_FromLong(_int_constants[i].constant_value); - if (!tmp_obj) { + if (!tmp_obj || !name) { + Py_XDECREF(name); goto error; } - PyDict_SetItemString(dict, _int_constants[i].constant_name, tmp_obj); + if (PyDict_SetItem(dict, name, tmp_obj) < 0) + goto error; + if (_int_constants[i].is_error_code) + if (PyDict_SetItem(errorcode, tmp_obj, name) < 0) + goto error; Py_DECREF(tmp_obj); + Py_DECREF(name); } if (!(tmp_obj = PyUnicode_FromString(PYSQLITE_VERSION))) { diff -r 1b2134a78c17 Modules/_sqlite/util.c --- a/Modules/_sqlite/util.c Fri Dec 28 10:09:54 2012 +0200 +++ b/Modules/_sqlite/util.c Fri Dec 28 21:03:25 2012 +0000 @@ -47,6 +47,7 @@ int pysqlite_step(sqlite3_stmt* statemen */ int _pysqlite_seterror(sqlite3* db, sqlite3_stmt* st) { + PyObject *exc_class; int errorcode; /* SQLite often doesn't report anything useful, unless you reset the statement first */ @@ -60,14 +61,14 @@ int _pysqlite_seterror(sqlite3* db, sqli { case SQLITE_OK: PyErr_Clear(); - break; + return errorcode; case SQLITE_INTERNAL: case SQLITE_NOTFOUND: - PyErr_SetString(pysqlite_InternalError, sqlite3_errmsg(db)); + exc_class = pysqlite_InternalError; break; case SQLITE_NOMEM: (void)PyErr_NoMemory(); - break; + return errorcode; case SQLITE_ERROR: case SQLITE_PERM: case SQLITE_ABORT: @@ -81,26 +82,51 @@ int _pysqlite_seterror(sqlite3* db, sqli case SQLITE_PROTOCOL: case SQLITE_EMPTY: case SQLITE_SCHEMA: - PyErr_SetString(pysqlite_OperationalError, sqlite3_errmsg(db)); + exc_class = pysqlite_OperationalError; break; case SQLITE_CORRUPT: - PyErr_SetString(pysqlite_DatabaseError, sqlite3_errmsg(db)); + exc_class = pysqlite_DatabaseError; break; case SQLITE_TOOBIG: - PyErr_SetString(pysqlite_DataError, sqlite3_errmsg(db)); + exc_class = pysqlite_DataError; break; case SQLITE_CONSTRAINT: case SQLITE_MISMATCH: - PyErr_SetString(pysqlite_IntegrityError, sqlite3_errmsg(db)); + exc_class = pysqlite_IntegrityError; break; case SQLITE_MISUSE: - PyErr_SetString(pysqlite_ProgrammingError, sqlite3_errmsg(db)); + exc_class = pysqlite_ProgrammingError; break; default: - PyErr_SetString(pysqlite_DatabaseError, sqlite3_errmsg(db)); + exc_class = pysqlite_DatabaseError; break; } + /* Create and set the exception. */ + { + const char *msg; + PyObject *exc = NULL; + PyObject *args = NULL; + + msg = sqlite3_errmsg(db); + + args = Py_BuildValue("(si)", msg, errorcode); + if (!args) goto error; + + exc = PyObject_Call(exc_class, args, NULL); + if (!exc) goto error; + + if (PyObject_SetAttrString(exc, "sqlite_errorcode", + PyTuple_GET_ITEM(args, 1)) < 0) + goto error; + + PyErr_SetObject((PyObject *) Py_TYPE(exc), exc); + + error: + Py_XDECREF(args); + Py_XDECREF(exc); + } + return errorcode; }