Database docs also cleaned up postgres isolation level handling.

This commit is contained in:
Charles Leifer
2026-02-20 18:35:23 -06:00
parent cee3c8af32
commit 930f769979
5 changed files with 771 additions and 675 deletions
+13 -6
View File
@@ -620,8 +620,7 @@ Database
:param bool register_unicode: Register unicode types.
:param str encoding: Database encoding.
:param int isolation_level: Isolation level constant, defined in the
``psycopg2.extensions`` module or ``psycopg.connection.IsolationLevel``
enum (psycopg3).
``psycopg2.extensions`` module or ``psycopg.IsolationLevel`` enum (psycopg3).
:param bool prefer_psycopg3: If both psycopg2 and psycopg3 are installed,
instruct Peewee to prefer psycopg3.
@@ -633,21 +632,29 @@ Database
Set the timezone on the current connection. If no connection is open,
then one will be opened.
.. py:method:: set_isolation_level(isolation_level)
:param int isolation_level: Isolation level constant, defined in the
``psycopg2.extensions`` module or ``psycopg.IsolationLevel`` enum (psycopg3).
Set to ``None`` to use the server default.
.. py:method:: atomic(isolation_level=None)
:param str isolation_level: Isolation strategy: SERIALIZABLE, READ COMMITTED, REPEATABLE READ, READ UNCOMMITTED
:param isolation_level: Isolation strategy: SERIALIZABLE, READ COMMITTED, REPEATABLE READ, READ UNCOMMITTED
:type isolation_level: ``int`` or ``str``.
Create an atomic context-manager, optionally using the specified
isolation level (if unspecified, the server default will be used).
isolation level (if unspecified, the connection default will be used).
.. note:: Isolation level only applies to the outermost ``atomic()`` block.
.. py:method:: transaction(isolation_level=None)
:param str isolation_level: Isolation strategy: SERIALIZABLE, READ COMMITTED, REPEATABLE READ, READ UNCOMMITTED
:param isolation_level: Isolation strategy: SERIALIZABLE, READ COMMITTED, REPEATABLE READ, READ UNCOMMITTED
:type isolation_level: ``int`` or ``str``.
Create a transaction context-manager, optionally using the specified
isolation level (if unspecified, the server default will be used).
isolation level (if unspecified, the connection default will be used).
.. py:class:: MySQLDatabase(database, **kwargs)
+681 -657
View File
File diff suppressed because it is too large Load Diff
+47 -4
View File
@@ -3946,8 +3946,34 @@ class SqliteDatabase(Database):
return fn.datetime(date_field, 'unixepoch')
class Psycopg2Adapter(object):
class _BasePsycopgAdapter(object):
isolation_levels = {} # Map int -> str.
def __init__(self):
self.isolation_levels_inv = {
v: k for k, v in self.isolation_levels.items()}
def isolation_level_int(self, isolation_level):
if isinstance(isolation_level, str):
return self.isolation_levels_inv[isolation_level]
return isolation_level
def isolation_level_str(self, isolation_level):
if isinstance(isolation_level, int):
return self.isolation_levels[isolation_level]
return isolation_level
class Psycopg2Adapter(_BasePsycopgAdapter):
isolation_levels = {
1: 'READ COMMITTED',
2: 'REPEATABLE READ',
3: 'SERIALIZABLE',
4: 'READ UNCOMMITTED',
}
def __init__(self):
super(Psycopg2Adapter, self).__init__()
self.json_type = Json_pg2
self.jsonb_type = Json_pg2
self.cast_json_case = True
@@ -4005,8 +4031,16 @@ class Psycopg2Adapter(object):
return fn.EXTRACT(NodeList((date_part, SQL('FROM'), date_field)))
class Psycopg3Adapter(object):
class Psycopg3Adapter(_BasePsycopgAdapter):
isolation_levels = {
1: 'READ UNCOMMITTED',
2: 'READ COMMITTED',
3: 'REPEATABLE READ',
4: 'SERIALIZABLE',
}
def __init__(self):
super(Psycopg3Adapter, self).__init__()
self.json_type = Json_pg3
self.jsonb_type = Jsonb_pg3
self.cast_json_case = False
@@ -4084,7 +4118,6 @@ class PostgresqlDatabase(Database):
isolation_level=None, **kwargs):
self._register_unicode = register_unicode
self._encoding = encoding
self._isolation_level = isolation_level
prefer_psycopg3 = kwargs.pop('prefer_psycopg3', False)
if psycopg is not None and prefer_psycopg3:
@@ -4092,6 +4125,11 @@ class PostgresqlDatabase(Database):
else:
self._adapter = self.psycopg2_adapter()
# Accept a string ('READ COMMITTED') or an int constant. Since the
# constants vary between psycopg2 & psycopg3 we have to abstract this.
self._isolation_level = self._adapter.isolation_level_int(
isolation_level)
super(PostgresqlDatabase, self).init(database, **kwargs)
def _connect(self):
@@ -4137,7 +4175,8 @@ class PostgresqlDatabase(Database):
if self.is_closed():
self.connect()
if isolation_level:
stmt = 'BEGIN TRANSACTION ISOLATION LEVEL %s' % isolation_level
txn_type = self._adapter.isolation_level_str(isolation_level)
stmt = 'BEGIN TRANSACTION ISOLATION LEVEL %s' % txn_type
else:
stmt = 'BEGIN'
with __exception_wrapper__:
@@ -4282,6 +4321,10 @@ class PostgresqlDatabase(Database):
def set_time_zone(self, timezone):
self.execute_sql('set time zone "%s";' % timezone)
def set_isolation_level(self, isolation_level):
self._isolation_level = self._adapter.isolation_level_int(
isolation_level)
class MySQLDatabase(Database):
field_types = {
+9 -7
View File
@@ -9,9 +9,11 @@ from playhouse.cockroachdb import PooledCockroachDatabase
from playhouse.pool import PooledCySqliteDatabase
from playhouse.pool import PooledMySQLDatabase
from playhouse.pool import PooledPostgresqlDatabase
from playhouse.pool import PooledPostgresqlExtDatabase
from playhouse.pool import PooledPsycopg3Database
from playhouse.pool import PooledSqliteDatabase
from playhouse.pool import PooledSqliteExtDatabase
from playhouse.postgres_ext import PostgresqlExtDatabase
from playhouse.postgres_ext import Psycopg3Database
from playhouse.sqlite_ext import SqliteExtDatabase
try:
@@ -31,6 +33,10 @@ schemes = {
'postgresql': PostgresqlDatabase,
'postgres+pool': PooledPostgresqlDatabase,
'postgresql+pool': PooledPostgresqlDatabase,
'postgresext': PostgresqlExtDatabase,
'postgresqlext': PostgresqlExtDatabase,
'postgresext+pool': PooledPostgresqlExtDatabase,
'postgresqlext+pool': PooledPostgresqlExtDatabase,
'psycopg3': Psycopg3Database,
'psycopg3+pool': PooledPsycopg3Database,
'sqlite': SqliteDatabase,
@@ -38,11 +44,6 @@ schemes = {
'sqlite+pool': PooledSqliteDatabase,
'sqliteext+pool': PooledSqliteExtDatabase,
}
if CySqliteDatabase is not None:
schemes.update({
'cysqlite': CySqliteDatabase,
'cysqlite+pool': PooledCySqliteDatabase,
})
def register_database(db_class, *names):
global schemes
@@ -136,8 +137,9 @@ else:
register_database(APSWDatabase, 'apsw')
try:
from playhouse.postgres_ext import PostgresqlExtDatabase
from playhouse.cysqlite_ext import CySqliteDatabase
except ImportError:
pass
else:
register_database(PostgresqlExtDatabase, 'postgresext', 'postgresqlext')
register_database(CySqliteDatabase, 'cysqlite')
register_database(PooledCySqliteDatabase, 'cysqlite+pool')
+21 -1
View File
@@ -872,10 +872,30 @@ class TestPostgresIsolationLevel(DatabaseTestCase):
conn.set_isolation_level(2)
self.assertEqual(conn.isolation_level, 2)
self.database.close()
conn = self.database.connection()
self.assertEqual(conn.isolation_level, 3)
self.database.close()
self.database.set_isolation_level(2)
for _ in range(2):
conn = self.database.connection()
self.assertEqual(conn.isolation_level, 2)
self.database.close()
def test_isolation_level_str(self):
db = db_loader('postgres', isolation_level='SERIALIZABLE')
conn = db.connection()
self.assertEqual(conn.isolation_level,
db._adapter.isolation_levels_inv['SERIALIZABLE'])
db.close()
db.set_isolation_level('READ COMMITTED')
conn = db.connection()
self.assertEqual(conn.isolation_level,
db._adapter.isolation_levels_inv['READ COMMITTED'])
db.close()
@skip_unless(pg12(), 'cte materialization requires pg >= 12')