Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add compatibility with Psycopg 3 #202

Merged
merged 8 commits into from
Aug 8, 2023
Merged
18 changes: 16 additions & 2 deletions drf_extra_fields/compat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
import django

try:
from django.contrib.postgres.fields import FloatRangeField
from django.contrib.postgres import fields as postgres_fields

if django.VERSION >= (4, 2):
try:
from psycopg.types.range import DateRange, NumericRange
from psycopg.types.range import TimestamptzRange as DateTimeTZRange
except ImportError:
from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange
else:
from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange
KaratasFurkan marked this conversation as resolved.
Show resolved Hide resolved
except ImportError:
KaratasFurkan marked this conversation as resolved.
Show resolved Hide resolved
FloatRangeField = None
postgres_fields = None
DateRange = None
DateTimeTZRange = None
NumericRange = None
34 changes: 13 additions & 21 deletions drf_extra_fields/fields.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,28 @@
import base64
import binascii
import filetype
import io
import uuid

import filetype
from django.core.exceptions import ValidationError
from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils.translation import gettext_lazy as _
from rest_framework.fields import (
DateField,
DateTimeField,
DecimalField,
DictField,
EmailField,
FileField,
FloatField,
ImageField,
IntegerField,
DecimalField,
)
from rest_framework.serializers import ModelSerializer
from rest_framework.utils import html
from drf_extra_fields import compat

try:
from django.contrib.postgres import fields as postgres_fields
from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange
except ImportError:
postgres_fields = None
DateRange = None
DateTimeTZRange = None
NumericRange = None

from drf_extra_fields import compat
from drf_extra_fields.compat import DateRange, DateTimeTZRange, NumericRange

DEFAULT_CONTENT_TYPE = "application/octet-stream"

Expand Down Expand Up @@ -195,8 +187,8 @@ class RangeField(DictField):
})

def __init__(self, **kwargs):
if postgres_fields is None:
assert False, "'psgl2' is required to use {name}. Please install the 'psycopg2' library from 'pip'".format(
if compat.postgres_fields is None:
assert False, "'psycopg' is required to use {name}. Please install the 'psycopg2' (or 'psycopg' if you are using Django>=4.2) library from 'pip'".format(
name=self.__class__.__name__
)

Expand Down Expand Up @@ -301,15 +293,15 @@ class DateRangeField(RangeField):
range_type = DateRange


if postgres_fields:
if compat.postgres_fields:
# monkey patch modelserializer to map Native django Range fields to
# drf_extra_fiels's Range fields.
ModelSerializer.serializer_field_mapping[postgres_fields.DateTimeRangeField] = DateTimeRangeField
ModelSerializer.serializer_field_mapping[postgres_fields.DateRangeField] = DateRangeField
ModelSerializer.serializer_field_mapping[postgres_fields.IntegerRangeField] = IntegerRangeField
ModelSerializer.serializer_field_mapping[postgres_fields.DecimalRangeField] = DecimalRangeField
if compat.FloatRangeField:
ModelSerializer.serializer_field_mapping[compat.FloatRangeField] = FloatRangeField
ModelSerializer.serializer_field_mapping[compat.postgres_fields.DateTimeRangeField] = DateTimeRangeField
ModelSerializer.serializer_field_mapping[compat.postgres_fields.DateRangeField] = DateRangeField
ModelSerializer.serializer_field_mapping[compat.postgres_fields.IntegerRangeField] = IntegerRangeField
ModelSerializer.serializer_field_mapping[compat.postgres_fields.DecimalRangeField] = DecimalRangeField
if hasattr(compat.postgres_fields, "FloatRangeField"):
ModelSerializer.serializer_field_mapping[compat.postgres_fields.FloatRangeField] = FloatRangeField


class LowercaseEmailField(EmailField):
Expand Down
1 change: 0 additions & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
Pillow >= 6.2.1
pytest-django
pytest-cov
psycopg2-binary
flake8
34 changes: 17 additions & 17 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
import base64
import copy
import datetime
import django
import os
from decimal import Decimal
from unittest.mock import patch

import django
import pytest
import pytz
from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings
from unittest.mock import patch
from psycopg2._range import NumericRange, DateTimeTZRange, DateRange
from rest_framework import serializers
from rest_framework.fields import DecimalField

from drf_extra_fields import compat
from drf_extra_fields.compat import DateRange, DateTimeTZRange, NumericRange
from drf_extra_fields.fields import (
Base64ImageField,
Base64FileField,
Base64ImageField,
DateRangeField,
DateTimeRangeField,
DecimalRangeField,
FloatRangeField,
HybridImageField,
IntegerRangeField,
LowercaseEmailField,
DecimalRangeField,
)
from drf_extra_fields.geo_fields import PointField
from drf_extra_fields import compat


class UploadedBase64Image:
Expand Down Expand Up @@ -417,7 +417,7 @@ class DateTimeRangeSerializer(serializers.Serializer):

class DateRangeSerializer(serializers.Serializer):

range = DateRangeField(initial=DateRange(None, None))
range = DateRangeField(initial=DateRange(None, None, '()'))


class DateRangeWithAllowEmptyFalseSerializer(serializers.Serializer):
Expand Down Expand Up @@ -504,7 +504,7 @@ class TestIntegerRangeField(FieldValues):
(NumericRange(**{'lower': '1', 'upper': '2'}),
{'lower': 1, 'upper': 2, 'bounds': '[)'}),
(NumericRange(**{'empty': True}), {'empty': True}),
(NumericRange(), {'bounds': '[)', 'lower': None, 'upper': None}),
(NumericRange(bounds='()'), {'bounds': '()', 'lower': None, 'upper': None}),
({'lower': '1', 'upper': 2, 'bounds': '[)'},
{'lower': 1, 'upper': 2, 'bounds': '[)'}),
({'lower': 1, 'upper': 2},
Expand Down Expand Up @@ -557,7 +557,7 @@ class TestIntegerRangeChildAllowNullField(FieldValues):
(NumericRange(**{'lower': '1', 'upper': '2'}),
{'lower': 1, 'upper': 2, 'bounds': '[)'}),
(NumericRange(**{'empty': True}), {'empty': True}),
(NumericRange(), {'bounds': '[)', 'lower': None, 'upper': None}),
(NumericRange(bounds='()'), {'bounds': '()', 'lower': None, 'upper': None}),
({'lower': '1', 'upper': 2, 'bounds': '[)'},
{'lower': 1, 'upper': 2, 'bounds': '[)'}),
({'lower': 1, 'upper': 2},
Expand Down Expand Up @@ -596,7 +596,7 @@ class TestDecimalRangeField(FieldValues):
(NumericRange(**{'lower': '1.1', 'upper': '2'}),
{'lower': '1.1', 'upper': '2', 'bounds': '[)'}),
(NumericRange(**{'empty': True}), {'empty': True}),
(NumericRange(), {'bounds': '[)', 'lower': None, 'upper': None}),
(NumericRange(bounds='()'), {'bounds': '()', 'lower': None, 'upper': None}),
({'lower': Decimal('1.1'), 'upper': "2.3", 'bounds': '[)'},
{'lower': "1.1", 'upper': "2.3", 'bounds': '[)'}),
({'lower': Decimal('1.1'), 'upper': "2.3"},
Expand Down Expand Up @@ -647,7 +647,7 @@ class TestDecimalRangeFieldWithChildAttribute(FieldValues):
(NumericRange(**{'lower': '1.1', 'upper': '2'}),
{'lower': '1.10', 'upper': '2.00', 'bounds': '[)'}),
(NumericRange(**{'empty': True}), {'empty': True}),
(NumericRange(), {'bounds': '[)', 'lower': None, 'upper': None}),
(NumericRange(bounds='()'), {'bounds': '()', 'lower': None, 'upper': None}),
({'lower': Decimal('1.1'), 'upper': "2.3", 'bounds': '[)'},
{'lower': "1.10", 'upper': "2.30", 'bounds': '[)'}),
({'lower': Decimal('1.1'), 'upper': "2.3"},
Expand All @@ -660,7 +660,7 @@ class TestDecimalRangeFieldWithChildAttribute(FieldValues):
]


@pytest.mark.skipif(django.VERSION >= (3, 1) or compat.FloatRangeField is None,
@pytest.mark.skipif(django.VERSION >= (3, 1) or not hasattr(compat.postgres_fields, "FloatRangeField"),
reason='FloatRangeField deprecated on django 3.1 ')
class TestFloatRangeField(FieldValues):
"""
Expand Down Expand Up @@ -690,7 +690,7 @@ class TestFloatRangeField(FieldValues):
(NumericRange(**{'lower': '1.1', 'upper': '2'}),
{'lower': 1.1, 'upper': 2, 'bounds': '[)'}),
(NumericRange(**{'empty': True}), {'empty': True}),
(NumericRange(), {'bounds': '[)', 'lower': None, 'upper': None}),
(NumericRange(bounds='()'), {'bounds': '()', 'lower': None, 'upper': None}),
({'lower': '1', 'upper': 2., 'bounds': '[)'},
{'lower': 1., 'upper': 2., 'bounds': '[)'}),
({'lower': 1., 'upper': 2.},
Expand Down Expand Up @@ -760,8 +760,8 @@ class TestDateTimeRangeField(TestCase, FieldValues):
'bounds': '[)'}),
(DateTimeTZRange(**{'empty': True}),
{'empty': True}),
(DateTimeTZRange(),
{'bounds': '[)', 'lower': None, 'upper': None}),
(DateTimeTZRange(bounds='()'),
{'bounds': '()', 'lower': None, 'upper': None}),
({'lower': '2001-01-01T13:00:00Z',
'upper': '2001-02-02T13:00:00Z',
'bounds': '[)'},
Expand Down Expand Up @@ -838,7 +838,7 @@ class TestDateRangeField(FieldValues):
'bounds': '[)'}),
(DateRange(**{'empty': True}),
{'empty': True}),
(DateRange(), {'bounds': '[)', 'lower': None, 'upper': None}),
(DateRange(bounds='()'), {'bounds': '()', 'lower': None, 'upper': None}),
({'lower': '2001-01-01',
'upper': '2001-02-02',
'bounds': '[)'},
Expand Down Expand Up @@ -870,7 +870,7 @@ def test_no_source_on_child(self):

def test_initial_value_of_field(self):
serializer = DateRangeSerializer()
assert serializer.data['range'] == {'lower': None, 'upper': None, 'bounds': '[)'}
assert serializer.data['range'] == {'lower': None, 'upper': None, 'bounds': '()'}

def test_allow_empty(self):
serializer = DateRangeWithAllowEmptyFalseSerializer(data={"range": {}})
Expand Down
4 changes: 2 additions & 2 deletions tests/test_model_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ class Meta:
app_label = 'tests'


@pytest.mark.skipif(django.VERSION >= (3, 1) or compat.FloatRangeField is None,
@pytest.mark.skipif(django.VERSION >= (3, 1) or not hasattr(compat.postgres_fields, "FloatRangeField"),
reason='FloatRangeField deprecated on django 3.1 ')
class TestFloatRangeFieldMapping(TestCase):

def test_float_range_field(self):
class FloatRangeFieldModel(models.Model):
float_range_field = compat.FloatRangeField()
float_range_field = compat.postgres_fields.FloatRangeField()

class Meta:
app_label = 'tests'
Expand Down
9 changes: 6 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ python =
[tox]
envlist =
flake8,
py{37,38,39,310}-drf3-django{22,32}
py{38,39,310}-drf3-django40
py{38,39,310,311}-drf3-django{41,42}
py{37,38,39,310}-drf3-django{22,32}-psycopg2
py{38,39,310}-drf3-django40-psycopg2
py{38,39,310,311}-drf3-django{41,42}-psycopg2
py{38,39,310,311}-drf3-django42-psycopg3

[testenv]
deps =
Expand All @@ -21,6 +22,8 @@ deps =
django41: Django>=4.1,<4.2
django42: Django>=4.2,<4.3
drf3: djangorestframework>=3
psycopg2: psycopg2-binary
psycopg3: psycopg[binary]
-r requirements_dev.txt
commands =
py.test {posargs} --cov-report=xml --cov
Expand Down
Loading