diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index dcffc779cfba3d..91bf7b0e7c879e 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -159,23 +159,41 @@ Module functions and constants .. data:: threadsafety - Integer constant required by the DB-API, stating the level of thread safety - the :mod:`sqlite3` module supports. Currently hard-coded to ``1``, meaning - *"Threads may share the module, but not connections."* However, this may not - always be true. You can check the underlying SQLite library's compile-time - threaded mode using the following query:: - - import sqlite3 - con = sqlite3.connect(":memory:") - con.execute(""" - select * from pragma_compile_options - where compile_options like 'THREADSAFE=%' - """).fetchall() - - Note that the `SQLITE_THREADSAFE levels - `_ do not match the DB-API 2.0 - ``threadsafety`` levels. - + Integer constant required by the DB-API 2.0, stating the level of thread + safety the :mod:`sqlite3` module supports. This attribute is set based on + the default `threading mode `_ the + underlying SQLite library is compiled with. The SQLite threading modes are: + + 1. **Single-thread**: In this mode, all mutexes are disabled and SQLite is + unsafe to use in more than a single thread at once. + 2. **Multi-thread**: In this mode, SQLite can be safely used by multiple + threads provided that no single database connection is used + simultaneously in two or more threads. + 3. **Serialized**: In serialized mode, SQLite can be safely used by + multiple threads with no restriction. + + The mappings from SQLite threading modes to DB-API 2.0 threadsafety levels + are as follows: + + +------------------+-----------------+----------------------+-------------------------------+ + | SQLite threading | `threadsafety`_ | `SQLITE_THREADSAFE`_ | DB-API 2.0 meaning | + | mode | | | | + +==================+=================+======================+===============================+ + | single-thread | 0 | 0 | Threads may not share the | + | | | | module | + +------------------+-----------------+----------------------+-------------------------------+ + | multi-thread | 1 | 2 | Threads may share the module, | + | | | | but not connections | + +------------------+-----------------+----------------------+-------------------------------+ + | serialized | 3 | 1 | Threads may share the module, | + | | | | connections and cursors | + +------------------+-----------------+----------------------+-------------------------------+ + + .. _threadsafety: https://www.python.org/dev/peps/pep-0249/#threadsafety + .. _SQLITE_THREADSAFE: https://sqlite.org/compile.html#threadsafe + + .. versionchanged:: 3.11 + Set *threadsafety* dynamically instead of hard-coding it to ``1``. .. data:: PARSE_DECLTYPES diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 3500b1ba6b1cbd..e2a89ca71dc3a6 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -253,6 +253,10 @@ sqlite3 setting and getting SQLite limits by connection basis. (Contributed by Erlend E. Aasland in :issue:`45243`.) +* :mod:`sqlite3` now sets :attr:`sqlite3.threadsafety` based on the default + threading mode the underlying SQLite library has been compiled with. + (Contributed by Erlend E. Aasland in :issue:`45613`.) + threading --------- diff --git a/Lib/sqlite3/dbapi2.py b/Lib/sqlite3/dbapi2.py index cfe6225f46efc0..7cf4dd32d541dd 100644 --- a/Lib/sqlite3/dbapi2.py +++ b/Lib/sqlite3/dbapi2.py @@ -28,8 +28,6 @@ paramstyle = "qmark" -threadsafety = 1 - apilevel = "2.0" Date = datetime.date diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index 34895be018078f..66515aa0d72967 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -54,8 +54,9 @@ def test_api_level(self): "apilevel is %s, should be 2.0" % sqlite.apilevel) def test_thread_safety(self): - self.assertEqual(sqlite.threadsafety, 1, - "threadsafety is %d, should be 1" % sqlite.threadsafety) + self.assertIn(sqlite.threadsafety, {0, 1, 3}, + "threadsafety is %d, should be 0, 1 or 3" % + sqlite.threadsafety) def test_param_style(self): self.assertEqual(sqlite.paramstyle, "qmark", diff --git a/Misc/NEWS.d/next/Library/2021-10-26-14-29-54.bpo-45613.55Ie3c.rst b/Misc/NEWS.d/next/Library/2021-10-26-14-29-54.bpo-45613.55Ie3c.rst new file mode 100644 index 00000000000000..ac0937b54aeeab --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-10-26-14-29-54.bpo-45613.55Ie3c.rst @@ -0,0 +1,3 @@ +:mod:`sqlite3` now sets :attr:`sqlite3.threadsafety` based on the default +threading mode the underlying SQLite library has been compiled with. Patch by +Erlend E. Aasland. diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index 65229623b84d66..5046df43b36f56 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -415,6 +415,28 @@ add_integer_constants(PyObject *module) { return 0; } +/* Convert SQLite default threading mode (as set by the compile-time constant + * SQLITE_THREADSAFE) to the corresponding DB-API 2.0 (PEP 249) threadsafety + * level. */ +static int +get_threadsafety(pysqlite_state *state) +{ + int mode = sqlite3_threadsafe(); + switch (mode) { + case 0: // Single-thread mode; threads may not share the module. + return 0; + case 1: // Serialized mode; threads may share the module, + return 3; // connections, and cursors. + case 2: // Multi-thread mode; threads may share the module, but not + return 1; // connections. + default: + PyErr_Format(state->InterfaceError, + "Unable to interpret SQLite threadsafety mode. Got %d, " + "expected 0, 1, or 2", mode); + return -1; + } +} + static int module_traverse(PyObject *module, visitproc visit, void *arg) { @@ -564,6 +586,14 @@ module_exec(PyObject *module) goto error; } + int threadsafety = get_threadsafety(state); + if (threadsafety < 0) { + goto error; + } + if (PyModule_AddIntConstant(module, "threadsafety", threadsafety) < 0) { + goto error; + } + /* initialize microprotocols layer */ if (pysqlite_microprotocols_init(module) < 0) { goto error;