diff --git a/django-stubs/core/validators.pyi b/django-stubs/core/validators.pyi index 644106429..62a92fc38 100644 --- a/django-stubs/core/validators.pyi +++ b/django-stubs/core/validators.pyi @@ -14,7 +14,7 @@ _Regex: TypeAlias = str | Pattern[str] _ValidatorCallable: TypeAlias = Callable[[Any], None] # noqa: PYI047 class RegexValidator(_Deconstructible): - regex: _Regex # Pattern[str] on instance, but may be str on class definition + regex: Pattern[str] message: _StrOrPromise code: str inverse_match: bool diff --git a/scripts/stubtest/allowlist.txt b/scripts/stubtest/allowlist.txt index 401d9e586..397e61485 100644 --- a/scripts/stubtest/allowlist.txt +++ b/scripts/stubtest/allowlist.txt @@ -550,3 +550,10 @@ django.contrib.gis.forms.BaseModelFormSet.save_m2m # Dynamically generated in https://github.com/django/django/blob/0ee06c04e0256094270db3ffe8b5dafa6a8457a3/django/core/mail/backends/locmem.py#L24 django.core.mail.outbox + +# The type system does not allow to represent an attribute with different type at the class level (`str | Pattern[str]`) +# and instance (`Pattern[str]`) level. But in order to have more precise type at the instance level, we restrict the types +# allowed at the class level to a subset. In an ideal world, these should probably have different attribute names. +django.core.validators.RegexValidator.regex +django.contrib.auth.validators.ASCIIUsernameValidator.regex +django.contrib.auth.validators.UnicodeUsernameValidator.regex diff --git a/tests/assert_type/core/test_validators.py b/tests/assert_type/core/test_validators.py new file mode 100644 index 000000000..71e293ae2 --- /dev/null +++ b/tests/assert_type/core/test_validators.py @@ -0,0 +1,32 @@ +import re +from re import Pattern + +from django.contrib.auth.validators import UnicodeUsernameValidator +from django.core.validators import RegexValidator +from typing_extensions import assert_type + +assert_type(RegexValidator().regex, Pattern[str]) +RegexValidator().regex = re.compile("") + +assert_type(UnicodeUsernameValidator().regex, Pattern[str]) +UnicodeUsernameValidator().regex = re.compile("") + +# expect "Pattern[str]" +RegexValidator().regex = "" # type: ignore[assignment] # pyright: ignore[reportAttributeAccessIssue] +UnicodeUsernameValidator().regex = "" # type: ignore[assignment] # pyright: ignore[reportAttributeAccessIssue] + + +class RegexSubtype(RegexValidator): + regex = re.compile("abc") + + +# We would like to improve on these, it should allow "str | Pattern[str]": +assert_type(RegexValidator.regex, Pattern[str]) +assert_type(UnicodeUsernameValidator.regex, Pattern[str]) + +RegexValidator.regex = "" # type: ignore[assignment] # pyright: ignore[reportAttributeAccessIssue] +UnicodeUsernameValidator.regex = "" # type: ignore[assignment] # pyright: ignore[reportAttributeAccessIssue] + + +class StrSubtype(RegexValidator): + regex = "abc" # type: ignore[assignment] # pyright: ignore[reportAssignmentType]