Skip to content

Commit 8a93324

Browse files
feat: adding typehints
1 parent 60ab226 commit 8a93324

12 files changed

Lines changed: 191 additions & 126 deletions

File tree

rest_framework_simplejwt/authentication.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1+
from typing import Optional, Set, Tuple, TypeVar
2+
13
from django.contrib.auth import get_user_model
4+
from django.contrib.auth.models import AbstractBaseUser
25
from django.utils.translation import gettext_lazy as _
36
from rest_framework import HTTP_HEADER_ENCODING, authentication
7+
from rest_framework.request import Request
48

59
from .exceptions import AuthenticationFailed, InvalidToken, TokenError
10+
from .models import TokenUser
611
from .settings import api_settings
12+
from .tokens import Token
713

814
AUTH_HEADER_TYPES = api_settings.AUTH_HEADER_TYPES
915

1016
if not isinstance(api_settings.AUTH_HEADER_TYPES, (list, tuple)):
1117
AUTH_HEADER_TYPES = (AUTH_HEADER_TYPES,)
1218

13-
AUTH_HEADER_TYPE_BYTES = {h.encode(HTTP_HEADER_ENCODING) for h in AUTH_HEADER_TYPES}
19+
AUTH_HEADER_TYPE_BYTES: Set[bytes] = {
20+
h.encode(HTTP_HEADER_ENCODING) for h in AUTH_HEADER_TYPES
21+
}
22+
23+
AuthUser = TypeVar("AuthUser", AbstractBaseUser, TokenUser)
1424

1525

1626
class JWTAuthentication(authentication.BaseAuthentication):
@@ -22,11 +32,11 @@ class JWTAuthentication(authentication.BaseAuthentication):
2232
www_authenticate_realm = "api"
2333
media_type = "application/json"
2434

25-
def __init__(self, *args, **kwargs):
35+
def __init__(self, *args, **kwargs) -> None:
2636
super().__init__(*args, **kwargs)
2737
self.user_model = get_user_model()
2838

29-
def authenticate(self, request):
39+
def authenticate(self, request: Request) -> Optional[Tuple[AuthUser, Token]]:
3040
header = self.get_header(request)
3141
if header is None:
3242
return None
@@ -39,13 +49,13 @@ def authenticate(self, request):
3949

4050
return self.get_user(validated_token), validated_token
4151

42-
def authenticate_header(self, request):
52+
def authenticate_header(self, request: Request) -> str:
4353
return '{} realm="{}"'.format(
4454
AUTH_HEADER_TYPES[0],
4555
self.www_authenticate_realm,
4656
)
4757

48-
def get_header(self, request):
58+
def get_header(self, request: Request) -> bytes:
4959
"""
5060
Extracts the header containing the JSON web token from the given
5161
request.
@@ -58,7 +68,7 @@ def get_header(self, request):
5868

5969
return header
6070

61-
def get_raw_token(self, header):
71+
def get_raw_token(self, header: bytes) -> Optional[bytes]:
6272
"""
6373
Extracts an unvalidated JSON web token from the given "Authorization"
6474
header value.
@@ -81,7 +91,7 @@ def get_raw_token(self, header):
8191

8292
return parts[1]
8393

84-
def get_validated_token(self, raw_token):
94+
def get_validated_token(self, raw_token: bytes) -> Token:
8595
"""
8696
Validates an encoded JSON web token and returns a validated token
8797
wrapper object.
@@ -106,7 +116,7 @@ def get_validated_token(self, raw_token):
106116
}
107117
)
108118

109-
def get_user(self, validated_token):
119+
def get_user(self, validated_token: Token) -> AuthUser:
110120
"""
111121
Attempts to find and return a user using the given validated token.
112122
"""
@@ -132,7 +142,7 @@ class JWTStatelessUserAuthentication(JWTAuthentication):
132142
token provided in a request header without performing a database lookup to obtain a user instance.
133143
"""
134144

135-
def get_user(self, validated_token):
145+
def get_user(self, validated_token: Token) -> AuthUser:
136146
"""
137147
Returns a stateless user object which is backed by the given validated
138148
token.
@@ -148,7 +158,7 @@ def get_user(self, validated_token):
148158
JWTTokenUserAuthentication = JWTStatelessUserAuthentication
149159

150160

151-
def default_user_authentication_rule(user):
161+
def default_user_authentication_rule(user: AuthUser) -> bool:
152162
# Prior to Django 1.10, inactive users could be authenticated with the
153163
# default `ModelBackend`. As of Django 1.10, the `ModelBackend`
154164
# prevents inactive users from authenticating. App designers can still

rest_framework_simplejwt/backends.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import json
2+
from collections.abc import Iterable
23
from datetime import timedelta
3-
from typing import Optional, Type, Union
4+
from typing import Any, Dict, Optional, Type, Union
45

56
import jwt
67
from django.utils.translation import gettext_lazy as _
78
from jwt import InvalidAlgorithmError, InvalidTokenError, algorithms
89

910
from .exceptions import TokenBackendError
11+
from .tokens import Token
1012
from .utils import format_lazy
1113

1214
try:
@@ -32,15 +34,15 @@
3234
class TokenBackend:
3335
def __init__(
3436
self,
35-
algorithm,
36-
signing_key=None,
37-
verifying_key="",
38-
audience=None,
39-
issuer=None,
40-
jwk_url: str = None,
41-
leeway: Union[float, int, timedelta] = None,
37+
algorithm: str,
38+
signing_key: Optional[str] = None,
39+
verifying_key: str = "",
40+
audience: Union[str, Iterable, None] = None,
41+
issuer: Optional[str] = None,
42+
jwk_url: Optional[str] = None,
43+
leeway: Union[float, int, timedelta, None] = None,
4244
json_encoder: Optional[Type[json.JSONEncoder]] = None,
43-
):
45+
) -> None:
4446
self._validate_algorithm(algorithm)
4547

4648
self.algorithm = algorithm
@@ -57,7 +59,7 @@ def __init__(
5759
self.leeway = leeway
5860
self.json_encoder = json_encoder
5961

60-
def _validate_algorithm(self, algorithm):
62+
def _validate_algorithm(self, algorithm: str) -> None:
6163
"""
6264
Ensure that the nominated algorithm is recognized, and that cryptography is installed for those
6365
algorithms that require it
@@ -91,7 +93,7 @@ def get_leeway(self) -> timedelta:
9193
)
9294
)
9395

94-
def get_verifying_key(self, token):
96+
def get_verifying_key(self, token: Token) -> Optional[str]:
9597
if self.algorithm.startswith("HS"):
9698
return self.signing_key
9799

@@ -103,7 +105,7 @@ def get_verifying_key(self, token):
103105

104106
return self.verifying_key
105107

106-
def encode(self, payload):
108+
def encode(self, payload: Dict[str, Any]) -> str:
107109
"""
108110
Returns an encoded token for the given payload dictionary.
109111
"""
@@ -125,7 +127,7 @@ def encode(self, payload):
125127
# For PyJWT >= 2.0.0a1
126128
return token
127129

128-
def decode(self, token, verify=True):
130+
def decode(self, token: Token, verify: bool = True) -> Dict[str, Any]:
129131
"""
130132
Performs a validation of the given token and returns its payload
131133
dictionary.

rest_framework_simplejwt/exceptions.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Any, Dict, Optional, Union
2+
13
from django.utils.translation import gettext_lazy as _
24
from rest_framework import exceptions, status
35

@@ -11,7 +13,14 @@ class TokenBackendError(Exception):
1113

1214

1315
class DetailDictMixin:
14-
def __init__(self, detail=None, code=None):
16+
default_detail: str
17+
default_code: str
18+
19+
def __init__(
20+
self,
21+
detail: Union[Dict[str, Any], str, None] = None,
22+
code: Optional[str] = None,
23+
) -> None:
1524
"""
1625
Builds a detail dictionary for the error to give more information to API
1726
users.
@@ -26,7 +35,7 @@ def __init__(self, detail=None, code=None):
2635
if code is not None:
2736
detail_dict["code"] = code
2837

29-
super().__init__(detail_dict)
38+
super().__init__(detail_dict) # type: ignore
3039

3140

3241
class AuthenticationFailed(DetailDictMixin, exceptions.AuthenticationFailed):

rest_framework_simplejwt/models.py

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
from typing import TYPE_CHECKING, Any, List, Optional, Union
2+
13
from django.contrib.auth import models as auth_models
24
from django.db.models.manager import EmptyManager
35
from django.utils.functional import cached_property
46

57
from .settings import api_settings
68

9+
if TYPE_CHECKING:
10+
from .tokens import Token
11+
712

813
class TokenUser:
914
"""
@@ -22,87 +27,89 @@ class instead of a `User` model instance. Instances of this class act as
2227
_groups = EmptyManager(auth_models.Group)
2328
_user_permissions = EmptyManager(auth_models.Permission)
2429

25-
def __init__(self, token):
30+
def __init__(self, token: "Token") -> None:
2631
self.token = token
2732

28-
def __str__(self):
33+
def __str__(self) -> str:
2934
return f"TokenUser {self.id}"
3035

3136
@cached_property
32-
def id(self):
37+
def id(self) -> Union[int, str]:
3338
return self.token[api_settings.USER_ID_CLAIM]
3439

3540
@cached_property
36-
def pk(self):
41+
def pk(self) -> Union[int, str]:
3742
return self.id
3843

3944
@cached_property
40-
def username(self):
45+
def username(self) -> str:
4146
return self.token.get("username", "")
4247

4348
@cached_property
44-
def is_staff(self):
49+
def is_staff(self) -> bool:
4550
return self.token.get("is_staff", False)
4651

4752
@cached_property
48-
def is_superuser(self):
53+
def is_superuser(self) -> bool:
4954
return self.token.get("is_superuser", False)
5055

51-
def __eq__(self, other):
56+
def __eq__(self, other: object) -> bool:
57+
if not isinstance(other, TokenUser):
58+
return NotImplemented
5259
return self.id == other.id
5360

54-
def __ne__(self, other):
61+
def __ne__(self, other: object) -> bool:
5562
return not self.__eq__(other)
5663

57-
def __hash__(self):
64+
def __hash__(self) -> int:
5865
return hash(self.id)
5966

60-
def save(self):
67+
def save(self) -> None:
6168
raise NotImplementedError("Token users have no DB representation")
6269

63-
def delete(self):
70+
def delete(self) -> None:
6471
raise NotImplementedError("Token users have no DB representation")
6572

66-
def set_password(self, raw_password):
73+
def set_password(self, raw_password: str) -> None:
6774
raise NotImplementedError("Token users have no DB representation")
6875

69-
def check_password(self, raw_password):
76+
def check_password(self, raw_password: str) -> None:
7077
raise NotImplementedError("Token users have no DB representation")
7178

7279
@property
73-
def groups(self):
80+
def groups(self) -> auth_models.Group:
7481
return self._groups
7582

7683
@property
77-
def user_permissions(self):
84+
def user_permissions(self) -> auth_models.Permission:
7885
return self._user_permissions
7986

80-
def get_group_permissions(self, obj=None):
87+
def get_group_permissions(self, obj: Optional[object] = None) -> set:
8188
return set()
8289

83-
def get_all_permissions(self, obj=None):
90+
def get_all_permissions(self, obj: Optional[object] = None) -> set:
8491
return set()
8592

86-
def has_perm(self, perm, obj=None):
93+
def has_perm(self, perm: str, obj: Optional[object] = None) -> bool:
8794
return False
8895

89-
def has_perms(self, perm_list, obj=None):
96+
def has_perms(self, perm_list: List[str], obj: Optional[object] = None) -> bool:
9097
return False
9198

92-
def has_module_perms(self, module):
99+
def has_module_perms(self, module: str) -> bool:
93100
return False
94101

95102
@property
96-
def is_anonymous(self):
103+
def is_anonymous(self) -> bool:
97104
return False
98105

99106
@property
100-
def is_authenticated(self):
107+
def is_authenticated(self) -> bool:
101108
return True
102109

103-
def get_username(self):
110+
def get_username(self) -> str:
104111
return self.username
105112

106-
def __getattr__(self, attr):
113+
def __getattr__(self, attr: str) -> Optional[Any]:
107114
"""This acts as a backup attribute getter for custom claims defined in Token serializers."""
108115
return self.token.get(attr, None)

0 commit comments

Comments
 (0)