@@ -40,6 +40,42 @@ def test_no_threadsafety(dbapi, threadsafety):
4040 PooledDB (dbapi )
4141
4242
43+ def test_creator_function (dbapi ):
44+ """Check that a creator function can be used instead of a module."""
45+ pool = PooledDB (dbapi .connect , 1 , database = 'ok' )
46+ # the threadsafety cannot be determined from a plain creator function,
47+ # so the connections are optimistically assumed to be thread-safe,
48+ # but not safe enough to be shared between several threads
49+ assert pool ._maxshared == 0
50+ db = pool .connection ()
51+ assert db .threadsafety () == dbapi .threadsafety
52+ cursor = db .cursor ()
53+ cursor .execute ('select test' )
54+ assert cursor .fetchone () == 'test'
55+
56+
57+ def test_creator_without_threadsafety (dbapi ):
58+ """Check that a creator that hides its threadsafety is rejected."""
59+
60+ class Creator :
61+ """A database module that does not report its threadsafety."""
62+
63+ connect = staticmethod (dbapi .connect )
64+
65+ with pytest .raises (NotSupportedError ):
66+ PooledDB (Creator )
67+
68+
69+ def test_creator_with_unsafe_connections (dbapi , monkeypatch ):
70+ """Check that connections that are not thread-safe are rejected."""
71+ monkeypatch .delattr (dbapi , 'threadsafety' )
72+ # the pool optimistically assumes that a creator function provides
73+ # thread-safe connections, but the connections themselves know better
74+ pool = PooledDB (dbapi .connect , 0 )
75+ with pytest .raises (NotSupportedError ):
76+ pool .connection ()
77+
78+
4379@pytest .mark .parametrize ("threadsafety" , [1 , 2 , 3 ])
4480def test_threadsafety (dbapi , threadsafety ):
4581 """Check that connections are only shared when they may be."""
@@ -470,6 +506,8 @@ def test_unshare_connection(dbapi, threadsafety):
470506 # every round takes the given number of connections out of the pool
471507 # and expects the given number of idle connections after releasing them
472508 (3 , 0 , 3 , [(3 , 3 ), (6 , 6 )]), # unlimited cache grows with the demand
509+ (3 , None , 3 , [(3 , 3 ), (6 , 6 )]), # None is the same as zero here
510+ (None , None , 0 , [(3 , 3 ), (6 , 6 )]), # and also when nothing is cached
473511 (0 , 3 , 0 , [(3 , 3 ), (6 , 3 )]), # cache fills up to maxcached
474512 (3 , 3 , 3 , [(3 , 3 ), (6 , 3 )]), # cache stays at the common bound
475513 (3 , 2 , 3 , [(4 , 3 )]), # mincached wins when it exceeds maxcached
@@ -959,11 +997,12 @@ def test_maxconnections_equal_to_maxshared(dbapi, threadsafety):
959997
960998
961999@pytest .mark .parametrize ("threadsafety" , [1 , 2 ])
962- def test_maxconnections_unlimited (dbapi , threadsafety ):
1000+ @pytest .mark .parametrize ("maxconnections" , [0 , None ])
1001+ def test_maxconnections_unlimited (dbapi , threadsafety , maxconnections ):
9631002 """Check that the number of connections is unlimited by default."""
9641003 dbapi .threadsafety = threadsafety
9651004 shareable = threadsafety > 1
966- pool = PooledDB (dbapi , 0 , 0 , 3 )
1005+ pool = PooledDB (dbapi , 0 , 0 , 3 , maxconnections )
9671006 assert pool ._maxconnections == 0
9681007 assert pool ._connections == 0
9691008 cache = []
@@ -1312,6 +1351,34 @@ def test_shared_in_transaction(dbapi):
13121351 pool .connection ()
13131352
13141353
1354+ def test_shared_in_transaction_blocking (dbapi ):
1355+ """Check that a thread waits for a shared connection in a transaction."""
1356+ pool = PooledDB (dbapi , 0 , 0 , 1 , 0 , True )
1357+ db = pool .connection ()
1358+ con = db ._con
1359+ db .begin ()
1360+ # the only connection that may be shared is in a transaction now
1361+ shared = []
1362+
1363+ def connection ():
1364+ shared .append (pool .connection ())
1365+
1366+ thread = Thread (target = connection )
1367+ thread .start ()
1368+ thread .join (0.1 )
1369+ # the thread cannot share that connection and blocks instead of failing
1370+ assert thread .is_alive ()
1371+ assert not shared
1372+ db .commit ()
1373+ # the thread is woken up when a connection is put back into the pool,
1374+ # and then finds the connection shareable again
1375+ pool .dedicated_connection ().close ()
1376+ thread .join (0.1 )
1377+ assert not thread .is_alive ()
1378+ assert len (shared ) == 1
1379+ assert shared [0 ]._con is con
1380+
1381+
13151382def test_shared_in_transaction_with_two_connections (dbapi ):
13161383 """Check that sharing prefers connections without a transaction."""
13171384 pool = PooledDB (dbapi , 0 , 2 , 2 )
@@ -1445,6 +1512,19 @@ def test_shared_db_connection_compare(dbapi):
14451512 assert con1 > con2
14461513
14471514
1515+ def test_shared_db_connection_hash (dbapi ):
1516+ """Check that shared connections stay hashable."""
1517+ # defining __eq__ would otherwise make the class unhashable
1518+ con = SharedDBConnection (dbapi .connect ())
1519+ hashed = hash (con )
1520+ assert hash (con ) == hashed
1521+ # the hash is derived from the underlying connection and the shares
1522+ con .share ()
1523+ assert hash (con ) != hashed
1524+ con .unshare ()
1525+ assert hash (con ) == hashed
1526+
1527+
14481528def timeout_is_not_fatal (error ):
14491529 """Treat a deliberate server side timeout as not fatal."""
14501530 return not error .args or error .args [0 ] != 3024
0 commit comments