Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions django-stubs/core/validators.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections.abc import Callable, Collection, Sequence, Sized
from decimal import Decimal
from re import Pattern, RegexFlag
from typing import Any, TypeAlias
from typing import Any, Generic, TypeAlias, TypeVar, overload, type_check_only

from django.core.files.base import File
from django.utils.deconstruct import _Deconstructible
Expand All @@ -13,8 +13,19 @@ _Regex: TypeAlias = str | Pattern[str]

_ValidatorCallable: TypeAlias = Callable[[Any], None] # noqa: PYI047

_ClassT = TypeVar("_ClassT")
_InstanceT = TypeVar("_InstanceT")

@type_check_only
class ClassOrInstanceAttribute(Generic[_ClassT, _InstanceT]):
@overload
def __get__(self, obj: None, owner: type[object]) -> _ClassT: ...
@overload
def __get__(self, obj: object, owner: type[object]) -> _InstanceT: ...
def __set__(self, obj: object, value: _InstanceT) -> None: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need an overload for class / instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I've read in this thread https://discuss.python.org/t/support-different-type-for-class-variable-and-instance-variable/54198, descriptor only have a class level getter, setter and deleter only make sense at the instance level so this is probably the best we can get that satisfies mypy and pyright for the most likely usages.

I don't expect many user to try setting regex at the class level, looks like a convoluted way of defining a new class.


class RegexValidator(_Deconstructible):
regex: _Regex # Pattern[str] on instance, but may be str on class definition
regex: ClassOrInstanceAttribute[_Regex, Pattern[str]]
message: _StrOrPromise
code: str
inverse_match: bool
Expand Down
11 changes: 11 additions & 0 deletions tests/assert_type/core/test_validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from re import Pattern
from typing import assert_type

from django.contrib.auth.validators import UnicodeUsernameValidator
from django.core.validators import RegexValidator

assert_type(RegexValidator.regex, str | Pattern[str])
assert_type(RegexValidator().regex, Pattern[str])

assert_type(UnicodeUsernameValidator.regex, str | Pattern[str])
assert_type(UnicodeUsernameValidator().regex, Pattern[str])
Loading