Skip to content

Commit 0e3d894

Browse files
committed
Finish documenting the context managers
1 parent 1889c80 commit 0e3d894

File tree

3 files changed

+93
-70
lines changed

3 files changed

+93
-70
lines changed

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ postgres
22
========
33

44
.. automodule:: postgres
5-
:members: Postgres
5+
:members:
66
:member-order: bysource

postgres.py

Lines changed: 90 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,15 @@
5050
<http://initd.org/psycopg/docs/cursor.html>`_ while still taking advantage of
5151
connection pooling:
5252
53-
>>> with db.get_cursor('SELECT * FROM foo ORDER BY bar') as cursor:
54-
... results = cursor.fetchall()
53+
>>> with db.get_cursor() as cursor:
54+
... cursor.execute("SELECT * FROM foo ORDER BY bar")
55+
... cursor.fetchall()
56+
[{"bar": "baz"}, {"bar": "buz"}]
5557
5658
A cursor you get from :py:func:`~postgres.Postgres.get_cursor` has
57-
``autocommit`` turned on for its connection, so every call you make using such
58-
a cursor will be isolated in a separate transaction. Need to include multiple
59-
calls in a single transaction? Use the
59+
:py:attr:`autocommit` turned on for its connection, so every call you make
60+
using such a cursor will be isolated in a separate transaction. Need to include
61+
multiple calls in a single transaction? Use the
6062
:py:func:`~postgres.Postgres.get_transaction` context manager:
6163
6264
>>> with db.get_transaction() as txn:
@@ -72,22 +74,22 @@
7274
[{"bar": "baz"}, {"bar": "blam"}, {"bar": "buz"}]
7375
7476
The :py:func:`~postgres.Postgres.get_transaction` manager gives you a cursor
75-
with ``autocommit`` turned off on its connection. If the block under management
76-
raises, the connection is rolled back. Otherwise it's committed. Use this when
77-
you want a series of statements to be part of one transaction, but you don't
78-
need fine-grained control over the transaction. For fine-grained control, use
79-
:py:func:`~postgres.Postgres.get_connection` to get a connection straight from
80-
the connection pool:
77+
with :py:attr:`autocommit` turned off on its connection. If the block under
78+
management raises, the connection is rolled back. Otherwise it's committed.
79+
Use this when you want a series of statements to be part of one transaction,
80+
but you don't need fine-grained control over the transaction. For fine-grained
81+
control, use :py:func:`~postgres.Postgres.get_connection` to get a connection
82+
straight from the connection pool:
8183
8284
>>> with db.get_connection() as connection:
8385
... cursor = connection.cursor()
84-
... cursor.execute('SELECT * FROM foo ORDER BY bar')
86+
... cursor.execute("SELECT * FROM foo ORDER BY bar")
8587
... cursor.fetchall()
8688
[{"bar": "baz"}, {"bar": "buz"}]
8789
88-
A connection gotten in this way will have ``autocommit`` turned off, and it'll
89-
never be implicitly committed otherwise. It'll actually be rolled back when
90-
you're done with it, so it's up to you to explicitly commit as needed.
90+
A connection gotten in this way will have :py:attr:`autocommit` turned off, and
91+
it'll never be implicitly committed otherwise. It'll actually be rolled back
92+
when you're done with it, so it's up to you to explicitly commit as needed.
9193
9294
9395
API
@@ -122,15 +124,13 @@
122124

123125
# A Helper
124126
# ========
127+
# Heroku gives us an URL, psycopg2 wants a DSN. Convert!
125128

126-
# Teach urlparse about postgres:// URLs.
127129
if 'postgres' not in urlparse.uses_netloc:
130+
# Teach urlparse about postgres:// URLs.
128131
urlparse.uses_netloc.append('postgres')
129132

130-
131133
def url_to_dsn(url):
132-
"""Heroku gives us an URL, psycopg2 wants a DSN. Convert!
133-
"""
134134
parsed = urlparse.urlparse(url)
135135
dbname = parsed.path[1:] # /foobar
136136
user = parsed.username
@@ -148,18 +148,20 @@ def url_to_dsn(url):
148148
# ==============
149149

150150
class Postgres(object):
151-
"""Interact with a `PostgreSQL <http://www.postgresql.org/>`_ datastore.
151+
"""Interact with a `PostgreSQL <http://www.postgresql.org/>`_ database.
152152
153153
:param unicode url: A ``postgres://`` URL or a `PostgreSQL connection string <http://www.postgresql.org/docs/current/static/libpq-connect.html>`_
154154
:param int minconn: The minimum size of the connection pool
155155
:param int maxconn: The minimum size of the connection pool
156156
157157
This is the main object that :py:mod:`postgres` provides, and you should
158-
have one instance per process. When instantiated, this object creates a
159-
`thread-safe connection pool
158+
have one instance per process for each database your process needs to talk
159+
to. When instantiated, this object creates a `thread-safe connection pool
160160
<http://initd.org/psycopg/docs/pool.html#psycopg2.pool.ThreadedConnectionPool>`_,
161-
which opens ``minconn`` connections immediately and up to ``maxconn``
162-
according to demand.
161+
which opens :py:attr:`minconn` connections immediately, and up to
162+
:py:attr:`maxconn` according to demand. The fundamental value of a
163+
:py:class:`~postgres.Postgres` instance is that it runs everything through
164+
its connection pool.
163165
164166
Features:
165167
@@ -180,68 +182,67 @@ def __init__(self, url, minconn=1, maxconn=10):
180182
def execute(self, *a, **kw):
181183
"""Execute the query and discard any results.
182184
"""
183-
with self.get_cursor(*a, **kw):
184-
pass
185+
with self.get_cursor() as cursor:
186+
cursor.execute(*a, **kw)
185187

186188
def fetchone(self, *a, **kw):
187189
"""Execute the query and return a single result (``dict`` or ``None``).
188190
"""
189-
with self.get_cursor(*a, **kw) as cursor:
191+
with self.get_cursor() as cursor:
192+
cursor.execute(*a, **kw)
190193
return cursor.fetchone()
191194

192195
def fetchall(self, *a, **kw):
193196
"""Execute the query and yield the results (``dict``).
194197
"""
195-
with self.get_cursor(*a, **kw) as cursor:
198+
with self.get_cursor() as cursor:
199+
cursor.execute(*a, **kw)
196200
for row in cursor:
197201
yield row
198202

199203
def get_cursor(self, *a, **kw):
200-
"""Execute the query and return a context manager wrapping the cursor.
204+
"""Return a :py:class:`~postgres.CursorContextManager` that uses our connection pool.
201205
202-
The cursor is a psycopg2 RealDictCursor. The connection underlying the
203-
cursor will be checked out of the connection pool and checked back in
204-
upon both successful and exceptional executions against the cursor.
206+
This is what :py:meth:`~postgres.Postgres.execute`,
207+
:py:meth:`~postgres.Postgres.fetchone`, and
208+
:py:meth:`~postgres.Postgres.fetchall` use under the hood. It's
209+
probably less directly useful than
210+
:py:meth:`~postgres.Postgres.get_transaction` and
211+
:py:meth:`~postgres.Postgres.get_connection`.
205212
206213
"""
207214
return CursorContextManager(self.pool, *a, **kw)
208215

209216
def get_transaction(self, *a, **kw):
210-
"""Return a context manager wrapping a transactional cursor.
217+
"""Return a :py:class:`~postgres.TransactionContextManager` that uses our connection pool.
211218
212-
This manager returns a cursor with autocommit turned off on its
213-
connection. If the block under management raises then the connection is
214-
rolled back. Otherwise it's committed. Use this when you want a series
215-
of statements to be part of one transaction, but you don't need
216-
fine-grained control over the transaction.
219+
Use this when you want a series of statements to be part of one
220+
transaction, but you don't need fine-grained control over the
221+
transaction.
217222
218223
"""
219224
return TransactionContextManager(self.pool, *a, **kw)
220225

221226
def get_connection(self):
222-
"""Return a context manager wrapping a :py:class:`postgres.Connection`.
227+
"""Return a :py:class:`~postgres.ConnectionContextManager` that uses our connection pool.
223228
224-
This manager turns autocommit off, and back on when you're done with
225-
the connection. The connection is rolled back on exit, so be sure to
226-
call commit as needed. The idea is that you'd use this when you want
227-
full fine-grained transaction control.
229+
Use this when you want to take advantage of connection pooling, but
230+
otherwise need full control, for example, to do complex things with
231+
transactions.
228232
229233
"""
230234
return ConnectionContextManager(self.pool)
231235

232236

233237
class Connection(psycopg2.extensions.connection):
234-
"""This is a subclass of psycopg2.extensions.connection.
238+
"""This is a subclass of :py:class:`psycopg2.extensions.connection`.
235239
236-
Changes:
240+
This class is used as the :py:attr:`connection_factory` for the connection
241+
pool in :py:class:`Postgres`. Here are the differences from the base class:
237242
238-
- The DB-API 2.0 spec calls for transactions to be left open by
239-
default. I don't think we want this. We set autocommit to
240-
:py:class:True.
241-
242-
- We enforce UTF-8.
243-
244-
- We use RealDictCursor.
243+
- We set :py:attr:`autocommit` to :py:const:`True`.
244+
- We set the client encoding to ``UTF-8``.
245+
- We use :py:class:`psycopg2.extras.RealDictCursor`.
245246
246247
"""
247248

@@ -260,7 +261,15 @@ def cursor(self, *a, **kw):
260261
# ================
261262

262263
class CursorContextManager(object):
263-
"""Instantiated once per cursor-level db access.
264+
"""Instantiated once per :py:func:`~postgres.Postgres.get_cursor` call.
265+
266+
The return value of :py:func:`CursorContextManager.__enter__` is a
267+
:py:class:`psycopg2.extras.RealDictCursor`. Any positional and keyword
268+
arguments to our constructor are passed through to the cursor constructor.
269+
The :py:class:`~postgres.Connection` underlying the cursor is checked
270+
out of the connection pool when the block starts, and checked back in when
271+
the block ends.
272+
264273
"""
265274

266275
def __init__(self, pool, *a, **kw):
@@ -273,16 +282,7 @@ def __enter__(self):
273282
"""Get a connection from the pool.
274283
"""
275284
self.conn = self.pool.getconn()
276-
cursor = self.conn.cursor()
277-
try:
278-
cursor.execute(*self.a, **self.kw)
279-
except:
280-
# If we get an exception from execute (like, the query fails:
281-
# pretty common), then the __exit__ clause is not triggered. We
282-
# trigger it ourselves to avoid draining the pool.
283-
self.__exit__()
284-
raise
285-
return cursor
285+
return self.conn.cursor(*self.a, **self.kw)
286286

287287
def __exit__(self, *exc_info):
288288
"""Put our connection back in the pool.
@@ -291,19 +291,32 @@ def __exit__(self, *exc_info):
291291

292292

293293
class TransactionContextManager(object):
294-
"""Instantiated once per db.get_transaction call.
294+
"""Instantiated once per :py:func:`~postgres.Postgres.get_transaction` call.
295+
296+
The return value of :py:func:`TransactionContextManager.__enter__` is a
297+
:py:class:`psycopg2.extras.RealDictCursor`. Any positional and keyword
298+
arguments to our constructor are passed through to the cursor constructor.
299+
When the block starts, the :py:class:`~postgres.Connection` underlying the
300+
cursor is checked out of the connection pool and :py:attr:`autocommit` is
301+
set to :py:const:`False`. If the block raises an exception, the
302+
:py:class:`~postgres.Connection` is rolled back. Otherwise it's committed.
303+
In either case, :py:attr:`autocommit` is restored to :py:const:`True` and
304+
the :py:class:`~postgres.Connection` is put back in the pool.
305+
295306
"""
296307

297308
def __init__(self, pool, *a, **kw):
298309
self.pool = pool
310+
self.a = a
311+
self.kw = kw
299312
self.conn = None
300313

301-
def __enter__(self, *a, **kw):
314+
def __enter__(self):
302315
"""Get a connection from the pool.
303316
"""
304317
self.conn = self.pool.getconn()
305318
self.conn.autocommit = False
306-
return self.conn.cursor(*a, **kw)
319+
return self.conn.cursor(*self.a, **self.kw)
307320

308321
def __exit__(self, *exc_info):
309322
"""Put our connection back in the pool.
@@ -317,10 +330,19 @@ def __exit__(self, *exc_info):
317330

318331

319332
class ConnectionContextManager(object):
320-
"""Instantiated once per db.get_connection call.
333+
"""Instantiated once per :py:func:`~postgres.Postgres.get_connection` call.
334+
335+
The return value of :py:func:`ConnectionContextManager.__enter__` is a
336+
:py:class:`postgres.Connection`. When the block starts, a
337+
:py:class:`~postgres.Connection` is checked out of the connection pool and
338+
:py:attr:`autocommit` is set to :py:const:`False`. When the block ends,
339+
:py:attr:`autocommit` is restored to :py:const:`True` and the
340+
:py:class:`~postgres.Connection` is rolled back before being put back in
341+
the pool.
342+
321343
"""
322344

323-
def __init__(self, pool, *a, **kw):
345+
def __init__(self, pool):
324346
self.pool = pool
325347
self.conn = None
326348

tests.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ def test_fetchall_fetches_all(self):
6262
class TestCursor(WithData):
6363

6464
def test_get_cursor_gets_a_cursor(self):
65-
with self.db.get_cursor("SELECT * FROM foo ORDER BY bar") as cursor:
65+
with self.db.get_cursor() as cursor:
66+
cursor.execute("SELECT * FROM foo ORDER BY bar")
6667
actual = cursor.fetchall()
6768
assert actual == [{"bar": "baz"}, {"bar": "buz"}]
6869

0 commit comments

Comments
 (0)