Skip to content

Commit a3d4d15

Browse files
Erlend Egeberg Aaslandserhiy-storchaka
Erlend Egeberg Aasland
andauthored
gh-95132: Correctly relay *args and **kwds from sqlite3.connect to factory (#95146)
This PR partially reverts gh-24421 (PR) and fixes the remaining concerns given in gh-93044 (issue): - keyword arguments are passed as positional arguments to factory() - if an argument is not passed to sqlite3.connect(), its default value is passed to factory() Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent c1e9298 commit a3d4d15

File tree

5 files changed

+59
-147
lines changed

5 files changed

+59
-147
lines changed

Lib/test/test_sqlite3/test_factory.py

+20
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,26 @@ def __init__(self, *args, **kwargs):
5050
con = sqlite.connect(":memory:", factory=factory)
5151
self.assertIsInstance(con, factory)
5252

53+
def test_connection_factory_relayed_call(self):
54+
# gh-95132: keyword args must not be passed as positional args
55+
class Factory(sqlite.Connection):
56+
def __init__(self, *args, **kwargs):
57+
kwargs["isolation_level"] = None
58+
super(Factory, self).__init__(*args, **kwargs)
59+
60+
con = sqlite.connect(":memory:", factory=Factory)
61+
self.assertIsNone(con.isolation_level)
62+
self.assertIsInstance(con, Factory)
63+
64+
def test_connection_factory_as_positional_arg(self):
65+
class Factory(sqlite.Connection):
66+
def __init__(self, *args, **kwargs):
67+
super(Factory, self).__init__(*args, **kwargs)
68+
69+
con = sqlite.connect(":memory:", 5.0, 0, None, True, Factory)
70+
self.assertIsNone(con.isolation_level)
71+
self.assertIsInstance(con, Factory)
72+
5373

5474
class CursorFactoryTests(unittest.TestCase):
5575
def setUp(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix a :mod:`sqlite3` regression where ``*args`` and ``**kwds`` were
2+
incorrectly relayed from :py:func:`~sqlite3.connect` to the
3+
:class:`~sqlite3.Connection` factory. The regression was introduced in 3.11a1
4+
with PR 24421 (:gh:`85128`). Patch by Erlend E. Aasland.`

Modules/_sqlite/clinic/module.c.h

+1-111
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/_sqlite/connection.c

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ class IsolationLevel_converter(CConverter):
140140
[python start generated code]*/
141141
/*[python end generated code: output=da39a3ee5e6b4b0d input=cbcfe85b253061c2]*/
142142

143+
// NB: This needs to be in sync with the sqlite3.connect docstring
143144
/*[clinic input]
144145
_sqlite3.Connection.__init__ as pysqlite_connection_init
145146

Modules/_sqlite/module.c

+33-36
Original file line numberDiff line numberDiff line change
@@ -42,47 +42,44 @@ module _sqlite3
4242
[clinic start generated code]*/
4343
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=81e330492d57488e]*/
4444

45-
// NOTE: This must equal sqlite3.Connection.__init__ argument spec!
46-
/*[clinic input]
47-
_sqlite3.connect as pysqlite_connect
48-
49-
database: object
50-
timeout: double = 5.0
51-
detect_types: int = 0
52-
isolation_level: object = NULL
53-
check_same_thread: bool(accept={int}) = True
54-
factory: object(c_default='(PyObject*)clinic_state()->ConnectionType') = ConnectionType
55-
cached_statements: int = 128
56-
uri: bool = False
57-
58-
Opens a connection to the SQLite database file database.
59-
60-
You can use ":memory:" to open a database connection to a database that resides
61-
in RAM instead of on disk.
62-
[clinic start generated code]*/
45+
// NB: This needs to be in sync with the Connection.__init__ docstring.
46+
PyDoc_STRVAR(module_connect_doc,
47+
"connect($module, /, database, timeout=5.0, detect_types=0,\n"
48+
" isolation_level='', check_same_thread=True,\n"
49+
" factory=ConnectionType, cached_statements=128, uri=False)\n"
50+
"--\n"
51+
"\n"
52+
"Opens a connection to the SQLite database file database.\n"
53+
"\n"
54+
"You can use \":memory:\" to open a database connection to a database that resides\n"
55+
"in RAM instead of on disk.");
56+
57+
#define PYSQLITE_CONNECT_METHODDEF \
58+
{"connect", _PyCFunction_CAST(module_connect), METH_FASTCALL|METH_KEYWORDS, module_connect_doc},
6359

6460
static PyObject *
65-
pysqlite_connect_impl(PyObject *module, PyObject *database, double timeout,
66-
int detect_types, PyObject *isolation_level,
67-
int check_same_thread, PyObject *factory,
68-
int cached_statements, int uri)
69-
/*[clinic end generated code: output=450ac9078b4868bb input=e16914663ddf93ce]*/
61+
module_connect(PyObject *module, PyObject *const *args, Py_ssize_t nargsf,
62+
PyObject *kwnames)
7063
{
71-
if (isolation_level == NULL) {
72-
isolation_level = PyUnicode_FromString("");
73-
if (isolation_level == NULL) {
74-
return NULL;
75-
}
64+
pysqlite_state *state = pysqlite_get_state(module);
65+
PyObject *factory = (PyObject *)state->ConnectionType;
66+
67+
static const int FACTORY_POS = 5;
68+
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
69+
if (nargs > FACTORY_POS) {
70+
factory = args[FACTORY_POS];
7671
}
77-
else {
78-
Py_INCREF(isolation_level);
72+
else if (kwnames != NULL) {
73+
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(kwnames); i++) {
74+
PyObject *item = PyTuple_GET_ITEM(kwnames, i); // borrowed ref.
75+
if (PyUnicode_CompareWithASCIIString(item, "factory") == 0) {
76+
factory = args[nargs + i];
77+
break;
78+
}
79+
}
7980
}
80-
PyObject *res = PyObject_CallFunction(factory, "OdiOiOii", database,
81-
timeout, detect_types,
82-
isolation_level, check_same_thread,
83-
factory, cached_statements, uri);
84-
Py_DECREF(isolation_level);
85-
return res;
81+
82+
return PyObject_Vectorcall(factory, args, nargsf, kwnames);
8683
}
8784

8885
/*[clinic input]

0 commit comments

Comments
 (0)