bpo-42213: Remove redundant cyclic GC hack in sqlite3 (GH-26517)
The sqlite3 module now fully implements the GC protocol, so there's no need for this workaround anymore. - Add and use managed resource helper for connections using TESTFN - Sort test imports - Split open-tests into their own test case Automerge-Triggered-By: GH:vstinner
This commit is contained in:
parent
2c1e2583fd
commit
d88b47b5a3
@ -20,14 +20,26 @@
|
|||||||
# misrepresented as being the original software.
|
# misrepresented as being the original software.
|
||||||
# 3. This notice may not be removed or altered from any source distribution.
|
# 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
|
||||||
import threading
|
import contextlib
|
||||||
import unittest
|
|
||||||
import sqlite3 as sqlite
|
import sqlite3 as sqlite
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
import unittest
|
||||||
|
|
||||||
from test.support.os_helper import TESTFN, unlink
|
from test.support.os_helper import TESTFN, unlink
|
||||||
|
|
||||||
|
|
||||||
|
# Helper for tests using TESTFN
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def managed_connect(*args, **kwargs):
|
||||||
|
cx = sqlite.connect(*args, **kwargs)
|
||||||
|
try:
|
||||||
|
yield cx
|
||||||
|
finally:
|
||||||
|
cx.close()
|
||||||
|
unlink(TESTFN)
|
||||||
|
|
||||||
|
|
||||||
class ModuleTests(unittest.TestCase):
|
class ModuleTests(unittest.TestCase):
|
||||||
def test_api_level(self):
|
def test_api_level(self):
|
||||||
self.assertEqual(sqlite.apilevel, "2.0",
|
self.assertEqual(sqlite.apilevel, "2.0",
|
||||||
@ -190,26 +202,27 @@ class ConnectionTests(unittest.TestCase):
|
|||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
self.cx.in_transaction = True
|
self.cx.in_transaction = True
|
||||||
|
|
||||||
|
class OpenTests(unittest.TestCase):
|
||||||
|
_sql = "create table test(id integer)"
|
||||||
|
|
||||||
def test_open_with_path_like_object(self):
|
def test_open_with_path_like_object(self):
|
||||||
""" Checks that we can successfully connect to a database using an object that
|
""" Checks that we can successfully connect to a database using an object that
|
||||||
is PathLike, i.e. has __fspath__(). """
|
is PathLike, i.e. has __fspath__(). """
|
||||||
self.addCleanup(unlink, TESTFN)
|
|
||||||
class Path:
|
class Path:
|
||||||
def __fspath__(self):
|
def __fspath__(self):
|
||||||
return TESTFN
|
return TESTFN
|
||||||
path = Path()
|
path = Path()
|
||||||
with sqlite.connect(path) as cx:
|
with managed_connect(path) as cx:
|
||||||
cx.execute('create table test(id integer)')
|
cx.execute(self._sql)
|
||||||
|
|
||||||
def test_open_uri(self):
|
def test_open_uri(self):
|
||||||
self.addCleanup(unlink, TESTFN)
|
with managed_connect(TESTFN) as cx:
|
||||||
with sqlite.connect(TESTFN) as cx:
|
cx.execute(self._sql)
|
||||||
cx.execute('create table test(id integer)')
|
with managed_connect(f"file:{TESTFN}", uri=True) as cx:
|
||||||
with sqlite.connect('file:' + TESTFN, uri=True) as cx:
|
cx.execute(self._sql)
|
||||||
cx.execute('insert into test(id) values(0)')
|
with self.assertRaises(sqlite.OperationalError):
|
||||||
with sqlite.connect('file:' + TESTFN + '?mode=ro', uri=True) as cx:
|
with managed_connect(f"file:{TESTFN}?mode=ro", uri=True) as cx:
|
||||||
with self.assertRaises(sqlite.OperationalError):
|
cx.execute(self._sql)
|
||||||
cx.execute('insert into test(id) values(1)')
|
|
||||||
|
|
||||||
|
|
||||||
class CursorTests(unittest.TestCase):
|
class CursorTests(unittest.TestCase):
|
||||||
|
@ -254,11 +254,15 @@ class TraceCallbackTests(unittest.TestCase):
|
|||||||
self.addCleanup(unlink, TESTFN)
|
self.addCleanup(unlink, TESTFN)
|
||||||
con1 = sqlite.connect(TESTFN, isolation_level=None)
|
con1 = sqlite.connect(TESTFN, isolation_level=None)
|
||||||
con2 = sqlite.connect(TESTFN)
|
con2 = sqlite.connect(TESTFN)
|
||||||
con1.set_trace_callback(trace)
|
try:
|
||||||
cur = con1.cursor()
|
con1.set_trace_callback(trace)
|
||||||
cur.execute(queries[0])
|
cur = con1.cursor()
|
||||||
con2.execute("create table bar(x)")
|
cur.execute(queries[0])
|
||||||
cur.execute(queries[1])
|
con2.execute("create table bar(x)")
|
||||||
|
cur.execute(queries[1])
|
||||||
|
finally:
|
||||||
|
con1.close()
|
||||||
|
con2.close()
|
||||||
self.assertEqual(traced_statements, queries)
|
self.assertEqual(traced_statements, queries)
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,9 +97,6 @@ pysqlite_cache_init(pysqlite_Cache *self, PyObject *args, PyObject *kwargs)
|
|||||||
}
|
}
|
||||||
|
|
||||||
self->factory = Py_NewRef(factory);
|
self->factory = Py_NewRef(factory);
|
||||||
|
|
||||||
self->decref_factory = 1;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,9 +105,7 @@ cache_traverse(pysqlite_Cache *self, visitproc visit, void *arg)
|
|||||||
{
|
{
|
||||||
Py_VISIT(Py_TYPE(self));
|
Py_VISIT(Py_TYPE(self));
|
||||||
Py_VISIT(self->mapping);
|
Py_VISIT(self->mapping);
|
||||||
if (self->decref_factory) {
|
Py_VISIT(self->factory);
|
||||||
Py_VISIT(self->factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
pysqlite_Node *node = self->first;
|
pysqlite_Node *node = self->first;
|
||||||
while (node) {
|
while (node) {
|
||||||
@ -124,9 +119,7 @@ static int
|
|||||||
cache_clear(pysqlite_Cache *self)
|
cache_clear(pysqlite_Cache *self)
|
||||||
{
|
{
|
||||||
Py_CLEAR(self->mapping);
|
Py_CLEAR(self->mapping);
|
||||||
if (self->decref_factory) {
|
Py_CLEAR(self->factory);
|
||||||
Py_CLEAR(self->factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* iterate over all nodes and deallocate them */
|
/* iterate over all nodes and deallocate them */
|
||||||
pysqlite_Node *node = self->first;
|
pysqlite_Node *node = self->first;
|
||||||
|
@ -52,10 +52,6 @@ typedef struct
|
|||||||
|
|
||||||
pysqlite_Node* first;
|
pysqlite_Node* first;
|
||||||
pysqlite_Node* last;
|
pysqlite_Node* last;
|
||||||
|
|
||||||
/* if set, decrement the factory function when the Cache is deallocated.
|
|
||||||
* this is almost always desirable, but not in the pysqlite context */
|
|
||||||
int decref_factory;
|
|
||||||
} pysqlite_Cache;
|
} pysqlite_Cache;
|
||||||
|
|
||||||
extern PyTypeObject *pysqlite_NodeType;
|
extern PyTypeObject *pysqlite_NodeType;
|
||||||
|
@ -149,14 +149,6 @@ pysqlite_connection_init(pysqlite_Connection *self, PyObject *args,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* By default, the Cache class INCREFs the factory in its initializer, and
|
|
||||||
* decrefs it in its deallocator method. Since this would create a circular
|
|
||||||
* reference here, we're breaking it by decrementing self, and telling the
|
|
||||||
* cache class to not decref the factory (self) in its deallocator.
|
|
||||||
*/
|
|
||||||
self->statement_cache->decref_factory = 0;
|
|
||||||
Py_DECREF(self);
|
|
||||||
|
|
||||||
self->detect_types = detect_types;
|
self->detect_types = detect_types;
|
||||||
self->timeout = timeout;
|
self->timeout = timeout;
|
||||||
(void)sqlite3_busy_timeout(self->db, (int)(timeout*1000));
|
(void)sqlite3_busy_timeout(self->db, (int)(timeout*1000));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user