Skip to content

Commit

Permalink
Add support for psycopg3
Browse files Browse the repository at this point in the history
Django 4.2+ supports psycopg3 and will prefer it over psycopg2 if
it is installed. Follow the same approach here to ensure that this
package behaves in a manner consistent with Django itself.
  • Loading branch information
ngnpope committed Mar 1, 2024
1 parent 745ff9f commit dba108f
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 13 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This package is tested against:
- Python 3.10, 3.11, or 3.12.
- Django 4.1, 4.2, or 5.0.
- PostgreSQL 12 to 16
- psycopg2 and psycopg3

## Local development

Expand Down
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ name = "django_integrity"
version = "0.0-alpha"
requires-python = ">=3.10"
dependencies = [
# Psycopg2 is required, but we do not include it here.
# We cannot decide for users if they want to use psycopg2 or psycopg2-binary.
# We cannot decide for users if they want to use psycopg2, psycopg2-binary, or
# psycopg (i.e. psycopg3) with or without the [binary] extra. It should be part of
# their own project dependencies anyway.
# See https://www.psycopg.org/docs/install.html#psycopg-vs-psycopg-binary
# See https://www.psycopg.org/psycopg3/docs/basic/install.html#binary-installation
]
classifiers = [
"Development Status :: 3 - Alpha",
Expand Down
6 changes: 5 additions & 1 deletion src/django_integrity/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
from collections.abc import Iterator, Sequence

from django import db as django_db
from psycopg2 import sql

try:
from psycopg import sql
except ImportError:
from psycopg2 import sql


# Note [Deferrable constraints]
Expand Down
16 changes: 10 additions & 6 deletions src/django_integrity/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
import re
from collections.abc import Iterator, Mapping

import psycopg2
from django import db as django_db

try:
import psycopg
except ImportError:
import psycopg2 as psycopg


@contextlib.contextmanager
def refine_integrity_error(rules: Mapping[_Rule, Exception]) -> Iterator[None]:
Expand Down Expand Up @@ -57,7 +61,7 @@ class Named(_Rule):
name: str

def is_match(self, error: django_db.IntegrityError) -> bool:
if not isinstance(error.__cause__, psycopg2.errors.IntegrityError):
if not isinstance(error.__cause__, psycopg.errors.IntegrityError):
return False

return error.__cause__.diag.constraint_name == self.name
Expand All @@ -75,7 +79,7 @@ class Unique(_Rule):
_pattern = re.compile(r"Key \((?P<fields>.+)\)=\(.*\) already exists.")

def is_match(self, error: django_db.IntegrityError) -> bool:
if not isinstance(error.__cause__, psycopg2.errors.UniqueViolation):
if not isinstance(error.__cause__, psycopg.errors.UniqueViolation):
return False

match = self._pattern.match(error.__cause__.diag.message_detail)
Expand Down Expand Up @@ -114,7 +118,7 @@ def __post_init__(self):
raise ModelHasNoPrimaryKey

def is_match(self, error: django_db.IntegrityError) -> bool:
if not isinstance(error.__cause__, psycopg2.errors.UniqueViolation):
if not isinstance(error.__cause__, psycopg.errors.UniqueViolation):
return False

match = self._pattern.match(error.__cause__.diag.message_detail)
Expand Down Expand Up @@ -148,7 +152,7 @@ class NotNull(_Rule):
field: str

def is_match(self, error: django_db.IntegrityError) -> bool:
if not isinstance(error.__cause__, psycopg2.errors.NotNullViolation):
if not isinstance(error.__cause__, psycopg.errors.NotNullViolation):
return False

return (
Expand All @@ -171,7 +175,7 @@ class ForeignKey(_Rule):
)

def is_match(self, error: django_db.IntegrityError) -> bool:
if not isinstance(error.__cause__, psycopg2.errors.ForeignKeyViolation):
if not isinstance(error.__cause__, psycopg.errors.ForeignKeyViolation):
return False

detail_match = self._detail_pattern.match(error.__cause__.diag.message_detail)
Expand Down
11 changes: 7 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
[tox]
min_version = 4.0
env_list =
py312-django{42,50}
py311-django{41,42,50}
py310-django{41,42,50}
py311-django41-psycopg2
py310-django41-psycopg2
py312-django{42,50}-psycopg{2,3}
py311-django{41,42,50}-psycopg{2,3}
py310-django{41,42,50}-psycopg{2,3}

[testenv]
pass_env =
DATABASE_URL
deps =
-r requirements/development.txt
psycopg2-binary
django41: django>=4.1,<4.2
django42: django>=4.2,<5.0
django50: django>=5.0,<5.1
psycopg2: psycopg2-binary
psycopg3: psycopg[binary]

commands =
python -m pytest

0 comments on commit dba108f

Please sign in to comment.