Skip to content

Commit 4daceb6

Browse files
committed
Make extract_postgres_error work with Psycopg 3.1
1 parent e6d0b8b commit 4daceb6

File tree

3 files changed

+48
-15
lines changed

3 files changed

+48
-15
lines changed

psqlextra/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.0.4"
1+
__version__ = "2.0.9rc3+swen.4"

psqlextra/error.py

+38-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
1-
from typing import Optional
2-
3-
import psycopg2
1+
from typing import Optional, Union
42

53
from django import db
64

5+
try:
6+
from psycopg2 import Error as Psycopg2Error
7+
except ImportError:
8+
Psycopg2Error = None
9+
10+
try:
11+
from psycopg import Error as Psycopg3Error
12+
except ImportError:
13+
Psycopg3Error = None
714

8-
def extract_postgres_error(error: db.Error) -> Optional[psycopg2.Error]:
15+
16+
def extract_postgres_error(
17+
error: db.Error,
18+
) -> Optional[Union["Psycopg2Error", "Psycopg3Error"]]:
919
"""Extracts the underlying :see:psycopg2.Error from the specified Django
1020
database error.
1121
@@ -14,7 +24,30 @@ def extract_postgres_error(error: db.Error) -> Optional[psycopg2.Error]:
1424
the cause of the error.
1525
"""
1626

17-
if not isinstance(error.__cause__, psycopg2.Error):
27+
if (Psycopg2Error and not isinstance(error.__cause__, Psycopg2Error)) and (
28+
Psycopg3Error and not isinstance(error.__cause__, Psycopg3Error)
29+
):
1830
return None
1931

2032
return error.__cause__
33+
34+
35+
def extract_postgres_error_code(error: db.Error) -> Optional[str]:
36+
"""Extracts the underlying Postgres error code.
37+
38+
As per PEP-249, Django wraps all database errors in its own
39+
exception. We can extract the underlying database error by examaning
40+
the cause of the error.
41+
"""
42+
43+
cause = error.__cause__
44+
if not cause:
45+
return None
46+
47+
if Psycopg2Error and isinstance(cause, Psycopg2Error):
48+
return cause.pgcode
49+
50+
if Psycopg3Error and isinstance(cause, Psycopg3Error):
51+
return cause.sqlstate
52+
53+
return None

tests/test_schema.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from django.db import InternalError, ProgrammingError, connection
66
from psycopg2 import errorcodes
77

8-
from psqlextra.error import extract_postgres_error
8+
from psqlextra.error import extract_postgres_error_code
99
from psqlextra.schema import PostgresSchema, postgres_temporary_schema
1010

1111

@@ -92,8 +92,8 @@ def test_postgres_schema_delete_and_create():
9292
with pytest.raises(InternalError) as exc_info:
9393
schema = PostgresSchema.delete_and_create(schema.name)
9494

95-
pg_error = extract_postgres_error(exc_info.value)
96-
assert pg_error.pgcode == errorcodes.DEPENDENT_OBJECTS_STILL_EXIST
95+
pg_error = extract_postgres_error_code(exc_info.value)
96+
assert pg_error == errorcodes.DEPENDENT_OBJECTS_STILL_EXIST
9797

9898
# Verify that the schema and table still exist
9999
assert _does_schema_exist(schema.name)
@@ -112,8 +112,8 @@ def test_postgres_schema_delete_and_create():
112112
cursor.execute("SELECT * FROM test.bla")
113113
assert cursor.fetchone() == ("hello",)
114114

115-
pg_error = extract_postgres_error(exc_info.value)
116-
assert pg_error.pgcode == errorcodes.UNDEFINED_TABLE
115+
pg_error = extract_postgres_error_code(exc_info.value)
116+
assert pg_error == errorcodes.UNDEFINED_TABLE
117117

118118

119119
def test_postgres_schema_delete():
@@ -134,8 +134,8 @@ def test_postgres_schema_delete_not_empty():
134134
with pytest.raises(InternalError) as exc_info:
135135
schema.delete()
136136

137-
pg_error = extract_postgres_error(exc_info.value)
138-
assert pg_error.pgcode == errorcodes.DEPENDENT_OBJECTS_STILL_EXIST
137+
pg_error = extract_postgres_error_code(exc_info.value)
138+
assert pg_error == errorcodes.DEPENDENT_OBJECTS_STILL_EXIST
139139

140140

141141
def test_postgres_schema_delete_cascade_not_empty():
@@ -176,8 +176,8 @@ def test_postgres_temporary_schema_not_empty():
176176
f"CREATE TABLE {schema.name}.mytable AS SELECT 'hello world'"
177177
)
178178

179-
pg_error = extract_postgres_error(exc_info.value)
180-
assert pg_error.pgcode == errorcodes.DEPENDENT_OBJECTS_STILL_EXIST
179+
pg_error = extract_postgres_error_code(exc_info.value)
180+
assert pg_error == errorcodes.DEPENDENT_OBJECTS_STILL_EXIST
181181

182182

183183
def test_postgres_temporary_schema_not_empty_cascade():

0 commit comments

Comments
 (0)