Skip to content

Commit e29f60b

Browse files
committed
create and re-use TypeAliases and TypeVars for "user" and "any user"
This change also adds a few user type vars and alises to cover the common use cases of ``User``, ``User | AnonymousUser``, and their ``TypeVar`` forms for using in generic contexts.
1 parent a0dce16 commit e29f60b

File tree

13 files changed

+93
-102
lines changed

13 files changed

+93
-102
lines changed
Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
from typing import Any
22

33
from django.contrib.auth.backends import BaseBackend
4-
from django.contrib.auth.base_user import _UserModel
5-
from django.contrib.auth.models import AnonymousUser
4+
from django.contrib.auth.models import _AnyUser, _User
65
from django.db.models.options import Options
76
from django.http.request import HttpRequest
87
from django.test.client import Client
@@ -18,17 +17,15 @@ REDIRECT_FIELD_NAME: str
1817

1918
def load_backend(path: str) -> BaseBackend: ...
2019
def get_backends() -> list[BaseBackend]: ...
21-
def authenticate(request: HttpRequest | None = ..., **credentials: Any) -> _UserModel | None: ...
22-
async def aauthenticate(request: HttpRequest | None = ..., **credentials: Any) -> _UserModel | None: ...
23-
def login(request: HttpRequest, user: _UserModel | None, backend: type[BaseBackend] | str | None = ...) -> None: ...
24-
async def alogin(
25-
request: HttpRequest, user: _UserModel | None, backend: type[BaseBackend] | str | None = ...
26-
) -> None: ...
20+
def authenticate(request: HttpRequest | None = ..., **credentials: Any) -> _User | None: ...
21+
async def aauthenticate(request: HttpRequest | None = ..., **credentials: Any) -> _User | None: ...
22+
def login(request: HttpRequest, user: _User | None, backend: type[BaseBackend] | str | None = ...) -> None: ...
23+
async def alogin(request: HttpRequest, user: _User | None, backend: type[BaseBackend] | str | None = ...) -> None: ...
2724
def logout(request: HttpRequest) -> None: ...
2825
async def alogout(request: HttpRequest) -> None: ...
29-
def get_user_model() -> type[_UserModel]: ...
30-
def get_user(request: HttpRequest | Client) -> _UserModel | AnonymousUser: ...
31-
async def aget_user(request: HttpRequest | Client) -> _UserModel | AnonymousUser: ...
26+
def get_user_model() -> type[_User]: ...
27+
def get_user(request: HttpRequest | Client) -> _AnyUser: ...
28+
async def aget_user(request: HttpRequest | Client) -> _AnyUser: ...
3229
def get_permission_codename(action: str, opts: Options) -> str: ...
33-
def update_session_auth_hash(request: HttpRequest, user: _UserModel) -> None: ...
34-
async def aupdate_session_auth_hash(request: HttpRequest, user: _UserModel) -> None: ...
30+
def update_session_auth_hash(request: HttpRequest, user: _User) -> None: ...
31+
async def aupdate_session_auth_hash(request: HttpRequest, user: _User) -> None: ...
Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
1-
from typing import Any, TypeAlias, TypeVar
1+
from typing import Any, TypeAlias
22

3-
from django.contrib.auth.base_user import AbstractBaseUser, _UserModel
4-
from django.contrib.auth.models import AnonymousUser, Permission
3+
from django.contrib.auth.models import Permission, _AnyUser, _User, _UserType
54
from django.db.models import QuerySet
65
from django.db.models.base import Model
76
from django.http.request import HttpRequest
87

9-
UserModel: TypeAlias = type[_UserModel]
10-
_AnyUser: TypeAlias = _UserModel | AnonymousUser
8+
UserModel: TypeAlias = type[_User]
119

1210
class BaseBackend:
13-
def authenticate(self, request: HttpRequest | None, **kwargs: Any) -> _UserModel | None: ...
14-
async def aauthenticate(self, request: HttpRequest | None, **kwargs: Any) -> _UserModel | None: ...
15-
def get_user(self, user_id: Any) -> _UserModel | None: ...
16-
async def aget_user(self, user_id: Any) -> _UserModel | None: ...
11+
def authenticate(self, request: HttpRequest | None, **kwargs: Any) -> _User | None: ...
12+
async def aauthenticate(self, request: HttpRequest | None, **kwargs: Any) -> _User | None: ...
13+
def get_user(self, user_id: Any) -> _User | None: ...
14+
async def aget_user(self, user_id: Any) -> _User | None: ...
1715
def get_user_permissions(self, user_obj: _AnyUser, obj: Model | None = ...) -> set[str]: ...
1816
async def aget_user_permissions(self, user_obj: _AnyUser, obj: Model | None = ...) -> set[str]: ...
1917
def get_group_permissions(self, user_obj: _AnyUser, obj: Model | None = ...) -> set[str]: ...
@@ -26,10 +24,10 @@ class BaseBackend:
2624
class ModelBackend(BaseBackend):
2725
def authenticate(
2826
self, request: HttpRequest | None, username: str | None = ..., password: str | None = ..., **kwargs: Any
29-
) -> _UserModel | None: ...
27+
) -> _User | None: ...
3028
async def aauthenticate(
3129
self, request: HttpRequest | None, username: str | None = ..., password: str | None = ..., **kwargs: Any
32-
) -> _UserModel | None: ...
30+
) -> _User | None: ...
3331
def has_module_perms(self, user_obj: _AnyUser, app_label: str) -> bool: ...
3432
async def ahas_module_perms(self, user_obj: _AnyUser, app_label: str) -> bool: ...
3533
def user_can_authenticate(self, user: _AnyUser | None) -> bool: ...
@@ -39,15 +37,13 @@ class ModelBackend(BaseBackend):
3937
is_active: bool = ...,
4038
include_superusers: bool = ...,
4139
obj: Model | None = ...,
42-
) -> QuerySet[_UserModel]: ...
40+
) -> QuerySet[_User]: ...
4341

4442
class AllowAllUsersModelBackend(ModelBackend): ...
4543

46-
_U = TypeVar("_U", bound=AbstractBaseUser)
47-
4844
class RemoteUserBackend(ModelBackend):
4945
create_unknown_user: bool
5046
def clean_username(self, username: str) -> str: ...
51-
def configure_user(self, request: HttpRequest, user: _U, created: bool = ...) -> _U: ...
47+
def configure_user(self, request: HttpRequest, user: _UserType, created: bool = ...) -> _UserType: ...
5248

5349
class AllowAllUsersRemoteUserBackend(RemoteUserBackend): ...

django-stubs/contrib/auth/base_user.pyi

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from collections.abc import Iterable
2-
from typing import Any, ClassVar, Literal, TypeAlias, TypeVar, overload
2+
from typing import Any, ClassVar, Literal, TypeVar, overload
33

44
from django.db import models
55
from django.db.models.base import Model
@@ -41,8 +41,3 @@ class AbstractBaseUser(models.Model):
4141
@classmethod
4242
@overload
4343
def normalize_username(cls, username: Any) -> Any: ...
44-
45-
# This is our "placeholder" type the mypy plugin refines to configured 'AUTH_USER_MODEL'
46-
# wherever it is used as a type. The most recognised example of this is (probably)
47-
# `HttpRequest.user`
48-
_UserModel: TypeAlias = AbstractBaseUser # noqa: PYI047

django-stubs/contrib/auth/decorators.pyi

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
from collections.abc import Awaitable, Callable, Iterable
22
from typing import TypeVar, overload
33

4-
from django.contrib.auth.base_user import _UserModel
5-
from django.contrib.auth.models import AnonymousUser
4+
from django.contrib.auth.models import _AnyUser
65
from django.http.response import HttpResponseBase
76

87
_VIEW = TypeVar("_VIEW", bound=Callable[..., HttpResponseBase | Awaitable[HttpResponseBase]])
98

109
def user_passes_test(
11-
test_func: Callable[[_UserModel | AnonymousUser], bool],
10+
test_func: Callable[[_AnyUser], bool],
1211
login_url: str | None = ...,
1312
redirect_field_name: str | None = ...,
1413
) -> Callable[[_VIEW], _VIEW]: ...

django-stubs/contrib/auth/forms.pyi

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from collections.abc import Iterable
22
from logging import Logger
3-
from typing import Any, TypeAlias, TypeVar
3+
from typing import Any, Generic, TypeAlias
44

55
from django import forms
6-
from django.contrib.auth.base_user import AbstractBaseUser, _UserModel
6+
from django.contrib.auth.models import _User, _UserType
77
from django.contrib.auth.tokens import PasswordResetTokenGenerator
88
from django.core.exceptions import ValidationError
99
from django.db import models
@@ -15,8 +15,7 @@ from django.utils.functional import _StrOrPromise
1515

1616
logger: Logger
1717

18-
UserModel: TypeAlias = type[_UserModel]
19-
_User = TypeVar("_User", bound=AbstractBaseUser)
18+
UserModel: TypeAlias = type[_User]
2019

2120
class ReadOnlyPasswordHashWidget(forms.Widget):
2221
template_name: str
@@ -31,7 +30,7 @@ class UsernameField(forms.CharField):
3130
def to_python(self, value: Any | None) -> Any | None: ...
3231
def widget_attrs(self, widget: Widget) -> dict[str, Any]: ...
3332

34-
class SetPasswordMixin:
33+
class SetPasswordMixin(Generic[_UserType]):
3534
error_messages: _ErrorMessagesDict
3635

3736
@staticmethod
@@ -43,12 +42,12 @@ class SetPasswordMixin:
4342
password1_field_name: str = ...,
4443
password2_field_name: str = ...,
4544
) -> None: ...
46-
def validate_password_for_user(self, user: AbstractBaseUser, password_field_name: str = "password2") -> None: ...
45+
def validate_password_for_user(self, user: _UserType, password_field_name: str = "password2") -> None: ...
4746
def set_password_and_save(
48-
self, user: AbstractBaseUser, password_field_name: str = "password1", commit: bool = True
49-
) -> AbstractBaseUser: ...
47+
self, user: _UserType, password_field_name: str = "password1", commit: bool = True
48+
) -> _UserType: ...
5049

51-
class SetUnusablePasswordMixin:
50+
class SetUnusablePasswordMixin(Generic[_UserType]):
5251
usable_password_help_text: _StrOrPromise
5352

5453
@staticmethod
@@ -59,20 +58,20 @@ class SetUnusablePasswordMixin:
5958
password2_field_name: str = ...,
6059
usable_password_field_name: str = ...,
6160
) -> None: ...
62-
def validate_password_for_user(self, user: AbstractBaseUser, **kwargs: Any) -> None: ...
61+
def validate_password_for_user(self, user: _UserType, **kwargs: Any) -> None: ...
6362
def set_password_and_save(self, user: _User, commit: bool = True, **kwargs: Any) -> _User: ...
6463

65-
class BaseUserCreationForm(forms.ModelForm[_User]):
64+
class BaseUserCreationForm(Generic[_UserType], forms.ModelForm[_UserType]):
6665
error_messages: _ErrorMessagesDict
6766
password1: forms.Field
6867
password2: forms.Field
6968
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
70-
def save(self, commit: bool = ...) -> _User: ...
69+
def save(self, commit: bool = ...) -> _UserType: ...
7170

72-
class UserCreationForm(BaseUserCreationForm[_User]):
71+
class UserCreationForm(BaseUserCreationForm[_UserType]):
7372
def clean_username(self) -> str: ...
7473

75-
class UserChangeForm(forms.ModelForm[_User]):
74+
class UserChangeForm(forms.ModelForm[_UserType]):
7675
password: forms.Field
7776
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
7877

@@ -81,11 +80,11 @@ class AuthenticationForm(forms.Form):
8180
password: forms.Field
8281
error_messages: _ErrorMessagesDict
8382
request: HttpRequest | None
84-
user_cache: _UserModel | None
83+
user_cache: _User | None
8584
username_field: models.Field
8685
def __init__(self, request: HttpRequest | None = ..., *args: Any, **kwargs: Any) -> None: ...
87-
def confirm_login_allowed(self, user: AbstractBaseUser) -> None: ...
88-
def get_user(self) -> _UserModel: ...
86+
def confirm_login_allowed(self, user: _User) -> None: ...
87+
def get_user(self) -> _User: ...
8988
def get_invalid_login_error(self) -> ValidationError: ...
9089
def clean(self) -> dict[str, Any]: ...
9190

@@ -100,7 +99,7 @@ class PasswordResetForm(forms.Form):
10099
to_email: str,
101100
html_email_template_name: str | None = ...,
102101
) -> None: ...
103-
def get_users(self, email: str) -> Iterable[_UserModel]: ...
102+
def get_users(self, email: str) -> Iterable[_User]: ...
104103
def save(
105104
self,
106105
domain_override: str | None = ...,
@@ -114,28 +113,26 @@ class PasswordResetForm(forms.Form):
114113
extra_email_context: dict[str, str] | None = ...,
115114
) -> None: ...
116115

117-
class SetPasswordForm(forms.Form):
118-
error_messages: _ErrorMessagesDict
116+
class SetPasswordForm(Generic[_UserType], SetPasswordMixin, forms.Form):
119117
new_password1: forms.Field
120118
new_password2: forms.Field
121-
user: AbstractBaseUser
122-
def __init__(self, user: AbstractBaseUser, *args: Any, **kwargs: Any) -> None: ...
123-
def save(self, commit: bool = ...) -> AbstractBaseUser: ...
119+
user: _UserType
120+
def __init__(self, user: _UserType, *args: Any, **kwargs: Any) -> None: ...
121+
def save(self, commit: bool = ...) -> _UserType: ...
124122

125123
class PasswordChangeForm(SetPasswordForm):
126-
error_messages: _ErrorMessagesDict
127124
old_password: forms.Field
128125
def clean_old_password(self) -> str: ...
129126

130-
class AdminPasswordChangeForm(forms.Form):
127+
class AdminPasswordChangeForm(Generic[_UserType], forms.Form):
131128
error_messages: _ErrorMessagesDict
132129
required_css_class: str
133130
usable_password_help_text: str
134131
password1: forms.Field
135132
password2: forms.Field
136-
user: AbstractBaseUser
137-
def __init__(self, user: AbstractBaseUser, *args: Any, **kwargs: Any) -> None: ...
138-
def save(self, commit: bool = ...) -> AbstractBaseUser: ...
133+
user: _UserType
134+
def __init__(self, user: _UserType, *args: Any, **kwargs: Any) -> None: ...
135+
def save(self, commit: bool = ...) -> _UserType: ...
139136
@property
140137
def changed_data(self) -> list[str]: ...
141138

django-stubs/contrib/auth/middleware.pyi

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
from collections.abc import Callable
22
from typing import Any, ClassVar
33

4-
from django.contrib.auth.base_user import _UserModel
5-
from django.contrib.auth.models import AnonymousUser
4+
from django.contrib.auth.models import _AnyUser
65
from django.http import HttpRequest, HttpResponseBase, HttpResponseRedirect
76
from django.utils.deprecation import MiddlewareMixin
87

9-
def get_user(request: HttpRequest) -> AnonymousUser | _UserModel: ...
10-
async def auser(request: HttpRequest) -> AnonymousUser | _UserModel: ...
8+
def get_user(request: HttpRequest) -> _AnyUser: ...
9+
async def auser(request: HttpRequest) -> _AnyUser: ...
1110

1211
class AuthenticationMiddleware(MiddlewareMixin):
1312
def process_request(self, request: HttpRequest) -> None: ...

django-stubs/contrib/auth/models.pyi

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,20 @@ from django.db.models.manager import EmptyManager
1212
from django.utils.functional import _StrOrPromise
1313
from typing_extensions import Self
1414

15-
_AnyUser: TypeAlias = Model | AnonymousUser
15+
# This is our "placeholder" type the mypy plugin refines to configured 'AUTH_USER_MODEL'
16+
# wherever it is used as a type. The most recognised example of this is (probably)
17+
# `HttpRequest.user`
18+
_User: TypeAlias = AbstractBaseUser
1619

17-
def update_last_login(sender: type[AbstractBaseUser], user: AbstractBaseUser, **kwargs: Any) -> None: ...
20+
_AnyUser: TypeAlias = _User | AnonymousUser
21+
22+
# These are only needed for generic classes in order to bind to a specific implementation
23+
_AnyUserType = TypeVar("_AnyUserType", bound=_AnyUser) # noqa: PYI018
24+
25+
# do not use the alias `_User` so the bound remains at `AbstractUser`
26+
_UserType = TypeVar("_UserType", bound=AbstractUser)
27+
28+
def update_last_login(sender: type[_User], user: _User, **kwargs: Any) -> None: ...
1829

1930
class PermissionManager(models.Manager[Permission]):
2031
def get_by_natural_key(self, codename: str, app_label: str, model: str) -> Permission: ...
@@ -38,23 +49,21 @@ class Group(models.Model):
3849
permissions = models.ManyToManyField(Permission)
3950
def natural_key(self) -> tuple[str]: ...
4051

41-
_T = TypeVar("_T", bound=Model)
42-
43-
class UserManager(BaseUserManager[_T]):
52+
class UserManager(BaseUserManager[_UserType]):
4453
def create_user(
4554
self, username: str, email: str | None = ..., password: str | None = ..., **extra_fields: Any
46-
) -> _T: ...
55+
) -> _UserType: ...
4756
def create_superuser(
4857
self, username: str, email: str | None = ..., password: str | None = ..., **extra_fields: Any
49-
) -> _T: ...
58+
) -> _UserType: ...
5059
def with_perm(
5160
self,
5261
perm: str | Permission,
5362
is_active: bool = ...,
5463
include_superusers: bool = ...,
5564
backend: str | None = ...,
5665
obj: Model | None = ...,
57-
) -> QuerySet[_T]: ...
66+
) -> QuerySet[_UserType]: ...
5867

5968
class PermissionsMixin(models.Model):
6069
is_superuser = models.BooleanField()

django-stubs/contrib/auth/password_validation.pyi

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ from collections.abc import Mapping, Sequence
22
from pathlib import Path, PosixPath
33
from typing import Any, Protocol, type_check_only
44

5-
from django.contrib.auth.base_user import _UserModel
5+
from django.contrib.auth.models import _User
66

77
@type_check_only
88
class PasswordValidator(Protocol):
9-
def validate(self, password: str, user: _UserModel | None = ..., /) -> None: ...
9+
def validate(self, password: str, user: _User | None = ..., /) -> None: ...
1010
def get_help_text(self) -> str: ...
1111

1212
def get_default_password_validators() -> list[PasswordValidator]: ...
1313
def get_password_validators(validator_config: Sequence[Mapping[str, Any]]) -> list[PasswordValidator]: ...
1414
def validate_password(
15-
password: str, user: _UserModel | None = ..., password_validators: Sequence[PasswordValidator] | None = ...
15+
password: str, user: _User | None = ..., password_validators: Sequence[PasswordValidator] | None = ...
1616
) -> None: ...
1717
def password_changed(
18-
password: str, user: _UserModel | None = ..., password_validators: Sequence[PasswordValidator] | None = ...
18+
password: str, user: _User | None = ..., password_validators: Sequence[PasswordValidator] | None = ...
1919
) -> None: ...
2020
def password_validators_help_texts(password_validators: Sequence[PasswordValidator] | None = ...) -> list[str]: ...
2121

@@ -24,24 +24,24 @@ password_validators_help_text_html: Any
2424
class MinimumLengthValidator:
2525
min_length: int
2626
def __init__(self, min_length: int = ...) -> None: ...
27-
def validate(self, password: str, user: _UserModel | None = ...) -> None: ...
27+
def validate(self, password: str, user: _User | None = ...) -> None: ...
2828
def get_help_text(self) -> str: ...
2929

3030
class UserAttributeSimilarityValidator:
3131
DEFAULT_USER_ATTRIBUTES: Sequence[str]
3232
user_attributes: Sequence[str]
3333
max_similarity: float
3434
def __init__(self, user_attributes: Sequence[str] = ..., max_similarity: float = ...) -> None: ...
35-
def validate(self, password: str, user: _UserModel | None = ...) -> None: ...
35+
def validate(self, password: str, user: _User | None = ...) -> None: ...
3636
def get_help_text(self) -> str: ...
3737

3838
class CommonPasswordValidator:
3939
DEFAULT_PASSWORD_LIST_PATH: Path
4040
passwords: set[str]
4141
def __init__(self, password_list_path: Path | PosixPath | str = ...) -> None: ...
42-
def validate(self, password: str, user: _UserModel | None = ...) -> None: ...
42+
def validate(self, password: str, user: _User | None = ...) -> None: ...
4343
def get_help_text(self) -> str: ...
4444

4545
class NumericPasswordValidator:
46-
def validate(self, password: str, user: _UserModel | None = ...) -> None: ...
46+
def validate(self, password: str, user: _User | None = ...) -> None: ...
4747
def get_help_text(self) -> str: ...

0 commit comments

Comments
 (0)