From 0e0856b2a57566e1d272a9d5631de0206757a24d Mon Sep 17 00:00:00 2001 From: Joshua Munn Date: Wed, 26 Jun 2024 18:17:57 +0100 Subject: [PATCH 1/8] Update Django testenvs --- hypothesis-python/tox.ini | 16 ++++++---------- tooling/src/hypothesistooling/__main__.py | 2 +- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/hypothesis-python/tox.ini b/hypothesis-python/tox.ini index 6042594930..46cf4633cd 100644 --- a/hypothesis-python/tox.ini +++ b/hypothesis-python/tox.ini @@ -156,22 +156,18 @@ commands = niche: bash scripts/other-tests.sh custom: python -bb -X dev -m pytest {posargs} -[testenv:django32] -commands = - pip install .[pytz] - pip install django~=3.2.15 - python -bb -X dev -m tests.django.manage test tests.django {posargs} - -[testenv:django41] +[testenv:django42] +setenv= + PYTHONWARNDEFAULTENCODING=1 commands = - pip install django~=4.1.0 + pip install django~=4.2.0 python -bb -X dev -m tests.django.manage test tests.django {posargs} -[testenv:django42] +[testenv:django50] setenv= PYTHONWARNDEFAULTENCODING=1 commands = - pip install django~=4.2.0 + pip install django~=5.0.0 python -bb -X dev -m tests.django.manage test tests.django {posargs} [testenv:py{37,38,39}-nose] diff --git a/tooling/src/hypothesistooling/__main__.py b/tooling/src/hypothesistooling/__main__.py index f57bbb0804..6933f50f6c 100644 --- a/tooling/src/hypothesistooling/__main__.py +++ b/tooling/src/hypothesistooling/__main__.py @@ -497,7 +497,7 @@ def standard_tox_task(name, py=ci_version): standard_tox_task("py39-pytest54", py="3.9") standard_tox_task("pytest62") -for n in [32, 41, 42]: +for n in [42, 50]: standard_tox_task(f"django{n}") for n in [13, 14, 15, 20, 21, 22]: From 81a57fd479c75d72a65ca0aa226985b4c364dfba Mon Sep 17 00:00:00 2001 From: Joshua Munn Date: Wed, 26 Jun 2024 22:07:22 +0100 Subject: [PATCH 2/8] Add Django 5.0 support --- hypothesis-python/src/hypothesis/extra/django/_fields.py | 4 +++- hypothesis-python/tests/django/manage.py | 6 ++++++ hypothesis-python/tests/django/toys/settings.py | 9 ++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/hypothesis-python/src/hypothesis/extra/django/_fields.py b/hypothesis-python/src/hypothesis/extra/django/_fields.py index 181c8869f9..29f6dcf00a 100644 --- a/hypothesis-python/src/hypothesis/extra/django/_fields.py +++ b/hypothesis-python/src/hypothesis/extra/django/_fields.py @@ -57,7 +57,9 @@ def inner(field): def timezones(): # From Django 4.0, the default is to use zoneinfo instead of pytz. assert getattr(django.conf.settings, "USE_TZ", False) - if getattr(django.conf.settings, "USE_DEPRECATED_PYTZ", True): + if django.VERSION < (5, 0, 0) and getattr( + django.conf.settings, "USE_DEPRECATED_PYTZ", True + ): from hypothesis.extra.pytz import timezones else: from hypothesis.strategies import timezones diff --git a/hypothesis-python/tests/django/manage.py b/hypothesis-python/tests/django/manage.py index be364ea9a3..6d1de7eebd 100755 --- a/hypothesis-python/tests/django/manage.py +++ b/hypothesis-python/tests/django/manage.py @@ -38,6 +38,12 @@ except ImportError: RemovedInDjango50Warning = () + try: + from django.utils.deprecation import RemovedInDjango60Warning + except ImportError: + RemovedInDjango60Warning = () + with warnings.catch_warnings(): warnings.simplefilter("ignore", category=RemovedInDjango50Warning) + warnings.simplefilter("ignore", category=RemovedInDjango60Warning) execute_from_command_line(sys.argv) diff --git a/hypothesis-python/tests/django/toys/settings.py b/hypothesis-python/tests/django/toys/settings.py index 6f753e9eca..6f8ae97fb5 100644 --- a/hypothesis-python/tests/django/toys/settings.py +++ b/hypothesis-python/tests/django/toys/settings.py @@ -19,6 +19,8 @@ import os +import django + # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(__file__)) @@ -85,7 +87,8 @@ USE_I18N = True -USE_L10N = True +if django.VERSION < (5, 0, 0): + USE_L10N = True USE_TZ = os.environ.get("HYPOTHESIS_DJANGO_USETZ", "TRUE") == "TRUE" @@ -121,3 +124,7 @@ "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] + +# Transitional setting until 6.0. See +# https://docs.djangoproject.com/en/5.0/ref/forms/fields/#django.forms.URLField.assume_scheme +FORMS_URLFIELD_ASSUME_HTTPS = True From c8c6aac774a8403c1f68f3ef2dec9e9c40d7a225 Mon Sep 17 00:00:00 2001 From: Joshua Munn Date: Thu, 27 Jun 2024 22:20:24 +0100 Subject: [PATCH 3/8] Add handling and tests for Django 5.0's GeneratedField --- .../src/hypothesis/extra/django/_impl.py | 4 ++++ .../tests/django/toystore/models.py | 22 +++++++++++++++++++ .../django/toystore/test_given_models.py | 17 ++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/hypothesis-python/src/hypothesis/extra/django/_impl.py b/hypothesis-python/src/hypothesis/extra/django/_impl.py index 5a7ab8f0e3..2721c453ee 100644 --- a/hypothesis-python/src/hypothesis/extra/django/_impl.py +++ b/hypothesis-python/src/hypothesis/extra/django/_impl.py @@ -13,6 +13,7 @@ from functools import partial from typing import TYPE_CHECKING, Optional, Type, TypeVar, Union +import django from django import forms as df, test as dt from django.contrib.staticfiles import testing as dst from django.core.exceptions import ValidationError @@ -105,6 +106,9 @@ def from_model( name not in field_strategies and not field.auto_created and not isinstance(field, dm.AutoField) + and not ( + django.VERSION >= (5, 0, 0) and isinstance(field, dm.GeneratedField) + ) and field.default is dm.fields.NOT_PROVIDED ): field_strategies[name] = from_field(field) diff --git a/hypothesis-python/tests/django/toystore/models.py b/hypothesis-python/tests/django/toystore/models.py index 80810fc8c1..b945f87243 100644 --- a/hypothesis-python/tests/django/toystore/models.py +++ b/hypothesis-python/tests/django/toystore/models.py @@ -8,7 +8,9 @@ # v. 2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at https://mozilla.org/MPL/2.0/. +import django from django.core.exceptions import ValidationError +from django.core.validators import MinValueValidator from django.db import models @@ -149,3 +151,23 @@ class CompanyExtension(models.Model): class UserSpecifiedAutoId(models.Model): my_id = models.AutoField(primary_key=True) + + +if django.VERSION >= (5, 0, 0): + import math + + class Pizza(models.Model): + AREA = math.pi * models.F("radius") ** 2 + + radius = models.IntegerField(validators=[MinValueValidator(1)]) + slices = models.PositiveIntegerField(validators=[MinValueValidator(2)]) + total_area = models.GeneratedField( + expression=AREA, + output_field=models.FloatField(), + db_persist=True, + ) + slice_area = models.GeneratedField( + expression=AREA / models.F("slices"), + output_field=models.FloatField(), + db_persist=False, + ) diff --git a/hypothesis-python/tests/django/toystore/test_given_models.py b/hypothesis-python/tests/django/toystore/test_given_models.py index ff291cd63e..8e393ca25c 100644 --- a/hypothesis-python/tests/django/toystore/test_given_models.py +++ b/hypothesis-python/tests/django/toystore/test_given_models.py @@ -11,6 +11,7 @@ import datetime as dt from uuid import UUID +import django from django.conf import settings as django_settings from django.contrib.auth.models import User @@ -210,3 +211,19 @@ class TestUserSpecifiedAutoId(TestCase): def test_user_specified_auto_id(self, user_specified_auto_id): self.assertIsInstance(user_specified_auto_id, UserSpecifiedAutoId) self.assertIsNotNone(user_specified_auto_id.pk) + + +if django.VERSION >= (5, 0, 0): + from tests.django.toystore.models import Pizza + + class TestModelWithGeneratedField(TestCase): + @given(from_model(Pizza)) + def test_create_pizza(self, pizza): + """ + Strategies are not inferred for GeneratedField. + """ + + pizza.full_clean() + # Check the expected types of the generated fields. + self.assertIsInstance(pizza.slice_area, float) + self.assertIsInstance(pizza.total_area, float) From 74012a557b986a2aaa6a3d9d100508c51fe50a0f Mon Sep 17 00:00:00 2001 From: Joshua Munn Date: Mon, 1 Jul 2024 08:33:50 +0100 Subject: [PATCH 4/8] Add release notes --- hypothesis-python/RELEASE.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 hypothesis-python/RELEASE.rst diff --git a/hypothesis-python/RELEASE.rst b/hypothesis-python/RELEASE.rst new file mode 100644 index 0000000000..dcf027ccf6 --- /dev/null +++ b/hypothesis-python/RELEASE.rst @@ -0,0 +1,5 @@ +RELEASE_TYPE: minor + +This release adds support for Django 5.0, and drops support for Django < 4.2. + +Thanks to Joshua Munn for this contribution. From e1c13b7da114d064a260b9f94553afbd702b4da8 Mon Sep 17 00:00:00 2001 From: Joshua Munn Date: Tue, 2 Jul 2024 20:49:27 +0100 Subject: [PATCH 5/8] Shorten GeneratedField check Co-authored-by: Zac Hatfield-Dodds --- hypothesis-python/src/hypothesis/extra/django/_impl.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hypothesis-python/src/hypothesis/extra/django/_impl.py b/hypothesis-python/src/hypothesis/extra/django/_impl.py index 2721c453ee..82b478fc7f 100644 --- a/hypothesis-python/src/hypothesis/extra/django/_impl.py +++ b/hypothesis-python/src/hypothesis/extra/django/_impl.py @@ -106,9 +106,7 @@ def from_model( name not in field_strategies and not field.auto_created and not isinstance(field, dm.AutoField) - and not ( - django.VERSION >= (5, 0, 0) and isinstance(field, dm.GeneratedField) - ) + and not isinstance(field, getattr(dm, "GeneratedField", ())) and field.default is dm.fields.NOT_PROVIDED ): field_strategies[name] = from_field(field) From 2fcf81799d399e77c09d88beee80e1f3bd9baf47 Mon Sep 17 00:00:00 2001 From: Joshua Munn Date: Tue, 2 Jul 2024 20:49:41 +0100 Subject: [PATCH 6/8] Improve release notes Co-authored-by: Zac Hatfield-Dodds --- hypothesis-python/RELEASE.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hypothesis-python/RELEASE.rst b/hypothesis-python/RELEASE.rst index dcf027ccf6..15582e3079 100644 --- a/hypothesis-python/RELEASE.rst +++ b/hypothesis-python/RELEASE.rst @@ -1,5 +1,5 @@ RELEASE_TYPE: minor -This release adds support for Django 5.0, and drops support for Django < 4.2. +This release improves support for Django 5.0, and drops support for end-of-life Django versions (< 4.2). Thanks to Joshua Munn for this contribution. From fbc4b160bda839edc29c85d72d6380d48de2ba05 Mon Sep 17 00:00:00 2001 From: Joshua Munn Date: Tue, 2 Jul 2024 20:50:32 +0100 Subject: [PATCH 7/8] Update Django testenvs in github action --- .github/workflows/main.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3d4b17d2ab..1668099972 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -67,9 +67,8 @@ jobs: - check-py39-pytest46 - check-py39-pytest54 - check-pytest62 + - check-django50 - check-django42 - - check-django41 - - check-django32 - check-pandas22 - check-pandas21 - check-pandas20 From b31cea0423057bf5c4a05024145e1380c7be0891 Mon Sep 17 00:00:00 2001 From: Joshua Munn Date: Tue, 2 Jul 2024 20:55:06 +0100 Subject: [PATCH 8/8] fixup! Shorten GeneratedField check --- hypothesis-python/src/hypothesis/extra/django/_impl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hypothesis-python/src/hypothesis/extra/django/_impl.py b/hypothesis-python/src/hypothesis/extra/django/_impl.py index 82b478fc7f..d4bcefb0c1 100644 --- a/hypothesis-python/src/hypothesis/extra/django/_impl.py +++ b/hypothesis-python/src/hypothesis/extra/django/_impl.py @@ -13,7 +13,6 @@ from functools import partial from typing import TYPE_CHECKING, Optional, Type, TypeVar, Union -import django from django import forms as df, test as dt from django.contrib.staticfiles import testing as dst from django.core.exceptions import ValidationError