Skip to content

Commit 54da25e

Browse files
committed
Support context managers (fixes #33)
1 parent c35a36d commit 54da25e

12 files changed

+148
-15
lines changed

Diff for: dbutils/pooled_db.py

+23
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@
114114
cur.close() # or del cur
115115
db.close() # or del db
116116
117+
You can also use context managers for simpler code:
118+
119+
with pool.connection() as db:
120+
with db.cursor as cur:
121+
cur.execute(...)
122+
res = cur.fetchone()
123+
117124
Note that you need to explicitly start transactions by calling the
118125
begin() method. This ensures that the connection will not be shared
119126
with other threads, that the transparent reopening will be suspended
@@ -440,6 +447,14 @@ def __del__(self):
440447
except: # builtin Exceptions might not exist any more
441448
pass
442449

450+
def __enter__(self):
451+
"""Enter a runtime context for the connection."""
452+
return self
453+
454+
def __exit__(self, *exc):
455+
"""Exit a runtime context for the connection."""
456+
self.close()
457+
443458

444459
class SharedDBConnection:
445460
"""Auxiliary class for shared connections."""
@@ -526,3 +541,11 @@ def __del__(self):
526541
self.close()
527542
except: # builtin Exceptions might not exist any more
528543
pass
544+
545+
def __enter__(self):
546+
"""Enter a runtime context for the connection."""
547+
return self
548+
549+
def __exit__(self, *exc):
550+
"""Exit a runtime context for the connection."""
551+
self.close()

Diff for: dbutils/pooled_pg.py

+13
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@
8383
res = db.query(...).getresult()
8484
db.close() # or del db
8585
86+
You can also a context manager for simpler code:
87+
88+
with pool.connection() as db:
89+
res = db.query(...).getresult()
90+
8691
Note that you need to explicitly start transactions by calling the
8792
begin() method. This ensures that the transparent reopening will be
8893
suspended until the end of the transaction, and that the connection will
@@ -293,3 +298,11 @@ def __del__(self):
293298
self.close()
294299
except: # builtin Exceptions might not exist any more
295300
pass
301+
302+
def __enter__(self):
303+
"""Enter a runtime context for the connection."""
304+
return self
305+
306+
def __exit__(self, *exc):
307+
"""Exit a runtime context for the connection."""
308+
self.close()

Diff for: docs/changelog.html

+21-13
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,22 @@
1212
<h1 class="title">Changelog for DBUtils</h1>
1313

1414
<section id="id1">
15+
<h2>2.0.2</h2>
16+
<p>DBUtils 2.0.2 was released on ...</p>
17+
<p>Changes:</p>
18+
<ul class="simple">
19+
<li><p>Allow using context managers for pooled connections.</p></li>
20+
</ul>
21+
</section>
22+
<section id="id2">
1523
<h2>2.0.1</h2>
1624
<p>DBUtils 2.0.1 was released on April 8, 2021.</p>
1725
<p>Changes:</p>
1826
<ul class="simple">
19-
<li><p>Avoid &quot;name Exception is not defined&quot; when exiting</p></li>
27+
<li><p>Avoid &quot;name Exception is not defined&quot; when exiting.</p></li>
2028
</ul>
2129
</section>
22-
<section id="id2">
30+
<section id="id3">
2331
<h2>2.0</h2>
2432
<p>DBUtils 2.0 was released on September 26, 2020.</p>
2533
<p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -35,7 +43,7 @@ <h2>2.0</h2>
3543
<li><p>This changelog has been compiled from the former release notes.</p></li>
3644
</ul>
3745
</section>
38-
<section id="id3">
46+
<section id="id4">
3947
<h2>1.4</h2>
4048
<p>DBUtils 1.4 was released on September 26, 2020.</p>
4149
<p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -46,7 +54,7 @@ <h2>1.4</h2>
4654
inside a transaction.</p></li>
4755
</ul>
4856
</section>
49-
<section id="id4">
57+
<section id="id5">
5058
<h2>1.3</h2>
5159
<p>DBUtils 1.3 was released on March 3, 2018.</p>
5260
<p>It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.</p>
@@ -55,12 +63,12 @@ <h2>1.3</h2>
5563
<li><p>This version now supports context handlers for connections and cursors.</p></li>
5664
</ul>
5765
</section>
58-
<section id="id5">
66+
<section id="id6">
5967
<h2>1.2</h2>
6068
<p>DBUtils 1.2 was released on February 5, 2017.</p>
6169
<p>It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.</p>
6270
</section>
63-
<section id="id6">
71+
<section id="id7">
6472
<h2>1.1.1</h2>
6573
<p>DBUtils 1.1.1 was released on February 4, 2017.</p>
6674
<p>It is intended to be used with Python versions 2.3 to 2.7.</p>
@@ -74,7 +82,7 @@ <h2>1.1.1</h2>
7482
<li><p>Fixed a problem when running under Jython (reported by Vitaly Kruglikov).</p></li>
7583
</ul>
7684
</section>
77-
<section id="id7">
85+
<section id="id8">
7886
<h2>1.1</h2>
7987
<p>DBUtils 1.1 was released on August 14, 2011.</p>
8088
<p>Improvements:</p>
@@ -103,7 +111,7 @@ <h2>1.1</h2>
103111
<li><p>Fixed some minor issues with the <span class="docutils literal">DBUtilsExample</span> for Webware.</p></li>
104112
</ul>
105113
</section>
106-
<section id="id8">
114+
<section id="id9">
107115
<h2>1.0</h2>
108116
<p>DBUtils 1.0 was released on November 29, 2008.</p>
109117
<p>It is intended to be used with Python versions 2.2 to 2.6.</p>
@@ -136,7 +144,7 @@ <h2>1.0</h2>
136144
the MySQLdb module (problem reported by Gregory Pinero).</p></li>
137145
</ul>
138146
</section>
139-
<section id="id9">
147+
<section id="id10">
140148
<h2>0.9.4</h2>
141149
<p>DBUtils 0.9.4 was released on July 7, 2007.</p>
142150
<p>This release fixes a problem in the destructor code and has been supplemented
@@ -145,7 +153,7 @@ <h2>0.9.4</h2>
145153
in the last release, since you can now pass custom creator functions
146154
for database connections instead of DB-API 2 modules.</p>
147155
</section>
148-
<section id="id10">
156+
<section id="id11">
149157
<h2>0.9.3</h2>
150158
<p>DBUtils 0.9.3 was released on May 21, 2007.</p>
151159
<p>Changes:</p>
@@ -160,7 +168,7 @@ <h2>0.9.3</h2>
160168
Added Chinese translation of the User's Guide, kindly contributed by gashero.</p></li>
161169
</ul>
162170
</section>
163-
<section id="id11">
171+
<section id="id12">
164172
<h2>0.9.2</h2>
165173
<p>DBUtils 0.9.2 was released on September 22, 2006.</p>
166174
<p>It is intended to be used with Python versions 2.2 to 2.5.</p>
@@ -170,7 +178,7 @@ <h2>0.9.2</h2>
170178
storage engine. Accordingly, renamed <span class="docutils literal">SolidPg</span> to <span class="docutils literal">SteadyPg</span>.</p></li>
171179
</ul>
172180
</section>
173-
<section id="id12">
181+
<section id="id13">
174182
<h2>0.9.1</h2>
175183
<p>DBUtils 0.9.1 was released on May 8, 2006.</p>
176184
<p>It is intended to be used with Python versions 2.2 to 2.4.</p>
@@ -184,7 +192,7 @@ <h2>0.9.1</h2>
184192
<li><p>Improved the documentation and added a User's Guide.</p></li>
185193
</ul>
186194
</section>
187-
<section id="id13">
195+
<section id="id14">
188196
<h2>0.8.1 - 2005-09-13</h2>
189197
<p>DBUtils 0.8.1 was released on September 13, 2005.</p>
190198
<p>It is intended to be used with Python versions 2.0 to 2.4.</p>

Diff for: docs/changelog.rst

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
Changelog for DBUtils
22
+++++++++++++++++++++
33

4+
2.0.2
5+
=====
6+
7+
DBUtils 2.0.2 was released on ...
8+
9+
Changes:
10+
11+
* Allow using context managers for pooled connections.
12+
13+
414
2.0.1
515
=====
616

717
DBUtils 2.0.1 was released on April 8, 2021.
818

919
Changes:
1020

11-
* Avoid "name Exception is not defined" when exiting
21+
* Avoid "name Exception is not defined" when exiting.
1222

1323
2.0
1424
===

Diff for: docs/main.de.html

+5
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,11 @@ <h3>PooledDB (pooled_db)</h3>
428428
res = cur.fetchone()
429429
cur.close() # oder del cur
430430
db.close() # oder del db</pre>
431+
<p>Sie können dies auch durch Verwendung von Kontext-Managern vereinfachen:</p>
432+
<pre class="literal-block">with pool.connection() as db:
433+
with db.cursor as cur:
434+
cur.execute(...)
435+
res = cur.fetchone()</pre>
431436
<p>Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
432437
<span class="docutils literal">begin()</span> eingeleiten werden müssen. Hierdurch wird sichergestellt,
433438
dass die Verbindung nicht mehr mit anderen Threads geteilt wird, dass das

Diff for: docs/main.de.rst

+8
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,14 @@ sie gebraucht werden, etwa so::
443443
cur.close() # oder del cur
444444
db.close() # oder del db
445445

446+
Sie können dies auch durch Verwendung von Kontext-Managern vereinfachen::
447+
448+
with pool.connection() as db:
449+
with db.cursor as cur:
450+
cur.execute(...)
451+
res = cur.fetchone()
452+
453+
446454
Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
447455
``begin()`` eingeleiten werden müssen. Hierdurch wird sichergestellt,
448456
dass die Verbindung nicht mehr mit anderen Threads geteilt wird, dass das

Diff for: docs/main.html

+5
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,11 @@ <h3>PooledDB (pooled_db)</h3>
390390
res = cur.fetchone()
391391
cur.close() # or del cur
392392
db.close() # or del db</pre>
393+
<p>You can also use context managers for simpler code:</p>
394+
<pre class="literal-block">with pool.connection() as db:
395+
with db.cursor as cur:
396+
cur.execute(...)
397+
res = cur.fetchone()</pre>
393398
<p>Note that you need to explicitly start transactions by calling the
394399
<span class="docutils literal">begin()</span> method. This ensures that the connection will not be shared
395400
with other threads, that the transparent reopening will be suspended

Diff for: docs/main.rst

+7
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,13 @@ object stays alive as long as you are using it, like that::
405405
cur.close() # or del cur
406406
db.close() # or del db
407407

408+
You can also use context managers for simpler code::
409+
410+
with pool.connection() as db:
411+
with db.cursor as cur:
412+
cur.execute(...)
413+
res = cur.fetchone()
414+
408415
Note that you need to explicitly start transactions by calling the
409416
``begin()`` method. This ensures that the connection will not be shared
410417
with other threads, that the transparent reopening will be suspended

Diff for: tests/test_persistent_db.py

+8
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,14 @@ def test_failed_transaction(self):
286286
db._con.close()
287287
cursor.execute('select test')
288288

289+
def test_context_manager(self):
290+
persist = PersistentDB(dbapi)
291+
with persist.connection() as db:
292+
with db.cursor() as cursor:
293+
cursor.execute('select test')
294+
r = cursor.fetchone()
295+
self.assertEqual(r, 'test')
296+
289297

290298
if __name__ == '__main__':
291299
unittest.main()

Diff for: tests/test_persistent_pg.py

+6
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,12 @@ def test_failed_transaction(self):
189189
db._con.close()
190190
self.assertEqual(db.query('select test'), 'test')
191191

192+
def test_context_manager(self):
193+
persist = PersistentPg()
194+
with persist.connection() as db:
195+
db.query('select test')
196+
self.assertEqual(db.num_queries, 1)
197+
192198

193199
if __name__ == '__main__':
194200
unittest.main()

Diff for: tests/test_pooled_db.py

+27
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,33 @@ def test_reset_transaction(self):
11811181
self.assertFalse(con._transaction)
11821182
self.assertEqual(con._con.session, ['rollback'])
11831183

1184+
def test_context_manager(self):
1185+
pool = PooledDB(dbapi, 1, 1, 1)
1186+
con = pool._idle_cache[0]._con
1187+
with pool.connection() as db:
1188+
self.assertTrue(hasattr(db, '_shared_con'))
1189+
self.assertFalse(pool._idle_cache)
1190+
self.assertTrue(con.valid)
1191+
with db.cursor() as cursor:
1192+
self.assertEqual(con.open_cursors, 1)
1193+
cursor.execute('select test')
1194+
r = cursor.fetchone()
1195+
self.assertEqual(con.open_cursors, 0)
1196+
self.assertEqual(r, 'test')
1197+
self.assertEqual(con.num_queries, 1)
1198+
self.assertTrue(pool._idle_cache)
1199+
with pool.dedicated_connection() as db:
1200+
self.assertFalse(hasattr(db, '_shared_con'))
1201+
self.assertFalse(pool._idle_cache)
1202+
with db.cursor() as cursor:
1203+
self.assertEqual(con.open_cursors, 1)
1204+
cursor.execute('select test')
1205+
r = cursor.fetchone()
1206+
self.assertEqual(con.open_cursors, 0)
1207+
self.assertEqual(r, 'test')
1208+
self.assertEqual(con.num_queries, 2)
1209+
self.assertTrue(pool._idle_cache)
1210+
11841211

11851212
class TestSharedDBConnection(unittest.TestCase):
11861213

Diff for: tests/test_pooled_pg.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from . import mock_pg # noqa
1616

17-
from dbutils.pooled_pg import PooledPg, InvalidConnection
17+
from dbutils.pooled_pg import PooledPg, InvalidConnection, TooManyConnections
1818

1919

2020
class TestPooledPg(unittest.TestCase):
@@ -304,6 +304,19 @@ def test_reset_transaction(self):
304304
self.assertEqual(con.session, [])
305305
self.assertEqual(con.num_queries, 0)
306306

307+
def test_context_manager(self):
308+
pool = PooledPg(1, 1, 1)
309+
with pool.connection() as db:
310+
db_con = db._con._con
311+
db.query('select test')
312+
self.assertEqual(db_con.num_queries, 1)
313+
self.assertRaises(TooManyConnections, pool.connection)
314+
with pool.connection() as db:
315+
db_con = db._con._con
316+
db.query('select test')
317+
self.assertEqual(db_con.num_queries, 2)
318+
self.assertRaises(TooManyConnections, pool.connection)
319+
307320

308321
if __name__ == '__main__':
309322
unittest.main()

0 commit comments

Comments
 (0)