@ -5,6 +5,8 @@ Note that this code requires psycopg2 and psycogreen, but the common module
as a whole does not to avoid needing to install them for components that don ' t need it.
as a whole does not to avoid needing to install them for components that don ' t need it.
"""
"""
from contextlib import contextmanager
import psycopg2
import psycopg2
import psycopg2 . extensions
import psycopg2 . extensions
import psycopg2 . extras
import psycopg2 . extras
@ -70,16 +72,15 @@ class DBManager(object):
the initial conn used to apply the schema , and you should use a real conn pool for
the initial conn used to apply the schema , and you should use a real conn pool for
any non - trivial use .
any non - trivial use .
Returned conns are set to seralizable isolation level , and use NamedTupleCursor cursors .
Returned conns are set to seralizable isolation level , autocommit , and use NamedTupleCursor cursors .
"""
"""
def __init__ ( self , * * connect_kwargs ) :
def __init__ ( self , * * connect_kwargs ) :
patch_psycopg ( )
patch_psycopg ( )
self . conns = [ ]
self . conns = [ ]
self . connect_kwargs = connect_kwargs
self . connect_kwargs = connect_kwargs
conn = self . get_conn ( )
conn = self . get_conn ( )
with conn :
with transaction ( conn ) :
with conn . cursor ( ) as cur :
query ( conn , SCHEMA )
cur . execute ( SCHEMA )
self . put_conn ( conn )
self . put_conn ( conn )
def put_conn ( self , conn ) :
def put_conn ( self , conn ) :
@ -93,24 +94,23 @@ class DBManager(object):
# we don't care about the performance concerns and everything we do is easily retryable.
# we don't care about the performance concerns and everything we do is easily retryable.
# This shouldn't matter in practice anyway since everything we're doing is either read-only
# This shouldn't matter in practice anyway since everything we're doing is either read-only
# searches or targetted single-row updates.
# searches or targetted single-row updates.
conn . set_session ( psycopg2 . extensions . ISOLATION_LEVEL_SERIALIZABLE )
conn . isolation_level = psycopg2 . extensions . ISOLATION_LEVEL_SERIALIZABLE
conn . autocommit = True
return conn
return conn
def retry_on_conflict ( conn , func , * args , * * kwargs ) :
@contextmanager
""" Run func(conn, *args, **kwargs) in a transaction up to max_tries (given as kwarg) times
def transaction ( conn ) :
( at least once , default 5 ) , retrying if it raises an error indicating a transaction conflict .
""" Helper context manager that runs the code block as a single database transaction
After max_tries , raises TransactionRollbackError .
instead of in autocommit mode . The only difference between this and " with conn " is
"""
that we explicitly disable then re - enable autocommit . """
max_tries = kwargs . pop ( ' max_tries ' , 5 )
old_autocommit = conn . autocommit
for _ in range ( max_tries - 1 ) :
conn . autocommit = False
try :
try :
with conn :
with conn :
return func ( conn )
yield
except psycopg2 . extensions . TransactionRollbackError :
finally :
pass
conn . autocommit = old_autocommit
with conn :
return func ( conn )
def query ( conn , query , * args , * * kwargs ) :
def query ( conn , query , * args , * * kwargs ) :